Jan 1, 2025

LDAPNightmare: SafeBreach Labs Publishes First Proof-of-Concept Exploit for CVE-2024-49113

See how SafeBreach Labs Researchers developed a zero-click PoC exploit that crashes unpatched Windows Servers using the Windows Lightweight Directory Access Protocol (LDAP) Denial of Service Vulnerability.

Authors: Or Yair, Security Research Team Lead | Shahak Morag, Research Lead

Active Directory Domain Controllers (DCs) are considered to be one of the crown jewels in organizational computer networks. Vulnerabilities found in DCs are usually much more critical than those found in usual workstations. The ability to run code on a DC or crash Windows servers heavily affects network security posture;.

On December 10, 2024, two LDAP vulnerabilities were found by Yuki Chen (@guhe120): a remote code execution (RCE) and a Denial Of Service/Info Leak that both affect any DC was published on the Microsoft Security Response Center (MSRC) website as part of the latest Patch Tuesday update. The RCE vulnerability was assigned as CVE-2024-49112 and was given the CVSS severity score of 9.8 out of 10; the DOS vulnerability was assigned CVE-2024-49113. However, a public exploit or blogpost explaining the vulnerability or exploitation path was not published anywhere for either.

The SafeBreach Labs team regularly undertakes projects that we feel can help both our customers, who represent some of the largest brands in the world, and the security community at large.  Given the severity of this vulnerability’s consequences and the attention it has received from both since it was fixed, we decided as a team to prioritize it and are proud of the findings we have identified that will help enterprises address any potential exposures.

High-Level Summary

SafeBreach Labs developed a proof of concept exploit for CVE-2024-49113 that crashes any unpatched Windows Server (not just DCs) with no pre-requisites except that the DNS server of the victim DC has Internet connectivity.

The attack flow:

  1. The attacker sends a DCE/RPC request to the Victim Server Machine
  2. The Victim is triggered to send a DNS SRV query about SafeBreachLabs.pro
  3. The Attacker’s DNS server responds with the Attacker’s hostname machine and LDAP port 
  4. The Victim sends a broadcast NBNS request to find the IP address of the received hostname (of the Attacker’s)
  5. The Attacker sends an NBNS response with its IP Address
  6. The Victim becomes an LDAP client and sends a CLDAP request to the Attacker’s machine
  7. The Attacker sends a CLDAP referral response packet with a specific value resulting in LSASS to crash and force a reboot of the Victim server

We believe this same attack vector may be leveraged to achieve an RCE; the entire chain noted above, including the first six steps, should be similar, but the last CLDAP packet sent should be modified.  

We believe our findings are significant for a number of reasons. First, we have shown the criticality of this vulnerability by proving that it can be used to crash multiple unpatched Windows servers.  According to Microsoft’s classification, this vulnerability can be further exploited to lead to remote code execution. Second, we did verify that Microsoft’s patch fixes the out-of-bounds vulnerability and the exploit is not capable of crashing patched servers. Finally,  we provided a public PoC that organizations can use to test and verify that their servers are protected. For more details, please see the GitHub repository noted at the end of this blog.

The vulnerability that the SafeBreach Labs PoC exploits affects technology that is in widespread use across enterprise networks, and this flaw could help attackers propagate more easily and effectively. SafeBreach helps large enterprises identify and address potential exposures, including vulnerabilities like CVE-2024-49113, and SafeBreach customers will soon have access to new capabilities to test their internal networks against this and other vulnerabilities. Keep an eye on www.safebreach.com for news to come.

Technical Deep Dive

Below, we will explain the exact technical details of how the SafeBreach Labs research team identified the exploitation path that triggers the vulnerability and crashes a DC (or any Windows Server), provide a step-by-step exploitation summary, and share a proof-of-concept (PoC) tool that executes these steps.

CVE-2024-49113 

