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層進行繞過。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章