# WPProbe maps WordPress plugins to known CVEs without brute force

> WPProbe is a Go scanner that finds installed WordPress plugins by matching exposed REST API routes, then correlates them with Wordfence and WPScan vulnerability data. It is fast and quiet, but it inherits the blind spots of whatever the target happens to expose.

**Chocapikk/wpprobe** — A fast WordPress plugin enumeration tool

- Repository: https://github.com/Chocapikk/wpprobe
- Stars: 972 · Forks: 125
- Language: Go
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/chocapikk-wpprobe

## REST API matching replaces blind directory guessing

WPProbe answers a narrow question: which plugins are running on a given WordPress install. The project description calls it a scanner that detects installed plugins via REST API enumeration and themes from HTML discovery, then maps them to known vulnerabilities. The coverage claim is concrete: "Over 5000 plugins detectable without brute-force, thousands more with it." That number matters because enumeration is the stage where most of the work happens in a WordPress assessment. Identifying a plugin slug is what allows a tool to look up a CVE, a fix version, and an authentication requirement, and it is far easier to do reliably by reading what the site already publishes about itself than by guessing directory names.

The audience is narrow. This is written for engineers running recon against a WordPress target, or for a team checking its own estate. It is not a vulnerability scanner in the general sense, and it does not attempt exploitation. The README frames the whole approach with the slogan "Because why scan blind when WordPress exposes itself?", which is a fair summary of the design choice: read the exposed surface first, and only fall back to noisy requests if that fails to answer the question.

## Stealthy mode turns exposed routes into a plugin signature list

The default mode is stealthy, and the mechanism is a single discovery request followed by matching. Stealthy mode queries exposed REST API routes at `?rest_route=/` and matches the endpoints that come back against a precompiled database of known plugin signatures. The endpoint list a WordPress install publishes is unusually useful, because each plugin that registers a REST route leaves a trace of its own namespace in that response. Matching those namespaces against a compiled signature table turns one response into a plugin list without sending a single request per candidate plugin.

Themes are handled by a different route entirely. WPProbe parses `wp-content/themes/` references out of the page HTML, then fetches the version from the theme's `style.css`. The documentation says this approach generates minimal requests and avoids detection by WAFs, and the claim is about request volume rather than about evasion tricks. There is no fuzzing, no string mutation and no evasion logic in the description. The stealth comes from volume, which is also why the request count is the thing worth checking against your own logging.

One practical detail: the version comes from a static asset file, not from a database read. A site that serves a cached or heavily customized stylesheet can return a version string that does not match what is actually deployed, and that mismatch propagates straight into the vulnerability range comparison later on.

## Brute-force mode treats a 200 response as unproven

When REST enumeration is not enough, bruteforce mode checks plugin directories directly with GET requests. The interesting part is how it handles responses. A 403 confirms that a plugin exists, because the server refuses to list the directory. A 200 response proves nothing on its own, since the project notes that some WordPress instances return 200 for all paths. To avoid false positives in that case, the tool looks for `readme.txt` inside the directory listing and only accepts the plugin if it is there.

The three modes are selected with a flag:

```sh
wpprobe scan -u https://example.com --mode stealthy
wpprobe scan -u https://example.com --mode bruteforce
wpprobe scan -u https://example.com --mode hybrid
```

Hybrid runs stealthy first and then brute-force for whatever is still missing, which the documentation describes as maximum coverage at medium stealth. Themes are discovered through HTML in every mode, so switching to bruteforce does not change how themes are found. The practical difference is not accuracy but noise: bruteforce generates one request per candidate plugin directory, and the readme.txt confirmation adds a second request for every hit, so a large site with a permissive web server can produce a noticeable burst of requests to a single host. If a target sits behind a WAF with per-IP request thresholds, that burst is where the scan will get noticed or get cut off.

## Two external databases decide what a detection is worth

Finding a plugin is only half the job. WPProbe correlates what it finds with known CVEs from Wordfence and WPScan databases, matching version ranges to decide whether an installation is vulnerable. The documentation shows the search surface for that correlation layer:

```sh
wpprobe search --cve CVE-2024-1234
wpprobe search --plugin woocommerce
wpprobe search --severity critical
wpprobe search --auth Unauth
wpprobe search --title "SQL Injection" --details
```

and `wpprobe list` for statistics on what is loaded.

There is one sharp edge here. Wordfence deprecated its v2 API on March 9, 2026, and the project states that all WPProbe versions prior to v0.10.16 have broken `update-db` functionality as a result. In practice that means an old binary silently stops refreshing its data. By default `update-db` pulls a pre-built database published from the repository and refreshed every two hours by CI, so no API key is needed for the default path. If you would rather fetch from Wordfence directly, the project describes creating an account, generating a key under Account and Integrations, and setting it through the `WORDFENCE_API_KEY` environment variable or the `--api-key` flag. WPScan is different: `WPSCAN_API_TOKEN` covers that feed only on an Enterprise plan. An operator without that token is running a scan with a thinner vulnerability corpus than the interface implies.

## Getting a working binary on Kali, Nix, Docker or from source

The project ships several install paths, and the install paths are listed explicitly. Kali Linux includes it in kali-rolling, BlackArch lists it under its web application tools, Exegol bundles it, and NixOS carries a package. For a plain Go toolchain the project says Go 1.22 or newer is required for the install route, while the module file itself declares `go 1.25.0` and the Dockerfile builds on `golang:1.23-alpine`. From source the build is a single `go build`:

```sh
git clone https://github.com/Chocapikk/wpprobe && cd wpprobe && go build -o wpprobe
```

The Docker image is built as a two-stage build and installs `ca-certificates` and `tzdata` in the runtime layer, which matters because the scanner makes outbound HTTPS calls to targets and to the Wordfence and WPScan APIs. It declares `/data` and `/config` volumes, sets `XDG_CONFIG_HOME=/config`, and uses the working directory `/data` so files can be mounted in without path juggling. The example invocations cover the common shapes:

```sh
docker run -it --rm wpprobe scan -u https://example.com
docker run -it --rm -v $(pwd):/data wpprobe scan -f /data/targets.txt -o /data/results.csv
```

A first real run needs the database populated before results mean anything, which the quick start lists as three commands:

```sh
go install github.com/Chocapikk/wpprobe@latest
wpprobe update-db
wpprobe scan -u https://example.com
```

If the scan returns nothing, an empty or stale database is the first thing to rule out, not the target.

## Threading, proxies and a JSON shape that stays valid at any scale

For anything beyond a single site, the operational flags matter more than the discovery logic. The usage examples show multi-target runs with a thread count, custom headers, a proxy, a rate limit and version checking disabled:

```sh
wpprobe scan -f targets.txt -t 20
wpprobe scan -u https://example.com \
  --header "User-Agent: CustomAgent" \
  --proxy http://proxy:8080 \
  --rate-limit 10 \
  --no-check-version
```

A custom User-Agent and a proxy are the two levers that matter most when a target sits behind infrastructure that fingerprints clients. `--rate-limit` gives you the ability to slow down against a specific host, which is worth using on hybrid mode given the request burst described earlier.

Output goes to CSV or JSON with `-o`. The format documentation makes a deliberate point about the JSON format: it is always an array with one object per scanned target, so a file written from a single URL and a file written from a list both remain valid JSON documents. Downstream tooling can parse either without branching. The CSV header runs URL, Plugin, Version, Severity, AuthType, CVEs, CVE Links, CVSS Score, CVSS Vector, Title, which means severity and CVSS data travel with each row rather than requiring a second lookup.

Two smaller behaviours are worth knowing. On startup the tool contacts the newest published release to label its banner, caches that answer for 24 hours, and can be told not to make the call at all with `WPPROBE_NO_UPDATE_CHECK=1`. And `wpprobe update` only replaces binaries that came from a release, because a source build is usually ahead of the latest release and self-updating would turn it into a downgrade.

## Where the scan quietly under-reports