CVE-2024-49113 was titled as “Windows Lightweight Directory Access Protocol (LDAP) Denial of Service Vulnerability”. LDAP is the protocol that workstations and servers in Microsoft’s Active Directory use to access and maintain directory services information. The title of the vulnerability means that the vulnerability probably has something to do with LDAP-related code. On MSRC’s page for the CVE, Microsoft provided a few details, but on the RCE vulnerability there was additional interesting data:

“How could an attacker exploit this vulnerability?

A remote unauthenticated attacker who successfully exploited this vulnerability would gain the ability to execute arbitrary code within the context of the LDAP service. However successful exploitation is dependent upon what component is targeted.

In the context of exploiting a domain controller for an LDAP server, to be successful an attacker must send specially crafted RPC calls to the target to trigger a lookup of the attacker’s domain to be performed in order to be successful.

In the context of exploiting an LDAP client application, to be successful an attacker must convince or trick the victim into performing a domain controller lookup for the attacker’s domain or into connecting to a malicious LDAP server. However, unauthenticated RPC calls would not succeed.“

Based on this information—and assuming the accuracy of Microsoft’s documentation— we made the following assumptions:

  1. The attacker does not need to authenticate
  2. The vulnerability is an integer overflow type and is sourced in an executable or a Dynamic-Linked Library (DLL) that implements an LDAP client logic
  3. There are RPC calls that we can leverage in order to affect a DC to query an LDAP server controlled by an attacker
  4. In the context of a DC, the vulnerability probably lies in lsass.exe or in one of the DLLs that it loads, as lsass.exe implements the LDAP service on a DC
  5. Thus, the RPC interface with the RPC call that has the vulnerable LDAP client code is located in lsass.exe or in one of the DLLs that it loads as well

In addition, we also found an interesting insight by Artur Marzano (@MacmodSec) on X that suggested the potential location where Microsoft’s patch for the vulnerability was made, in wldap32.dll:

This insight fits perfectly with the documentation in MSRC’s website, as wldap32.dll implements the logic of an LDAP client.

Triggering a Remote LDAP Request

We started with proving the first step of exploitation against a DC—affecting it to query an LDAP server controlled by us. We needed to find an RPC call sourced in lsass.exe itself or in a DLL loaded into lsass.exe that imports functions from wldap32.dll. Using RpcView, we listed the available RPC interfaces loaded into lsass.exe:

Out of these RPC interfaces, we listed only the ones sourced in DLLs that are dependent on wldap32.dll and use its exported functions. We were looking for RPC interfaces that did not require authentication, as we assumed that the attacker does not need to authenticate. Two interesting interfaces we found that offered several interestingly named RPC calls that seemed related to LDAP queries and could possibly trigger one were located in lsasrv.dll and netlogon.dll:

Using IDA, we searched from the bottom up for RPC calls that actively use one of the functions imported from wldap32.dll. After a long search, we found DsrGetDcNameEx2. According to Microsoft’s documentation:

“The DsrGetDcNameEx2 method SHOULD return information about a domain controller (DC) in the specified domain and site. If the AccountName parameter is not NULL, and a DC matching the requested capabilities (as defined in the Flags parameter) responds during this method call, then that DC will have verified that the DC account database contains an account for the AccountName specified.

 NET_API_STATUS DsrGetDcNameEx2(

   [in, unique, string] LOGONSRV_HANDLE ComputerName,

   [in, unique, string] wchar_t* AccountName,

   [in] ULONG AllowableAccountControlBits,

   [in, unique, string] wchar_t* DomainName,

   [in, unique] GUID* DomainGuid,

   [in, unique, string] wchar_t* SiteName,

   [in] ULONG Flags,

   [out] PDOMAIN_CONTROLLER_INFOW* DomainControllerInfo

 );

This function looked pretty promising. It actively retrieves a hostname of a domain controller, in addition to verifying that a specific account exists in it. Both the domain name and the account are specified by the caller. That means that if the function uses LDAP in order to fulfil its purpose, we have what we need.

