VMware Carbon Black App Control漏洞分析

VMware Carbon Black App Control漏洞分析

碎碎念

“像是发起了一场冲锋,确没有找到敌人,无疾而终”

漏洞分析

关于VMware Carbon Black App Control详细在这不赘述,详细可到【经典回顾系列】CVE-2021-21988 VMware Carbon Black App Control认证绕过漏洞分析文章中查看。

漏洞介绍

查看了一下漏洞大概,CVE-2021-21998这个漏洞是因为路由转发的时候,不支持URL编码,使用URL编码未授权访问接口获取认证Token。

漏洞分析

环境分析

下载cwp.ova,导入镜像,进行导入即可

image-20220920221046524

image-20220920221155236

image-20220920221216897

image-20220920221615530

后端是java启动的,分别在3030,3010,3020端口

80和443端口是envoy启动的,查看静态配置文件

image-20220920221346769

Envoy的几个clusters映射

相关配置文件

service_vsw -> 127.0.0.1:3030
service_apw -> 127.0.0.1:3020
service_acs -> 127.0.0.1:3010
service_plugin -> 127.0.0.1:8080

相关配置内容

 clusters:
    -
      connect_timeout: 5s
      hosts:
        -
          socket_address:
            address: "127.0.0.1"
            port_value: 3030
      lb_policy: round_robin
      name: service_vsw
      type: LOGICAL_DNS
    -
      connect_timeout: 5s
      hosts:
        -
          socket_address:
            address: "127.0.0.1"
            port_value: 3020
      lb_policy: round_robin
      name: service_apw
      type: LOGICAL_DNS
    -
      connect_timeout: 5s
      hosts:
        -
          socket_address:
            address: "127.0.0.1"
            port_value: 3010
      lb_policy: round_robin
      name: service_acs
      type: LOGICAL_DNS
    -
      connect_timeout: 5s
      hosts:
        -
          socket_address:
            address: "127.0.0.1"
            port_value: 8080
      lb_policy: round_robin
      name: service_plugin
      type: LOGICAL_DNS
    -
      connect_timeout: 5s
      hosts:
        -
          socket_address:
            address: "127.0.0.1"
            port_value: 3020
      name: configServer
      type: STATIC
  listeners:
    -
 /usr/java/jre-vmware/bin/java -server -XX:+UseG1GC -Xms128M -Xmx256M -XX:+UseStringDeduplication -XX:ErrorFile=/var/log/cwp/access-control-service-java-error%%p.log -Xlog:gc:file=/var/log/cwp/access-control-service-gc.log::filesize=200M,filecount=2 -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cwp/heapdump/access-control-service/access-control-service.hprof -jar /opt/vmware/cwp/access-control-service/lib/access-control-service-1.0.1-SNAPSHOT.jar --logging.file=/var/log/cwp/access-control-service.log


/usr/java/jre-vmware/bin/java -server -XX:+UseG1GC -Xms128M -Xmx512M -XX:+UseStringDeduplication -XX:ErrorFile=/var/log/cwp/vsphere-worker-java-error%%p.log -Xlog:gc:file=/var/log/cwp/vsphere-worker-gc.log::filesize=200M,filecount=2 -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cwp/heapdump/vsphere-worker/vsphere-worker.hprof -Dservice-token=eyJhbGciOiJSUzUxMiJ9.eyJzdWIiOiJ2c3ciLCJpc3MiOiJ1c2VyLXNlcnZpY2UiLCJuYmYiOjE2NjM3MDYyODAsImV4cCI6MTc1MDEwNjI4MCwicG9saWN5Ijp7InJvbGUiOiJTRVJWSUNFX1VTRVIiLCJwZXJtaXNzaW9ucyI6eyIqIjpbIioiXX19LCJyZWZyZXNoYWJsZSI6ZmFsc2UsImlhdCI6MTY2MzcwNjI4MH0.jGj3j4qNS_6pZUCHTxbpu-QYPr_ejveRsoQ7tOe8Gr_i1DNg6EbR1KALvlc8HxcV70vo5Sa4uyJykq0vzWQcnT29X-6Oev6lhwAwuAf3Vb2srg_RV8rtd7t9gbWVKzh0ZFC8_lexvgS9RRPZ0D8obrrnj74O24C6Cedabj-ynYW5AB4yDyJSHOrjr8uK2NcyL-B6IZ3KSfWeMa1DLDl4p9LEWwfoG1kZfVjJVVx-hUMsUcOwgTjcWFhoGiHVOu7BHc3QDKD4GTVStALskM-1o4EOUaeOdKTBBzlw_7MXp9fagw2_kpFOZPp9YlM5wyNO5o4ujsDIMUJhWjI0p5oQ-Q -jar /opt/vmware/cwp/vsphere-worker/lib/vsphere-worker-1.0.1-SNAPSHOT.jar --logging.file=/var/log/cwp/vsphere-worker.log

