Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/scanner/ldap/ldap_login.rb
28052 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'metasploit/framework/credential_collection'
7
require 'metasploit/framework/login_scanner/ldap'
8
9
class MetasploitModule < Msf::Auxiliary
10
include Msf::Auxiliary::Report
11
include Msf::Auxiliary::AuthBrute
12
include Msf::Auxiliary::Scanner
13
include Msf::Exploit::Remote::LDAP
14
include Msf::Sessions::CreateSessionOptions
15
include Msf::Auxiliary::CommandShell
16
include Msf::Auxiliary::ReportSummary
17
18
def initialize(info = {})
19
super(
20
update_info(
21
info,
22
'Name' => 'LDAP Login Scanner',
23
'Description' => 'This module attempts to login to the LDAP service.',
24
'Author' => [ 'Dean Welch' ],
25
'License' => MSF_LICENSE,
26
'Notes' => {
27
'Stability' => [CRASH_SAFE],
28
'Reliability' => [],
29
'SideEffects' => []
30
}
31
)
32
)
33
34
register_options(
35
[
36
OptBool.new(
37
'APPEND_DOMAIN', [true, 'Appends `@<DOMAIN> to the username for authentication`', false],
38
conditions: ['LDAP::Auth', 'in', [Msf::Exploit::Remote::AuthOption::AUTO, Msf::Exploit::Remote::AuthOption::PLAINTEXT]]
39
),
40
Msf::OptString.new('LDAPDomain', [false, 'The domain to authenticate to'], fallbacks: ['DOMAIN']),
41
Msf::OptString.new('LDAPUsername', [false, 'The username to authenticate with'], fallbacks: ['USERNAME'], aliases: ['BIND_DN']),
42
Msf::OptString.new('LDAPPassword', [false, 'The password to authenticate with'], fallbacks: ['PASSWORD'], aliases: ['BIND_PW']),
43
OptInt.new('SessionKeepalive', [true, 'Time (in seconds) for sending protocol-level keepalive messages', 10 * 60])
44
]
45
)
46
47
# A password must be supplied unless doing anonymous login
48
# De-registering USERNAME and PASSWORD as they are pulled in via the Msf::Auxiliary::AuthBrute mixin
49
options_to_deregister = %w[USERNAME PASSWORD BLANK_PASSWORDS]
50
51
if framework.features.enabled?(Msf::FeatureManager::LDAP_SESSION_TYPE)
52
add_info('The %grnCreateSession%clr option within this module can open an interactive session')
53
else
54
# Don't give the option to create a session unless ldap sessions are enabled
55
options_to_deregister << 'CreateSession'
56
options_to_deregister << 'SessionKeepalive'
57
end
58
59
deregister_options(*options_to_deregister)
60
end
61
62
def create_session?
63
# The CreateSession option is de-registered if LDAP_SESSION_TYPE is not enabled
64
# but the option can still be set/saved so check to see if we should use it
65
if framework.features.enabled?(Msf::FeatureManager::LDAP_SESSION_TYPE)
66
datastore['CreateSession']
67
else
68
false
69
end
70
end
71
72
def run
73
validate_connect_options!
74
results = super || {}
75
logins = results.flat_map { |_k, v| v[:successful_logins] }
76
sessions = results.flat_map { |_k, v| v[:successful_sessions] }
77
print_status("Bruteforce completed, #{logins.size} #{logins.size == 1 ? 'credential was' : 'credentials were'} successful.")
78
return results unless framework.features.enabled?(Msf::FeatureManager::LDAP_SESSION_TYPE)
79
80
if create_session?
81
print_status("#{sessions.size} LDAP #{sessions.size == 1 ? 'session was' : 'sessions were'} opened successfully.")
82
else
83
print_status('You can open an LDAP session with these credentials and %grnCreateSession%clr set to true')
84
end
85
results
86
end
87
88
def validate_connect_options!
89
# Verify we can create arbitrary connect opts, this won't make a connection out to the real host - but will verify the values are valid
90
get_connect_opts
91
rescue Msf::ValidationError => e
92
fail_with(Msf::Exploit::Remote::Failure::BadConfig, "Invalid datastore options for chosen auth type: #{e.message}")
93
end
94
95
def run_host(ip)
96
cred_collection = build_specific_credential_collection(
97
void_login: datastore['LDAP::Auth'] == Msf::Exploit::Remote::AuthOption::SCHANNEL,
98
no_password_login: datastore['LDAP::Auth'] == Msf::Exploit::Remote::AuthOption::KERBEROS && !datastore['ANONYMOUS_LOGIN'] && !datastore['LDAPPassword']
99
)
100
101
pkcs12_storage = Msf::Exploit::Remote::Pkcs12::Storage.new(framework: framework, framework_module: self)
102
opts = {
103
domain: datastore['LDAPDomain'],
104
append_domain: datastore['APPEND_DOMAIN'],
105
ssl: datastore['SSL'],
106
proxies: datastore['PROXIES'],
107
domain_controller_rhost: datastore['DomainControllerRhost'],
108
ldap_auth: datastore['LDAP::Auth'],
109
ldap_pkcs12: datastore['LDAP::CertFile'] ? pkcs12_storage.read_pkcs12_cert_path(datastore['LDAP::CertFile']) : nil,
110
ldap_rhostname: datastore['Ldap::Rhostname'],
111
ldap_krb_offered_enc_types: datastore['Ldap::KrbOfferedEncryptionTypes'],
112
ldap_krb5_cname: datastore['Ldap::Krb5Ccname']
113
}
114
115
realm_key = nil
116
if opts[:ldap_auth] == Msf::Exploit::Remote::AuthOption::KERBEROS
117
realm_key = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
118
if !datastore['ANONYMOUS_LOGIN'] && !datastore['LDAPPassword']
119
# In case no password has been provided, we assume the user wants to use Kerberos tickets stored in cache
120
# Write mode is still enable in case new TGS tickets are retrieved.
121
opts[:kerberos_ticket_storage] = kerberos_ticket_storage({ read: true, write: true })
122
else
123
# Write only cache so we keep all gathered tickets but don't reuse them for auth while running the module
124
opts[:kerberos_ticket_storage] = kerberos_ticket_storage({ read: false, write: true })
125
end
126
end
127
128
scanner = Metasploit::Framework::LoginScanner::LDAP.new(
129
configure_login_scanner(
130
host: ip,
131
port: rport,
132
cred_details: cred_collection,
133
stop_on_success: datastore['STOP_ON_SUCCESS'],
134
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
135
connection_timeout: datastore['LDAP::ConnectTimeout'].to_i,
136
framework: framework,
137
framework_module: self,
138
realm_key: realm_key,
139
opts: opts,
140
use_client_as_proof: create_session?
141
)
142
)
143
144
successful_logins = []
145
successful_sessions = []
146
scanner.scan! do |result|
147
credential_data = result.to_h
148
credential_data.merge!(
149
module_fullname: fullname,
150
workspace_id: myworkspace_id,
151
service_name: 'ldap',
152
protocol: 'tcp'
153
)
154
if result.success?
155
successful_logins << result
156
if opts[:ldap_auth] == Msf::Exploit::Remote::AuthOption::SCHANNEL
157
# Schannel auth has no meaningful credential information to store in the DB
158
msg = opts[:ldap_pkcs12].nil? ? 'Using stored certificate' : "Cert File #{opts[:ldap_pkcs12][:path]} (#{opts[:ldap_pkcs12][:value].certificate.subject})"
159
report_successful_login(
160
public: opts[:ldap_pkcs12][:value].certificate.subject.to_s,
161
private: opts[:ldap_pkcs12][:path]
162
)
163
print_brute level: :good, ip: ip, msg: "Success: '#{msg}'"
164
else
165
create_credential_and_login(credential_data) if result.credential.private
166
print_brute level: :good, ip: ip, msg: "Success: '#{result.credential}'"
167
end
168
successful_sessions << create_session(result, ip) if create_session?
169
else
170
invalidate_login(credential_data)
171
vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
172
end
173
end
174
{ successful_logins: successful_logins, successful_sessions: successful_sessions }
175
end
176
177
private
178
179
def create_session(result, ip)
180
session_setup(result)
181
rescue StandardError => e
182
elog('Failed to setup the session', error: e)
183
print_brute level: :error, ip: ip, msg: "Failed to setup the session - #{e.class} #{e.message}"
184
result.connection.close unless result.connection.nil?
185
end
186
187
# @param [Metasploit::Framework::LoginScanner::Result] result
188
# @return [Msf::Sessions::LDAP]
189
def session_setup(result)
190
return unless result.connection && result.proof
191
192
# Create a new session
193
my_session = Msf::Sessions::LDAP.new(result.connection, { client: result.proof, keepalive_seconds: datastore['SessionKeepalive'] })
194
195
merge_me = {
196
'USERPASS_FILE' => nil,
197
'USER_FILE' => nil,
198
'PASS_FILE' => nil,
199
'USERNAME' => result.credential.public,
200
'PASSWORD' => result.credential.private
201
}
202
203
start_session(self, nil, merge_me, false, my_session.rstream, my_session)
204
end
205
206
def build_specific_credential_collection(void_login:, no_password_login:)
207
if void_login
208
Metasploit::Framework::PrivateCredentialCollection.new({
209
nil_passwords: true
210
})
211
elsif no_password_login
212
Metasploit::Framework::CredentialCollection.new({
213
username: datastore['LDAPUsername'],
214
nil_passwords: true
215
})
216
else
217
build_credential_collection(
218
username: datastore['LDAPUsername'],
219
password: datastore['LDAPPassword'],
220
realm: datastore['DOMAIN'],
221
anonymous_login: datastore['ANONYMOUS_LOGIN'],
222
blank_passwords: false
223
)
224
end
225
end
226
end
227
228