CVE-2021-21972 VMware vCenter未授权文件上传漏洞复现

一、信息梳理

根据网络漏洞信息:

vSphere 是 VMware 推出的虚拟化平台套件,包含 ESXi、vCenter Server 等一系列的软件。其中 vCenter Server 为 ESXi 的控制中心,可从单一控制点统一管理数据中心的所有 vSphere 主机和虚拟机,使得 IT 管理员能够提高控制能力,简化入场任务,并降低 IT 环境的管理复杂性与成本。

vSphere Client(HTML5)在 vCenter Server 插件vRealize Operations(默认安装)中存在一个远程执行代码漏洞。未授权的攻击者可以通过开放 443 端口的服务器向 vCenter Server 发送精心构造的请求,从而在服务器上写入 webshell,最终造成远程任意代码执行。

vCenter Server 的 vROPS 插件的 API 未经过鉴权,存在一些敏感借口。其中 uploadova 接口存在一个上传 OVA 文件的功能,将 TAR 文件解压后上传到 /tmp/unicorn_ova_dir 目录

版本:

  • vmware:vcenter_server 7.0 U1c 之前的 7.0 版本
  • vmware:vcenter_server 6.7 U3l 之前的 6.7 版本
  • vmware:vcenter_server 6.5 U3n 之前的 6.5 版本

可将重要信息提取:

存在漏洞的地方vSphere Client(HTML5) 的插件vRealize Operations(默认安装)存在RCE:接口 uploadova 接口存在一个上传 OVA 文件,上传tar文件会将文件放在/tmp/unicorn_ova_dir目录,导致可以上传shell。

具体漏洞接口位置:

1
https://<VC-IP-or-FQDN>/ui/vropspluginui/rest/services/updateova

二、本地复现

本地部署

从运维大佬那里要来的ova文件:链接:https://pan.baidu.com/s/1-jK1iY46-hiza9ni2TdDkA 提取码:ns83

导入ova,最小配置要求如下图2 vCPUs、10GB内存、300GB硬盘

image-20210301195607605

本地部署具体可以参考https://blog.51cto.com/wangchunhai/2439987

image-20210301201517412

设置单点登录密码

image-20210301200329371

设置root密码

image-20210301200316042

省略中间页面配置过程…

Web页面设置相关信息后保存,最后一步比较花费时间,多等待一下。

image-20210301234302258

上传脚本getshell

脚本:https://github.com/NS-Sp4ce/CVE-2021-21972

image-20210302094623215

image-20210302094748281

三、分析

根据github已公开脚本 可以发现其利用步骤如下:

  1. 准备好jsp文件
  2. 添加文件到相应路径的tar包,Linux和Windows路径不同。
  3. 将添加好的tar包上传到漏洞API:/ui/vropspluginui/rest/services/updateova
  4. 访问上传后解压的路径对应的URL

思考问题:

  1. 通过代码分析漏洞原因
  2. Windows相对来说简单,可以直接上传到相应路径(..\..\ProgramData\VMware\vCenterServer\data\perfcharts\tc-instance\webapps\statsreport\)然后访问shell。但是Linux不同,他的利用方法目前网络公开的是通过免密登录或者shell上传,上传位置/usr/lib/vmware-vsphere-ui/server/work/deployer/s/global/{REPLACE_RANDOM_ID_HERE}/0/h5ngc.war/resources/,这个路径上传不一定能成功利用,除了这个位置,需要思考有没有其它可以利用路径,该路径需要满足可执行、未授权可访问、不存在随机性。

代码分析

目前根据公开信息,CVE-2021-21972 VMware vCenter未授权文件上传漏洞该漏洞主要有两方面原因导致getshell,一是未授权可上传,一是路径穿越,我们一一分析下。

首先未授权很容易找到原因,vRops插件默认安装,并且默认配置了未认证,所以导致未授权可以访问上传路径,如下图可以看出:

image-20210302141731972

