Symlink abuse in a sudo script
A common Linux escalation pattern: an unprivileged user can run via sudo (often NOPASSWD) a system-owned script that manipulates files as root. If that script follows symbolic links without resolving the real path, the user controls which file root ends up reading or touching.
The typical flaw. The script tries to protect itself by checking a link's target and rejecting sensitive paths:
LINK_TARGET=$(readlink "$LINK") # only the FIRST hop
if echo "$LINK_TARGET" | grep -Eq '(etc|root)'; then
unlink "$LINK" # deletes it
else
cat "$QUAR_DIR/$LINK_NAME" # reads it as root
fiThe mistake is using readlink (a single hop) instead of readlink -f (resolves the full chain). Only the direct target is inspected.
The bypass: a double link. Two symlinks are chained. The first points to an "innocent" target; the second, to the sensitive file:
ln -s /root/root.txt pwn.txt # hidden link (real target)
ln -s pwn.txt pwn.png # what readlink sees: 'pwn.txt', passes the filterreadlink pwn.png returns pwn.txt (it contains neither etc nor root, so the filter lets it through). But when the script runs cat pwn.png, the kernel resolves the whole chain down to /root/root.txt and root reads that content for us.
Variants and nuances. Sometimes the script's behaviour depends on an environment variable (in LinkVortex, CHECK_CONTENT): if the sudoers rule keeps that variable with env_keep, just export it (sudo CHECK_CONTENT=true ...) to trigger the branch that prints the content. The most rewarding target isn't just root.txt: reading /root/.ssh/id_rsa yields a private key to log in over SSH as root and get a full shell, not merely a flag.
How to detect it. sudo logs the script invocation in /var/log/auth.log with its user and arguments; the abuse signature is the privileged process opening, through a link, a file outside its expected working directory (/root/.ssh/id_rsa, /etc/shadow): auditd over open/openat with the script's ppid reveals the real path resolved by the kernel, different from the one the script thought it validated. Creating a chain of symlinks in the quarantine/work directory (chained ln -s) just before the sudo is the preceding sign. A sudo <script> with an unexpected environment variable (CHECK_CONTENT=true) kept by env_keep also gives the manipulation away.
Defence. In any privileged script, always resolve the canonical path (realpath/readlink -f) before deciding, don't follow user-controlled links, and minimise env_keep in /etc/sudoers. Related to SUID abuse and, in general, to trusting paths the user controls.
Machines where it's used
- LinkVortexHackTheBox
- MarketingProving Grounds Practice