Moving forward, we needed to understand each one of DsrGetDcNameEx2 ‘s arguments and the values that we would set for them:

  • ComputerName: The hostname of the target DC – This would be set to the victim’s hostname (further research revealed that this value does not matter at all for the function)
  • AccountName: The account name that will be searched in the queried attacker’s domain —it can be any name—we don’t care if it exists or not
  • AllowableAccountControlBits: Controls what will be queried about “AccountName” – can be 0 – we don’t really care about the queried account
  • DomainName: The domain that will be queried – we set this to the domain name of the attacker
  • SiteName: The site in which the DC must be located – can be set to NULL
  • Flags – extra configuration for the call – we wanted the default behavior first, so we set it to 0
  • DomainControllerInfo – Output parameter, where the returned information will be placed

For testing purposes, we installed two new DCs in the same subnet, and created two new root domains in each one of them. One was called SBRESEARCH.LAB and the other TESTDOMAIN.LAB. The goal was to run on the DC at SBRESEARCH.LAB (the attacker), and get the DC at TESTDOMAIN.LAB (the victim) to query the LDAP server on the DC at SBRESEARCH.LAB.

So running on the attacker DC, we called the DsrGetDcNameEx2 function on the victim DC. The arguments for the call were:

  • ComputerName – WIN-ELD41******
  • AccountName – user1
  • AllowableAccountControlBits  – 0
  • DomainName – SBRESEARCH.LAB
  • SiteName – NULL
  • Flags – 0

Unfortunately, this was not enough. Looking in Wireshark at the packets that were sent and received by the victim, we did not see any LDAP request initiated by the victim. However, Wireshark did show us something else very interesting. We saw that the victim sent a DNS query to its DNS server about a subdomain of SBRESEARCH.LAB. The DNS query was replied with an error code specifying that the DNS server did not find any record about that domain. Then it made perfect sense why the call failed. The only way for the victim DC to get a successful answer about this query is if the attacker DC was its DNS server. But we can’t just change a DNS server of a DC; that alone is likely to be considered a vulnerability:

The specific DNS query that was sent was of type SRV. DNS SRV queries specify a domain name, to which another domain name and a port are mapped in the response. The specific full domain name of the two queries sent by the victim DC were:

  • _ldap._tcp.Default-First-Site-Name._sites.dc._msdcs.SBRESEARCH.LAB
  • _ldap._tcp.dc._msdcs.SBRESEARCH.LAB

Great! It looked like the victim DC really was looking for an LDAP server on our attacker domain. If we could just get this DNS query to be solved successfully, then the LDAP query by the victim would potentially happen. But if we couldn’t change the victim’s DNS server, what else could we do?

Do we have to control the victim’s DNS server in order to get the query to be solved successfully? The victim’s DNS server does not know SBRESEARCH.LAB, but it does know other domains. Not all the domain names that are known to the victim’s DNS server were manually configured on it. This DNS server knows “google.com” of course. What did Google do in order to be known by this DNS server? They bought a domain on the Internet, so this is exactly what we did as well.

We bought the domain “safebreachlabs.pro” to create two SRV records for:

  • _ldap._tcp.Default-First-Site-Name._sites.dc._msdcs.safebreachlabs.pro
  • _ldap._tcp.dc._msdcs.safebreachlabs.pro

These SRV records need to return a domain name (IP is not supported) and a port, which are likely to be contacted by the victim as the LDAP server. At first sight, it looks like this might mean that the victim will have to contact an LDAP server that has a public IP on the Internet, which is a requirement that we prefer not to have, as a firewall might block such communication. But, if we already have access to the DC’s subnet, we can maybe set the domain name that the SRV record returns to be the hostname of the computer that we control in the subnet. So, we mapped both SRV records to the hostname of the attacker DC, and port 389 (its LDAP server).

Following that, we ran another test. Running on the attacker DC, we again called the DsrGetDcNameEx2 function on the victim DC, but this time changed the DomainName parameter to be “safebreachlabs.pro” instead of “SBRESEARCH.LAB”, and it worked. The victim DC issued an LDAP query to our attacker DC.

As you can see in the image above, the query is sent in Connectionless LDAP (CLDAP) and uses UDP instead of TCP. Using Windbg, we were able to verify that even though this request is being made in CLDAP, it is still performed by wldap32.dll.