另外就是路径穿越。上传OVA的接口如下,我们分析下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@RequestMapping(
value = {"/uploadova"},
method = {RequestMethod.POST}
)
public void uploadOvaFile(@RequestParam(value = "uploadFile",required = true) CommonsMultipartFile uploadFile, HttpServletResponse response) throws Exception {
logger.info("Entering uploadOvaFile api");
// 判断是否正确上传
int code = uploadFile.isEmpty() ? 400 : 200;
PrintWriter wr = null;
try {
if (code != 200) {
response.sendError(code, "Arguments Missing");
return;
}
wr = response.getWriter();
} catch (IOException var14) {
var14.printStackTrace();
logger.info("upload Ova Controller Ended With Error");
}
response.setStatus(code);
String returnStatus = "SUCCESS";
if (!uploadFile.isEmpty()) {
try {
logger.info("Downloading OVA file has been started");
logger.info("Size of the file received : " + uploadFile.getSize());
InputStream inputStream = uploadFile.getInputStream();
File dir = new File("/tmp/unicorn_ova_dir");
// 创建"/tmp/unicorn_ova_dir"
if (!dir.exists()) {
dir.mkdirs();
} else {
String[] entries = dir.list();
String[] var9 = entries;
int var10 = entries.length;
for(int var11 = 0; var11 < var10; ++var11) {
String entry = var9[var11];
File currentFile = new File(dir.getPath(), entry);
currentFile.delete();
}
logger.info("Successfully cleaned : /tmp/unicorn_ova_dir");
}
// tar文件解压 解包输入流
TarArchiveInputStream in = new TarArchiveInputStream(inputStream);
// 获取归档文件中的条目
TarArchiveEntry entry = in.getNextTarEntry();
ArrayList result = new ArrayList();
while(entry != null) {
if (entry.isDirectory()) {
entry = in.getNextTarEntry();
} else {
// File(String parent, String child),根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。parent 路径名字符串用于表示目录,child 路径名字符串用于表示目录或文件。简而言之父路径字符串和子路径字符串拼接创建一个文件
File curfile = new File("/tmp/unicorn_ova_dir", entry.getName());
File parent = curfile.getParentFile();
if (!parent.exists()) {
// 创建目录
parent.mkdirs();
}
OutputStream out = new FileOutputStream(curfile);
IOUtils.copy(in, out);
out.close();
result.add(entry.getName());
entry = in.getNextTarEntry();
}
}
in.close();
logger.info("Successfully deployed File at Location :/tmp/unicorn_ova_dir");
} catch (Exception var15) {
logger.error("Unable to upload OVA file :" + var15);
returnStatus = "FAILED";
}
}
wr.write(returnStatus);
wr.flush();
wr.close();

这段代码主要逻辑:

  1. POST上传文件,并且判断是否上传成功,上传成功会返回SUCCESS
  2. 判断/tmp/unicorn_ova_di是否存在,不存在则创建
  3. 以tar文件形式解压上传的文件输入流
  4. /tmp/unicorn_ova_dir与解压的文件(包含路径)进行拼接,创建目录文件

由于使用File(String parent, String child)将两个目录直接拼接了,并且没有过滤任何的”../“,所以导致可以穿越目录从而上传文件到任意目录下。

四、拓展

如何实现文件上传(通过post报文、python和curl)?

http:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST/upload.html HTTP/1.1 

Accept: text/plain, */*
Accept-Language: zh-cn
Host: 192.168.24.56
Content-Type:multipart/form-data;boundary=-----------------------------7db372eb000e2
User-Agent: WinHttpClient
Content-Length: 3693
Connection: Keep-Alive

-------------------------------7db372eb000e2
Content-Disposition: form-data; name="file"; filename="chrome.jpg"
Content-Type: image/jpeg

(此处省略jpeg文件二进制数据...)
-------------------------------7db372eb000e2--

根据 rfc1867, multipart/form-data是必须的。---------------------------7db372eb000e2是分隔符,分隔多个文件、表单项。其中b372eb000e2是即时生成的一个数字,用以确保整个分隔符不会在文件或表单项的内容中出现。Form每个部分用分隔符分割,分隔符之前必须加上--这两个字符(即–{boundary})才能被http协议认为是Form的分隔符,表示结束的话用在正确的分隔符后面添加”–”表示结束---------------------------7d是 IE 特有的标志,Mozila 为---------------------------71------webkitformboundary 是safari 浏览器从客户端向服务器端传送HTML标签数据所使用的分隔符(一般会在分隔符后附加一串十六进制的数以示区别)

python:

1
2
# Requests也支持以multipart形式发送post请求,只需将一文件传给requests.post()的files参数即可。"content-type"为 "multipart/form-data",请求正文是binary
r = requests.post(url, files={'file' : open('chrome.jpg', 'rb')})

CURL:

1
2
3
4
5
6
# 上传单个文件,[headers 身份认证、不认证https(-k)、查看详细报文(-v)]
curl -F 'data=@path/to/local/file' http://localhost/upload [-H "User-Agent: WinHttpClient" -H "token: XXX"] [-k] [-v]
# 上传多个文件,添加-F即可
curl -F 'file1=@/path/to/file1' -F 'file2=@/path/to/file2' ... http://localhost/upload
# 上传文件树组
curl -F 'files[]=@/path/to/file1' -F 'files[]=@/path/to/file2' ... http://localhost/upload

参考链接:

1
2
3
4
5
6
https://swarm.ptsecurity.com/unauth-rce-vmware/
https://mp.weixin.qq.com/s/g2eFMSpZereNA73Hve8cQg
https://www.cnblogs.com/insane-Mr-Li/p/9145152.html
https://www.cnblogs.com/frustrate2/archive/2012/11/07/2759080.html
https://www.gonever.com/post/45
https://www.cnblogs.com/xiaohulumanong/p/8338248.html