Path traversal bugs are old news in web application security — everyone knows to test for ../../../etc/passwd in a file parameter. But when the vulnerable server itself is launched as root through a sudo rule, a “just” file-read bug stops being a content-disclosure footnote and becomes a full privilege escalation path.
That’s exactly what happened on this box.
The Setup
I had a shell as the user cassie on a machine called clue. As always, the first thing I checked was sudo -l:

User cassie may run the following commands on clue:
(ALL) NOPASSWD: /usr/local/bin/cassandra-web
cassie could run /usr/local/bin/cassandra-web as root, no password required. cassandra-web is a small Ruby gem that spins up a lightweight web front-end for browsing an Apache Cassandra cluster. I had no reason to trust it blindly just because it looked obscure — obscure tools get far less security scrutiny than mainstream ones, which makes them a better bet, not a worse one, for hiding an exploitable bug.
Enumeration
I ran the command exactly as the sudo rule allowed, giving it a bind address and port:
sudo -u root /usr/local/bin/cassandra-web -B 0.0.0.0:9991 -u cassie -p SecondBiteTheApple330
This launched cassandra-web as root, listening on port 9991, using credentials that were already known to cassie from the sudo invocation itself. I backgrounded the job with bg so I could keep working in the same shell while the server stayed up.
With the server running as root, the next question was simple: does it let me read files it shouldn’t? I pulled the gem’s source from /var/lib/gems/2.7.0/gems/cassandra-web-0.5.0/app/public to check how it handled file paths internally:

DT = '../'
DT_NUM = 8
# ...
payload = urllib.parse.quote_plus(DT * self.number + self.file)
# ...
req = requests.get(target + '/' + payload)
That answered the question immediately. The application builds a file-serving path by prepending a fixed number of ../ traversal sequences (eight of them, hardcoded) to a user-suppliable filename, then URL-encodes the result and requests it from its own web server. There’s no validation restricting the requested file to an intended directory — the traversal depth is baked in specifically so that a relative filename can walk all the way up to the filesystem root and back down into an arbitrary path.
Exploitation
Since the server was already running as root, any file it could read on disk — regardless of ownership or permissions — it could now serve back to me over HTTP, traversal and all. The obvious target on a multi-user box: another user’s SSH private key.
I restarted the server bound to a fresh port and fired the traversal payload directly at it with curl:

sudo /usr/local/bin/cassandra-web -B 0.0.0.0:4442 -u cassie -p SecondBiteTheApple330 &
curl --path-as-is http://127.0.0.1:4442/../../../../../../../../home/anthony/.ssh/id_rsa
The response came back as a complete OpenSSH private key — anthony’s. --path-as-is was important here: curl normalizes ../ sequences out of URLs by default, which would have silently defeated the traversal before the request ever reached the server. Telling curl to leave the path alone let the raw traversal string hit cassandra-web intact, where the application’s own eight-level-deep prefix carried it the rest of the way to /home/anthony/.ssh/id_rsa.
With a full private key in hand, I saved it locally and used it directly:
ssh -i id_rsa root@localhost

root@clue:~# id
uid=0(root) gid=0(root) groups=0(root)
The key authenticated straight into a root shell on the box — whether because root’s authorized_keys trusted the same key pair Anthony used, or because of how the lab environment was provisioned, the practical result was the same: a file-read bug in a root-owned process turned directly into root access.
Why This Works
Three things had to align for this to work:
- A sudo rule let a low-privilege user launch an arbitrary web application as root. That alone is a huge privilege gap — the moment
cassandra-webruns as root, every bug in it inherits root’s file-access rights. - The application had a path traversal bug baked into its own design, not just a missing sanitization check — the hardcoded
../prefix count strongly suggests this was meant to let the tool browse Cassandra data directories flexibly, without anyone considering what happens when the tool itself runs as root. curl’s default URL normalization had to be bypassed with--path-as-is, since the traversal sequence would otherwise be collapsed away client-side before it ever reached the vulnerable server.
Once those aligned, the file-read primitive was powerful enough to grab any file readable by root — SSH keys being the highest-value target on almost any box.
Root Cause
The root cause is granting root execution rights over a niche, lightly-audited web application, combined with that application’s own unsandboxed path handling. Running any web server as root is already a risk multiplier — it means every bug the server ships with, including ones nobody has looked for yet, is a root-level bug by default.
Fix:
# Never run a web front-end as root. Bind it to a dedicated low-privilege
# service account instead, and drop any sudo rule granting it root:
# remove: cassie ALL=(root) NOPASSWD: /usr/local/bin/cassandra-web
# If cassandra-web must run with elevated Cassandra access, use a
# dedicated service account with only the DB privileges it needs —
# not root on the underlying host.
# Patch or replace the vulnerable path-building logic so requested
# files are resolved and confined to an intended base directory:
requested = File.expand_path(File.join(base_dir, filename))
raise "invalid path" unless requested.start_path?(base_dir)
Key Takeaways
- Any sudo rule that lets a low-privilege user run a web server or application binary as root deserves a source-code review, not just a version check — the privilege gap alone makes any bug inside it critical
- Path traversal isn’t always a missing sanitization bug — sometimes it’s designed-in behavior (a hardcoded
../depth) meant for a legitimate use case that nobody threat-modeled against running as root curl --path-as-ismatters when testing traversal payloads — default URL normalization will silently eat your../sequences before the request even leaves your machine- SSH private keys are one of the highest-value targets for any arbitrary file read primitive — always check
~/.ssh/id_rsafor every user on the box once you have read access to an unexpected file
Questions? Hit me up on X.