/usr/java/jre-vmware/bin/java -server -XX:+UseG1GC -Xms128M -Xmx512M -XX:+UseStringDeduplication -XX:ErrorFile=/var/log/cwp/appliance-worker-java-error%%p.log -Xlog:gc:file=/var/log/cwp/appliance-worker-gc.log::filesize=200M,filecount=2 -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cwp/heapdump/appliance-worker/appliance-worker.hprof -Dservice-token=eyJhbGciOiJSUzUxMiJ9.eyJzdWIiOiJhcHciLCJpc3MiOiJ1c2VyLXNlcnZpY2UiLCJuYmYiOjE2NjM3MDYyODAsImV4cCI6MTc1MDEwNjI4MCwicG9saWN5Ijp7InJvbGUiOiJTRVJWSUNFX1VTRVIiLCJwZXJtaXNzaW9ucyI6eyIqIjpbIioiXX19LCJyZWZyZXNoYWJsZSI6ZmFsc2UsImlhdCI6MTY2MzcwNjI4MH0.kvJ2Gn9-FsNQ-nARtdUnkOydpmGZptrn1cH_NPIokHkxDmBbs61LoHyMdkv6JzMm9gqqns0fOf3iXe_toENIuNrwmhwDcp1TZF-He9quXfu_IQ_YdRGv6uX_nfgU33Ndy9L_pGFJPYiLSoaRrgdw_b04BMc-4OQeei2hB9vvlZpNHYWLZoiWOA7Vgb3c5btb965_FFECgBmPGeqRLovUeJVqisL6Z4-ceXfEhXC5pVhH9cy7UdYPWn6DRQkCvX0yIKXJb3lD-kBmS5Jp9QW7c2L9AQPYHzRCwvYU2hG5OcabmhXs_PgbpNlbNPHcEjjO5DRDgSxqdQRB6PWTr3cpsw -jar /opt/vmware/cwp/appliance-worker/lib/appliance-worker-1.0.1-SNAPSHOT.jar --logging.file=/var/log/cwp/appliance-worker.log