The blind spots follow directly from the mechanism. A plugin that registers no REST route leaves no trace for stealthy mode to match, so it will not appear in a stealthy-only run. If a site disables its REST API entirely, stealthy mode has nothing to work with and the operator must fall back to brute-force, with the noise that entails. Themes are better covered because they are read from HTML, but the version string comes from `style.css`, and a customised or cached stylesheet can report a version that does not match the deployed code, which turns into a wrong range comparison.

The vulnerability side has its own limits. Findings depend on the downloaded databases, and the Wordfence feed path broke for versions before v0.10.16 after the March 2026 v2 API change. The WPScan feed needs an Enterprise token. A scan that reports a clean result may therefore mean an empty database rather than a clean target, and the difference between those two states is not visible in the output.

Two more constraints are structural. Detection quality depends on how the target responds to the specific patterns the tool uses, so a hardened host that answers every path with 200 will break the brute-force confirmation step that relies on `readme.txt` appearing in a directory listing. And the tool reports what it can see and correlate; it does not attempt exploitation, does not verify that a listed CVE is actually reachable in the site's configuration, and does not document any rollback or clean-up procedure for the database it writes.

## Conclusion

WPProbe earns a place in an early recon pass when a target exposes a REST API and the operator wants the noise floor kept low, especially in a scanned-range workflow where WAF alerting matters. It is the wrong choice as the only check on a site whose plugins register no routes, and it is the wrong choice for an operator who cannot obtain the WPScan Enterprise token that carries the richest vulnerability feed. Before trusting a result, verify the database is current by running update-db, confirm the theme version detected from style.css matches the deployed release, and treat a clean scan of a stealthy-mode target as evidence of what was visible rather than evidence of what is installed.

## FAQ

### How do I install WPProbe on my own machine?

Several install routes are listed: `sudo apt install wpprobe` on Kali Linux, `go install github.com/Chocapikk/wpprobe@latest` with Go 1.22 or newer, `nix-shell -p wpprobe` on Nix, or a Docker image run with `docker run -it --rm wpprobe scan -u https://example.com`. Building from source is `git clone` followed by `go build -o wpprobe`.

### Do I need a Wordfence API key to run WPProbe?

No. By default `wpprobe update-db` fetches a pre-built database published from the repository and refreshed every two hours by CI, so no API key is required. Setting `WORDFENCE_API_KEY` or passing `--api-key` is optional and lets you fetch from Wordfence directly. WPScan data needs `WPSCAN_API_TOKEN` and an Enterprise plan.

### What is the difference between stealthy, bruteforce and hybrid scanning modes?

Stealthy mode matches exposed REST API routes against known plugin signatures and is the default, covering 5000 or more plugins. Bruteforce mode checks plugin directories directly with GET requests and covers 10k or more plugins but generates far more requests. Hybrid runs stealthy first and then brute-force for whatever is still missing, which is described as the maximum coverage option at medium stealth.

### Why does my WPProbe scan report no plugins?

Check the database first by running `wpprobe update-db`, since an empty or stale corpus produces empty results silently. Also note that stealthy mode only finds plugins that register a REST route, so a target with the REST API disabled needs `--mode bruteforce` or `--mode hybrid`. A clean result is not proof that the target is clean.

### Can I keep my Wordfence database and configuration inside Docker?

Yes. The usage examples show a named volume mounted at `/config` with `-e WORDFENCE_API_KEY` and `-e WPSCAN_API_TOKEN` set for a `wpprobe update-db` run, and the Dockerfile sets `XDG_CONFIG_HOME=/config` so the database persists between container invocations instead of being rebuilt each time.

## Sources

- [Chocapikk/wpprobe on GitHub](https://github.com/Chocapikk/wpprobe)
- [Issues](https://github.com/Chocapikk/wpprobe/issues)
- [License: MIT](https://github.com/Chocapikk/wpprobe/blob/main/LICENSE)
- [README](https://github.com/Chocapikk/wpprobe/blob/main/README.md)
- [Releases](https://github.com/Chocapikk/wpprobe/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/chocapikk-wpprobe
