Building a WordPress CVE Lab for Bug Bounty Practice
If you’re learning web app pentesting or working your way into bug bounty, WordPress is one of the richest hunting grounds you can practice on. Tens of thousands of plugins, a massive historical CVE database, and most vulnerabilities are still reachable against old plugin versions you can download and install yourself. The problem is getting a sensible lab environment that lets you drop in vulnerable plugin versions, reproduce known CVEs, and actually *understand* how each bug class works — rather than just running automated scanners against live targets (which you shouldn’t be doing anyway, whilst learning).
Automattic’s WPScan Vulnerability Test Bench (https://github.com/Automattic/wpscan-vulnerability-test-bench) is purpose-built for exactly this. It spins up a standardised WordPress multisite with pre-configured users at every privilege level so when a CVE write-up says “exploitable by any authenticated Subscriber,” you have a Subscriber account ready to log in with. When it says “requires Editor role,” you’ve got that too.
This post walks through the whole setup on Windows via WSL, then shows you how to use it as a practice lab for studying historical WordPress plugin CVEs.
Why this setup is good for learning
A few things matter when you’re building a skill in this space:
- Reproducibility – When you work through a CVE write-up, you need the same WordPress version, same user roles, same multisite config the original researcher used. The test bench gives you that baseline.
- Every privilege level available – Most plugin bugs are authentication- or authorisation-sensitive. You need to be able to test as a Subscriber, then as a Contributor, then as an Editor, to actually understand the auth boundary the bug crosses.
- Disposable – Tearing down and rebuilding is a single command, so you can try dangerous payloads without worrying about polluting your environment.
- Local only – Nothing you do here touches the public internet. You can exploit freely without legal or ethical concerns.
Prerequisites
A Windows 10 (build 19041+) or Windows 11 machine with virtualisation enabled in BIOS. That’s it.
Step 1: Install WSL and Ubuntu
Open **PowerShell as Administrator** — right-click the Start button and choose “Terminal (Admin)”.
Type:
wsl --install -d Ubuntu
This one command enables the WSL feature, enables the Virtual Machine Platform, installs WSL2, and installs the latest Ubuntu LTS. **Reboot when prompted.**
After reboot, Ubuntu launches automatically and asks you to create a UNIX username and password. This is separate from your Windows login pick something memorable because you’ll be typing it for `sudo` commands.
Confirm you’re on WSL2:
wsl -l -v
Ubuntu should show `VERSION 2`. If it shows 1, run `wsl –set-version Ubuntu 2`.
Step 2: Update Ubuntu
From here on, everything happens **inside the Ubuntu terminal**. Launch it from the Start menu.
sudo apt update && sudo apt upgrade -y
Step 3: Install Docker
This is where a lot of guides will send you down a rabbit hole. Ubuntu’s default `docker.io` package is convenient but ships without the buildx and compose plugins that DDEV requires. You’ll get a cryptic “compose build requires buildx 0.17.0 or later” error later and end up manually downloading binaries.
Skip that entirely by using Docker’s official repository, which bundles the plugins:
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Let your user run Docker without `sudo`:
sudo usermod -aG docker $USER
sudo service docker start
Step 4: Enable systemd
WSL doesn’t run systemd by default, which means Docker won’t come back after a reboot unless you start it manually each time. Fix that with a one-line config file:
sudo tee /etc/wsl.conf > /dev/null <<EOF
[boot]
systemd=true
EOF
Step 5: Restart WSL
This is the step people miss. The docker group change from Step 3 only takes effect in new shells, and systemd only activates after WSL fully restarts. Closing the terminal window isn’t enough you need to shut down the WSL VM itself.
From **PowerShell** (not Ubuntu):
wsl --shutdown
Wait about ten seconds, then reopen Ubuntu from the Start menu.
Verify Docker works without `sudo`:
sudo systemctl enable --now docker
Step 6: Install DDEV
DDEV’s install script does everything in one command — installs the `ddev` binary, installs `mkcert`, and registers a local certificate authority so HTTPS works on your test sites.
curl -fsSL https://ddev.com/install.sh | bash
Enter your sudo password when prompted.
Verify Install:
ddev --version
Step 7: Clone and launch the test bench
Clone into your Linux home directory, **not** `/mnt/c/…`. File I/O between WSL and the Windows filesystem is dramatically slower and will make `ddev start` crawl.
cd ~
git clone https://github.com/Automattic/wpscan-vulnerability-test-bench.git
cd wpscan-vulnerability-test-bench
ddev start
First start pulls Docker images and runs WordPress’s install routine. Takes a few minutes. When it finishes, DDEV prints the site URL — typically `https://wpscan-vulnerability-test-bench.ddev.site`.
Open the admin:
ddev launch wp-admin/
Step 8: The users you’ll be logging in as
The test bench creates six users, password `password` for all:
- superadmin — network superadmin
- simpleadmin — site admin
- editor — Editor role
- author — Author role
- contributor — Contributor role
- subscriber — Subscriber role
Keep this list handy. A huge proportion of the CVEs you’ll study hinge on privilege boundaries, and you’ll be switching between these accounts constantly.
Using the lab to study historical CVEs
1. Pick a CVE
Good places to find WordPress plugin vulnerabilities with enough detail to reproduce:
- WPScan’s vulnerability database — the canonical source, with affected version ranges.
- Wordfence Intelligence — detailed write-ups, often with PoC code.
- Patchstack Database — similar coverage, good taxonomy by bug class.
For your first few, pick bugs in popular, freely-downloadable plugins where the affected version is still available. Aim for variety across bug classes:
- Broken access control / IDOR — common and great for learning auth boundaries
- Stored / reflected XSS — learn how WordPress’s sanitisation helpers should be used (and how plugins skip them)
- SQL injection — increasingly rare but still appears, especially around
$wpdb->prepare()misuse - CSRF — plugins forgetting nonce checks on admin actions
- Authenticated RCE — arbitrary file uploads, PHP deserialisation, arbitrary option updates leading to code execution
- SSRF — plugins that fetch URLs without validation
2. Download the exact vulnerable version
WPScan reports tell you the affected version range, e.g. “vulnerable in versions < 2.4.1.” Download that specific version from the WordPress plugin SVN. For a plugin slugged example-plugin:
ddev wp plugin install https://downloads.wordpress.org/plugin/PLUGINNAME.VERSION.ZIP --activate
3. Install it in the test bench
You’ve got two options. Drop the plugin directory into wp-content/plugins/ and activate it in the admin UI, or if it’s still on wp.org (some vulnerable versions still are), let wp-cli install it:
ddev wp plugin install pathtoplugin.zip --activate
4. Configure the vulnerable state
Read the CVE carefully. Most plugin vulns require specific configuration settings enabled, shortcodes placed on a page, a post created with specific metadata. Set up whatever the write-up describes before you start testing. If there is no writeup then its up to you to set up the plugin in and go find it yourself, this helps build your hunting skills!
5. Reproduce the bug
Log in as the role the CVE specifies (or as unauthenticated if it’s pre-auth). Intercept the request in Burp Suite or ZAP, study what the plugin’s doing, trigger the bug, confirm the impact.
This is where the real learning happens not in running the PoC, but in reading the vulnerable plugin’s source code and seeing exactly what the author got wrong. Open the plugin files in VS Code (which integrates beautifully with WSL — code . from the project directory opens it) and trace from the HTTP endpoint to the vulnerable sink.
6. Study the patch
Find the fixed version of the plugin. Diff it against the vulnerable version:
diff -r plugin-2.4.0 plugin-2.4.1
The patch is where most of the learning lives. Seeing that the fix was “add current_user_can('edit_posts') check before the action” teaches you to look for missing capability checks in every plugin you audit afterwards.
7. Reset for the next one
ddev delete--omit-snapshots
ddev start
Fresh environment, no state bleed, ready for the next CVE.
Burp Suite on WSL
If you’re serious about this, you’ll want Burp running alongside. Install Burp Community (or Pro) in Windows itself, not in WSL — the Java GUI is miserable through WSLg. Point your browser at Burp’s proxy on 127.0.0.1:8080 and it’ll intercept requests to the DDEV site just as it would any other local target. Install Burp’s CA certificate in your browser to see HTTPS traffic.
Day-to-day DDEV commands
ddev stop # stop containers
ddev start # start again
ddev logs # aggregated server logs (useful for debugging PHP errors during exploitation)
ddev wp <command> # run wp-cli inside the container
ddev ssh # shell into the web container
ddev describe # URLs, database info, all the details
ddev delete --omit-snapshots # full teardown, files stay
ddev ssh is particularly useful when you’re exploiting a bug that writes a file — you can hop into the container and verify the file actually landed where you expected.
Troubleshooting
permission denied while trying to connect to the docker API at unix:///var/run/docker.sock
Your shell hasn’t picked up the docker group. Run wsl --shutdown from PowerShell, wait ten seconds, reopen Ubuntu.
compose build requires buildx 0.17.0 or later
You installed docker.io instead of using Step 3’s official Docker repo. Easiest fix is to sudo apt remove docker.io and follow Step 3 properly.
Port conflicts on 80 or 443 Something on Windows is listening — often IIS or another local web server. Either stop that service or reassign DDEV’s ports with
ddev config --router-http-port=8080 --router-https-port=8443.
Sluggish performance You cloned into /mnt/c/.... Move the project to your Linux home directory.
Browser shows certificate warning The mkcert -install step didn’t register the CA in Windows. Run mkcert -install again from Ubuntu, then restart your browser.
Ethics and legal bit
This is worth saying plainly. Everything in this post assumes you’re attacking your own local lab and publicly-known CVEs in old plugin versions you’ve deliberately installed on that lab. That’s legal, educational, and the right way to build skill.
What this setup is not for:
- Scanning live WordPress sites without written permission, even if you think they’re vulnerable
- “Testing” a friend’s site, or your employer’s site, without an authorised scope
- Running exploits against plugins on sites you don’t own
If you want to find bugs on live targets, get onto a legitimate bug bounty platform — HackerOne, Bugcrowd, Intigriti — and work within their scopes. Patchstack and wordfence also runs a dedicated WordPress plugin bounty programme where you can submit novel findings in real plugins for real payouts, which is the natural progression once you’ve spent a few weeks in a local lab reproducing historical CVEs.
Where to go from here
Once you’re comfortable reproducing existing CVEs, start auditing plugins that haven’t been reported on. Pick a popular one, install it in the test bench, and read the source with a mental checklist: where does user input enter, does every admin action have a nonce check, does every sensitive function have a capability check, does every database query use $wpdb->prepare() with proper placeholders, are uploaded files validated. You’ll find bugs. Submit them responsibly the plugin author first, then through Patchstack or WPScan if they don’t respond.
The path from “reproducing known CVEs in a local lab” to “finding novel bugs that pay” is shorter than people think. The lab you’ve just built is where that journey starts.