代码中com.vmware.cwp.appliance.applianceworker.service.impl.EnvoyXDSServiceImpl#routeDiscovery类来进行路由的映射转发

 public String routeDiscovery(final DiscoveryRequest discoveryRequest) {
      CloudSettingsDTO cloudSettings = this.cloudSettingsService.getSettings("FULL_DETAILS");
      if (Objects.equals(this.cloudSettings, cloudSettings)) {
         this.applianceService.registerAppliance();
      }

      this.cloudSettings = cloudSettings;
      String hostName = this.getHostName(cloudSettings);
      String applianceIPv4Address = this.networkService.getNetworkConf().getIpv4Address();
      Iterable headers = this.populateHeaders();
      Route rds_block = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/apw/v2/discovery:routes").build()).setRoute(RouteAction.newBuilder().setCluster("service_vsw").setPrefixRewrite("/no_cloud").build()).build();
      Route service_token_block = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/acs/api/v1/service-token").build()).setRoute(RouteAction.newBuilder().setCluster("service_vsw").setPrefixRewrite("/no_cloud").build()).build();
      Route cds_block = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/apw/v2/discovery:clusters").build()).setRoute(RouteAction.newBuilder().setCluster("service_vsw").setPrefixRewrite("/no_cloud").build()).build();
      Route apw = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/apw/").build()).setRoute(RouteAction.newBuilder().setCluster("service_apw").setPrefixRewrite("/").setHostRewrite(applianceIPv4Address).build()).build();
      Route no_cloud_connectivity = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/vsw/plugin/index.html").build()).setRoute(RouteAction.newBuilder().setCluster("service_apw").setPrefixRewrite("/no_cloud").setHostRewrite(applianceIPv4Address).build()).build();
      Route plugin_api = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/vsw/plugin/api/").build()).setRoute(RouteAction.newBuilder().setCluster("api_cloud_cluster").setHostRewrite(hostName).setPrefixRewrite("/").build()).addAllRequestHeadersToAdd(headers).build();
      Route vc_plugin_ui = Route.newBuilder().build();
      if (this.hasSupportingPluginURL()) {
         vc_plugin_ui = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/vsw/plugin/").build()).setRoute(RouteAction.newBuilder().setCluster("plugin_cluster").setHostRewrite(this.getPluginHostURL(cloudSettings).getHost()).setPrefixRewrite(this.getPluginHostURL(cloudSettings).getUrlReWrite()).build()).build();
      }

      Route apw_vc_plugin = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/vsw/plugin/apw/").build()).setRoute(RouteAction.newBuilder().setCluster("service_apw").setPrefixRewrite("/").setHostRewrite(applianceIPv4Address).build()).build();
      Route vsw_vc_plugin = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/vsw/plugin/vsw/api/").build()).setRoute(RouteAction.newBuilder().setCluster("service_vsw").setPrefixRewrite("/vsw/api/").setHostRewrite(applianceIPv4Address).build()).build();
      Route inventory = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/inventory/").build()).setRoute(RouteAction.newBuilder().setCluster("api_cloud_cluster").setHostRewrite(hostName).build()).addAllRequestHeadersToAdd(headers).build();
      Route lcm = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/lcm/").build()).setRoute(RouteAction.newBuilder().setCluster("api_cloud_cluster").setHostRewrite(hostName).build()).addAllRequestHeadersToAdd(headers).build();
      Route acs = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/acs/").build()).setRoute(RouteAction.newBuilder().setCluster("service_acs").setHostRewrite(applianceIPv4Address).build()).build();
      Route vsw = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/vsw/").build()).setRoute(RouteAction.newBuilder().setCluster("service_vsw").setTimeout(Duration.newBuilder().setSeconds(60L)).setHostRewrite(applianceIPv4Address).build()).build();
      Route appliance = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/applianceservice/").build()).setRoute(RouteAction.newBuilder().setCluster("api_cloud_cluster").setHostRewrite(hostName).build()).addAllRequestHeadersToAdd(headers).build();
      Route default_route = Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/").build()).setRedirect(RedirectAction.newBuilder().setPathRedirect("/apw/login").build()).build();
      Builder virtualHostOrBuilder = VirtualHost.newBuilder().setName("backend").addDomains("*");
      virtualHostOrBuilder.addRoutes(cds_block);
      virtualHostOrBuilder.addRoutes(rds_block);
      virtualHostOrBuilder.addRoutes(service_token_block);
      if (this.hasCloudURL(cloudSettings)) {
         virtualHostOrBuilder.addRoutes(appliance);
      }

      if (this.hasSupportingPluginURL()) {
         virtualHostOrBuilder.addRoutes(plugin_api);
         virtualHostOrBuilder.addRoutes(apw_vc_plugin);
         virtualHostOrBuilder.addRoutes(vsw_vc_plugin);
         virtualHostOrBuilder.addRoutes(vc_plugin_ui);
         virtualHostOrBuilder.addRoutes(lcm);
         virtualHostOrBuilder.addRoutes(inventory);
      } else {
         virtualHostOrBuilder.addRoutes(no_cloud_connectivity);
      }

      virtualHostOrBuilder.addRoutes(acs);
      virtualHostOrBuilder.addRoutes(apw);
      virtualHostOrBuilder.addRoutes(vsw);
      virtualHostOrBuilder.addRoutes(default_route);
      VirtualHost virtualHost = virtualHostOrBuilder.build();
      RouteConfiguration routeConfiguration = RouteConfiguration.newBuilder().setName("route").addVirtualHosts(virtualHost).build();
      DiscoveryResponse discoveryResponse = DiscoveryResponse.newBuilder().setVersionInfo("1").addResources(Any.pack(routeConfiguration)).build();
      TypeRegistry typeRegistry = TypeRegistry.newBuilder().add(DiscoveryResponse.getDescriptor()).add(ClusterLoadAssignment.getDescriptor()).add(RouteConfiguration.getDescriptor()).build();
      String response = null;

      try {
         response = JsonFormat.printer().usingTypeRegistry(typeRegistry).print(discoveryResponse);
      } catch (InvalidProtocolBufferException var28) {
         log.error("Error while serializing response", var28);
      }

      return response;
   }