Sending a Malicious LDAP Response

Once we managed to get the victim DC to query our attacker LDAP server, we could move on to understand what needed to be in the response for that query. That is in order to get the victim to execute the assumed vulnerable function found by Artur Marzano – LdapChaseReferral.

Referrals allow an Active Directory tree to be partitioned between multiple LDAP servers.
When an LDAP server can’t answer a request, it can reply with referrals to other servers that may provide the answers for the query. Then, the client can “chase” these referrals and query the referred servers instead. It’s important to note that a client is not obligated to “chase” these referrals. However, in our case it does chase them.

In order for a server to indicate that it does not have the answer for the query and refer the client to different servers, it needs to reply with the “referral” LDAP result code (equals to 10). The response must also contain valid LDAP URLs (starts with “ldap://” or “ldaps://”).

Going back to our exploitation scenario, in order to trigger the LdapChaseReferral function, we created our own custom LDAP UDP server that allowed us to send such a referral response packet.

Looking at the logic of the patch, Microsoft added a condition that verifies that a certain value is not bigger than another value. Based on the logs printed next to this logic, these compared values are named as “lm_referral” and “referral table size”. “lm_referral” is taken from an “ldap_message” struct (probably our response message) and “referral table size” is taken from an “ldap_connection” struct. The condition checks whether the “lm_referral” value is inside the range of the “referral table”. This range is the “referral table size”.

In the vulnerable version without the patch, this “lm_referral” value is indeed used to access a certain offset inside the referral table:

In our tests with Windbg, we saw that the value in “lm_referral” is always equal to 0, while the pointer to the referral table is also equal to 0. However, the condition determining whether the code accesses the “referral table” only verifies whether the “lm_referral” value is not zero. That means that in order to trigger the vulnerability we must control the “lm_referral” variable and make it non-zero. If we succeed, then the code will dereference a pointer that we can control using lm_referral’s value.

Searching for where the “lm_referral” variable inside the “ldap_message” struct is populated, we looked for other occurrences in wldap32.dll where the offset of “lm_referral” inside the “ldap_message” struct is being used (+0x3C). This resulted in two functions: LdapInitialDecodeMessage and LdapChaseReferral:

Then we identified the code that sets “lm_referral” in LdapInitialDecodeMessage:

What we saw is that “msgid” and “lm_referral” are taken from the same part of the packet.
In the above screenshot, the “value_from_response_packet” must be a 4-byte DWORD in order to make “lm_referral” a non-zero WORD, due to the shift by 25.

In the default response packet that we sent using our custom UDP LDAP server, we can fully control the value of “value_from_response_packet” (seen in the above screenshot), and it is one byte long. What we learned is that this value is prefixed with its length.

Then we understood what we needed to do in order to set a non-zero value for “lm_referral”:

  • Change the byte that represents the length of “value_from_response_packet” (combination of “lm_referral” and “lm_msgid”) to 4 instead of 1.
  • Now “value_from_response_packet” is 4 bytes long, and we can set the most significant byte from it, which will affect “lm_referral”. Keep in mind that we can set this byte only to a value that can be divided equally by 2, or otherwise we affect the value of “lm_msgid”

These two actions will point the flow of the code into the scope of the vulnerable code and create an access violation once the dereferencing happens:

Since “ref_table” is equal to NULL, and “lm_referral” at this point is a non-zero value, the last line of code in the above image will trigger a dereference for a non-existent address resulting in out-of-bounds read and crasing LSASS and the entire operating system.

Next Steps: On the Way to Achieve RCE

Based on the initial research outlined here, we continued working towards the implementation of a full RCE chain (CVE-2024-49112). We haven’t achieved that yet, but we have made some good progress towards it. In this section, we will describe this progress.

In order not to crash, and continue the exploit, we are planning to find a way to trigger an integer overflow in the code that parses the CLDAP response, as MSRC documented that CVE-2024-49112 is triggered by an integer overflow.
We analyzed the diff between the patched and the unpatched version of the DLL and found the integer overflow fixes. The fixes are achieved by adding calls to the functions ULongMult, ULongAdd, and ULongSub. These functions perform the addition/multiplication/substitution and check whether an overflow happened. In addition, they store the result in an output parameter.

These functions are known to be Microsoft’s usual mitigation for previous integer overflow vulnerabilities, for example MS16-098. See our previous analysis of this patching method here.

Here are the xrefs to these three new added functions in the patched DLL:

We started going through the fixes, and at least for now, concluded that the ones we checked will probably not lead to the RCE. We started with LdapParseResult. On the left, we can see the unpatched version of the function LdapParseResult and on the right we can see the patched version, which has the added call to ULongMult before calling ldapMalloc.

We verified that our exploit code triggers this function if we send back a regular CLDAP response instead of the one crashing LSASS, but it was not enough. This function will get to this patched code only if the sixth parameter is not zero. This sixth parameter is a unicode string representing referrals. Based on our check, the RPC method that we call leads the parameter to be always zero. There were other xrefs to LdapParseResult, but we didn’t succeed to find a flow that calls it and sets the sixth parameter to a non-zero value using the RPC call that we use. It is possible that it can be triggered with a different RPC call than the one we use – DsrGetDcNameEx2. For now, we are going to try to use DsrGetDcNameEx2.

We decided to shift to a different attack flow. We analyzed the other calls to ULongMult and focused on the function SendLdapSearch.

Just below the call to ULongMult, we found an error print that verifies an integer overflow mitigation:

This is the unpatched code:

We were able to reach this function and even reached the call to ldapMalloc:

In the relevant code, we understood that the variable that is passed to strlenW is an LDAP filter sent as part of a search that is resulted from our RPC call. If filterLength is multiplied by 2, and filterLength is long enough, an overflow will happen and the allocation will be very small compared to the filter length. An important thing to note is that we control some elements in this filter such as the username and DnsDomain:

We tried sending a very long username, but it seems like the required length for the username value to trigger this overflow is too big to be passed to the DsrGetDcNameEx2 RPC call. 

We are still in the progress of investigating the other references to the three integer overflow mitigation functions, and we will provide additional updates here once we have new findings.

Exploit PoC

We have created a research repository that includes a PoC of the LDAP Nightmare exploit that  organizations can use to test and verify that their servers are protected against this vulnerability.

Affected Windows Servers

While our research focused on the testing of a Windows Server 2022 (DC) and Windows server 2019 (non DC), we believe this exploit path and PoC are applicable for any Windows Server version until the patch point.

Mitigation

To mitigate the risk of this vulnerability, organizations should implement the patch released by Microsoft detailed here. As noted above, SafeBreach Labs verified that the patch sufficiently prevents the exploitation and crashing of the tested servers. We believe patching this vulnerability is time critical, but also understand  that patching a DC and Windows Servers must be done carefully and with the proper caution.

As such, we suggest organizations implement detections to monitor suspicious CLDAP referral responses (with the specific malicious value set), suspicious DsrGetDcNameEx2 calls, and suspicious DNS SRV queries until a patch can be applied.

Conclusion  

This research set out to explore whether  the LDAP CVE-2024-49113 vulnerability could be exploited. Our research proved that not only can it be exploited  against Domain Controllers, it also affects any unpatched Windows Server.

In addition, we provided an exploit PoC for testing purposes, noted in the section above.
We also believe that this will make exploitation of CVE-2024-49112 more likely in the near future, so we recommend patching both vulnerabilities.

For more in-depth information about this research, please: 

  • Contact your customer success representative if you are a current SafeBreach customer
  • Schedule a one-on-one discussion with a SafeBreach expert
  • Contact Kesselring PR for media inquiries 

Credits

We would also like to give credit to the talented individuals below for their work: 

  • Yuki Chen (@guhe120) – CVE-2024-49112, CVE-2024-49113 
  • Artur Marzano (@MacmodSec)

About Our Researchers

Get the latest
research and news