0x01 背景
vSphere
是VMware
推出的虚拟化平台套件,包含ESXi
、vCenter Server
等一系列的软件。其中vCenter Server
为 ESXi
的控制中心,可从单一控制点统一管理数据中心的所有vSphere
主机和虚拟机,使得IT
管理员能够提高控制能力,简化入场任务,并降低IT
环境的管理复杂性与成本。
vSphere Client(HTML5)
在vCenter Server
插件中存在一个远程执行代码漏洞。未授权的攻击者可以通过开放443
端口的服务器向vCenter Server
发送精心构造的请求,从而在服务器上写入webshell
,最终造成远程任意代码执行。
0x02 代码分析
vCenter Server
的vROPS
插件的API
未经过鉴权,存在一些敏感接口。其中 uploadova
接口存在一个上传 OVA 文件的功能:
@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"); 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();
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(); }
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(); }
|
代码逻辑是将TAR
文件解压后上传到 /tmp/unicorn_ova_dir
目录。注意到如下代码:
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
拼接并写入文件。如果文件名内存在 ../
即可实现目录遍历。
0x03 影响版本
vcenter_server
7.0 U1c 之前的 7.0 版本
vcenter_server
6.7 U3l 之前的 6.7 版本
vcenter_server
6.5 U3n 之前的 6.5 版本
0x04 复现
以VMware-VCSA-all-6.7.0-8217866
为例,web
的资源目录为/usr/lib/vmware-vsphere-ui/server/work/deployer/s/global/{REPLACE_RANDOM_ID_HERE}/0/h5ngc.war/resources/
向https://VCIP/ui/vropspluginui/rest/services/uploadova
发送POST请求,tar
包内容为带有目录穿越的shell
,
上传成功会返回SUCCESS
访问上传的shell
0x05 VMware的骚操作
在vSphere vCenter
6.7 U2的某个分支里,VM官方搞了个骚操作导致6.7U2+无法通过上传webshell
的形式去获取vCenter
权限,以下为启动前端UI的命令
/usr/java/jre-vmware/bin/vsphere-ui.launcher -Xmx597m -XX:CompressedClassSpaceSize=256m -Xss320k -XX:ParallelGCThreads=1 -Djava.io.tmpdir=/usr/lib/vmware-vsphere-ui/server/work/tmp -Dorg.eclipse.virgo.kernel.home=/usr/lib/vmware-vsphere-ui/server -DPS_BASEDIR=/storage/vsphere-ui/ -Declipse.ignoreApp=true -Dcatalina.base=/usr/lib/vmware-vsphere-ui/server -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/vmware/vsphere-ui/ -XX:ErrorFile=/var/log/vmware/vsphere-ui/java_error%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintReferenceGC -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=1024K -XX:-OmitStackTraceInFastThrow -Xloggc:/var/log/vmware/vsphere-ui/vsphere-ui-gc.log -Djava.security.properties=/etc/vmware/java/vmware-override-java.security -Djava.ext.dirs=/usr/java/jre-vmware/lib/ext:/usr/java/packages/lib/ext:/opt/vmware/jre_ext/ -Dorg.osgi.framework.system.packages.extra=sun.misc -Dsun.zip.disableMemoryMapping=true -Dui.component.name=vsphere-ui -Dvlsi.client.vecs.certstore=false -DisFling=false -Dorg.apache.tomcat.websocket.DISABLE_BUILTIN_EXTENSIONS=true -Dlogback.configurationFile=/usr/lib/vmware-vsphere-ui/server/conf/serviceability.xml -Dlogs.dir=/var/log/vmware/vsphere-ui/logs/ -Dhttps.port=5443 -Dhttp.port=5090 -Dshutdown.port=-1 -Dcom.sun.org.apache.xml.internal.security.ignoreLineBreaks=true -Dorg.apache.xml.security.ignoreLineBreaks=true -classpath /usr/lib/vmware-vsphere-ui/server/bootstrap/server-launcher.jar:/usr/lib/vmware-vsphere-ui/server/bin/bootstrap.jar:/usr/lib/vmware-vsphere-ui/server/bin/tomcat-juli.jar com.vmware.vise.launcher.tomcat.TomcatLauncher start
|
注意server-launcher.jar
这个包调用了com.vmware.vise.launcher.tomcat.TomcatLauncher
类,反编译后部分代码如下
public final class TomcatLauncher { private static final String VECS_KEYSTORE_TYPE = "VKS"; private static final String MACHINE_SSL_CERT = "MACHINE_SSL_CERT"; private static final String VECS_LOAD_STORE_PARAM_TYPE = "com.vmware.provider.VecsLoadStoreParameter"; private static final String FILE_SEPARATOR; private static final String SYSPROP_COMPONENT_NAME = "ui.component.name"; private static final String DEFAULT_COMPONENT_NAME = "vsphere-ui"; private static final String CLIENT_APP_DATA_FOLDER; private static final String DEST_KEYSTORE_NAME = "keystore.jks"; private static final String PASSWORD; private static final String TOMCAT_DIR = "catalina.base"; private static final String CONFIG_DIR; public static void main(final String[] args) { loadKeystore(); deleteWorkDirectory(); createJavaTempDir(); Bootstrap.main(args); }
|
其中有个函数deleteWorkDirectory
private static void deleteWorkDirectory() { final File workDir = new File(System.getProperty("catalina.base") + "/work"); if (!workDir.exists()) { return; } try { deleteDirectoryRecur(workDir); } catch (IOException e) { throw new RuntimeException("Cannot clean work dir", e); } }
|
这个函数主要作用就是删除web
目录,先从命令行中的catalina.base
获取设置的Tomcat
工作目录/usr/lib/vmware-vsphere-ui/server
,然后拼接/work
路径并判断是否存在,不存在则返回,存在的调用deleteDirectoryRecur
函数进行删除
private static void deleteDirectoryRecur(final File directory) throws IOException { final File[] contents = directory.listFiles(); if (contents != null) { for (final File f : contents) { if (isSymlink(f)) { f.delete(); } else { deleteDirectoryRecur(f); } } } directory.delete(); }
private static boolean isSymlink(final File file) throws IOException { if (file == null) { throw new NullPointerException("File must not be null"); } File canon; if (file.getParent() == null) { canon = file; } else { final File canonDir = file.getParentFile().getCanonicalFile(); canon = new File(canonDir, file.getName()); } return !canon.getCanonicalFile().equals(canon.getAbsoluteFile()); }
|
这也是为什么6.7U2+、7.0+的版本无法进行常规写shell的操作原因。
但是问题来了,实际发现tomcat
并没有启动
0x06 柳暗花明
经过分析启动进程rhttpproxy
,发现该进程将443请求转发到5090端口
这和启动进程的参数吻合
经过后续的分析,发现了一种有条件写shell
的操作,vCenter
在重启时会启动服务,但是在启动服务的过程中会调用war
包重新生成web
目录,这就可以给我们一个可乘之机
查看war
包权限
770
权限,那么我们可以先把该文件下载回本地进行加料,再利用CVE-2021-21792
漏洞回传回vCener
服务器进行复写操作,等待vCenter
重启时就会在内存中驻留我们的webshell
成功如图
0x07 EXP
https://github.com/NS-Sp4ce/CVE-2021-21972