image-20220921005700544

以以上图举例,如果匹配acs前缀则走到service_acs这个clusters中。这个clusters在前面配置文件可见

hosts:
        -
          socket_address:
            address: "127.0.0.1"
            port_value: 3010
      lb_policy: round_robin
      name: service_acs
      type: LOGICAL_DNS

转发到3010端口处理,以此类推。

漏洞分析

再来看到漏洞,漏洞位置在com.vmware.cwp.appliance.acs.api.controller.TokenGeneratorApi#getServiceToken

image-20220921010233401

image-20220921010343915

 public ResponseEntity getServiceToken(final String serviceName) {
      AccessTokenDTO response = this.tokenGeneratorService.getServiceToken(serviceName);
      return new ResponseEntity(response, HttpStatus.OK);
   }
   public AccessTokenDTO getServiceToken(final String serviceName) {
      if (StringUtils.isBlank(serviceName)) {
         return (new AccessTokenDTO()).token("XXXXXX.XXXXXXXXXX.XXXXX");
      } else {
         JwtRbacPolicy jwtRbacPolicy = this.getJwtRbacPolicy("SERVICE_USER");
         Date expiryDate = new Date(System.currentTimeMillis() + this.serviceProperties.getServiceTokenDuration().toMillis());
         return (new AccessTokenDTO()).token(this.tokenGenerator.generate(jwtRbacPolicy, serviceName, expiryDate, false));
      }
   }

调用JwtTokenGenerator生成token进行返回。

构造一下请求,该路由对应的是access-control-service模块,需要加入acs前缀

https://192.168.31.84/acs/api/v1/service-token/admin

image-20220921010815056

请求会发现404

回到com.vmware.cwp.appliance.applianceworker.service.impl.EnvoyXDSServiceImpl#routeDiscovery

image-20220921010937672

以上可以看到/acs/api/v1/service-token被他转发到service_vsw中去了,即便转发到3030端口,对应vsphere-worker模块

image-20220921011209649

该模块并没有该路由,本地请求测试

image-20220921011339316

发现直接请求3010端口是可以未授权获取token的。

image-20220921011524915

回看到这个地方,发现/acs/api/v1/service-token/,优先级高于/acs

漏洞是使用了URL编码,在来到envoy的时候,并不会自动URL解码。所以不走/acs/api/v1/service-token/,路由规则,走到下面的acs规则,使端口转发到3010,而3010端口的springboot是会解析url编码的,从而走入/api/v1/service-token/{serviceName}这条路由规则中。

Envoy框架默认是不启动URL解析,需要手工开启。使用RBAC过滤器时启用normalize_path_settings特性。

image-20220921011844252

image-20220921012236075

Reference

官网文档

CVE-2021-21988 VMware Carbon Black App Control认证绕过漏洞分析

结尾

调完该漏洞,挺巧妙,在envoy层进行绕过。

posted @ 2022-09-21 01:32  nice_0e3  阅读(94)  评论(0编辑  收藏  举报