
POC CVE-2020-2551 à utiliser sur Internet
python CVE-2020-2551.py [HOST] [IP]
python CVE-2020-TEST.py [HOST] [IP]
(Publié pour la première fois sur Anquanke, lien original)
Pour étudier cette vulnérabilité, des connaissances préalables sont nécessaires, par exemple CORBA et RMI.
Résumé simple :
CORBA est un ensemble de normes techniques établies par l'OMG pour les applications distribuées. Il utilise IDL pour la prise en charge multilingue, et la communication entre le client et le serveur se fait via le protocole IIOP.
RMI est une autre technologie d'application distribuée. En Java, JNDI peut être utilisé pour simplifier l'application. Le client et le serveur communiquent via le protocole JRMP, mais dans weblogic, RMI utilise le protocole T3, pour lequel de nombreuses ont déjà été publiées.
RMI-IIOP combine les avantages respectifs de RMI et CORBA, en déployant des applications RMI via le protocole IIOP.
La documentation officielle mentionne également :
Les objets serveur RMI peuvent utiliser le protocole IIOP et communiquer avec des objets clients CORBA écrits dans n'importe quel langage.
Sans parler de weblogic pour le moment, concentrons-nous d'abord sur la façon d'écrire un exemple RMI-IIOP :
Le code client peut se référer au projet de test dans Java : RMI, JNDI, LDAP, JRMP, JMX, JMS (Partie 1). Vous pouvez compiler HelloClient et HelloServer vous-même, ou utiliser les versions déjà compilées du projet de test.
Démarrez le serveur de noms en ligne de commande (intégré à Java) :
start orbd -ORBInitialPort 1050
Démarrez le serveur HelloServer en ligne de commande et configurez le débogage distant. Pour savoir comment utiliser IDEA pour le débogage distant, référez-vous à la méthode mentionnée au début de cet article.
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 HelloServer
Bien sûr, vous pouvez aussi simplement exécuter sans débogage distant pour voir le résultat directement :
java HelloServer
Démarrez le client en ligne de commande :
Java HelloClient
À ce moment, la calculatrice s'ouvre. Si vous réussissez le débogage distant, vous verrez la pile d'appels suivante :

Exécution de la commande dans EvilMessage.readObejct() :

En aparté : articles sur l'installation et le débogage de weblogic
Qu'en est-il de RMI-IIOP dans weblogic ? L'article À propos de RMI-IIOP en Java mentionne l'exploitation de RMI-IIOP de weblogic. Sur cette base, nous avons fait quelques recherches. Using WebLogic’s RMI over IIOP décrit plusieurs façons pour weblogic d'utiliser le client RMI-IIOP, notamment :
La différence entre les deux premières méthodes semble être uniquement la configuration de JNDI_FACTORY :

Lors de nos études précédentes sur la désérialisation T3 de weblogic, nous avions déployé l'application Helloserver sur weblogic, avec une méthode sayhello() exploitable. Nous avons essayé de définir deux JNDI_FACTORY et d'appeler la méthode ; avec la deuxième JNDI_FACTORY, nous avons réussi à appeler la méthode sayHello().
Modifions ensuite le POC du protocole T3 de weblogic POC. En fait, il suffit de remplacer RMI par IIOP, et on constate que la chaîne d'exploitation jtaTransactionManager s'exécute avec succès et envoie une requête jrmp au jrmplisten local :

Regardons le trafic : lors de l'appel à la méthode remove(), une requête remove__java_lang_Object est envoyée, contenant des données malveillantes dans le trafic, mais on ne trouve pas le header magique aced :

On suppose qu'une analyse spéciale est effectuée côté serveur avant de désérialiser les données. Regardons la pile d'appels : la partie arrière de la chaîne d'exécution ressemble beaucoup à celle du RMI-IIOP natif précédent. La différence est que le déclenchement se fait ici à partir de IIOPInputStream.read_value() dans weblogic, alors que précédemment c'était à partir de CDRInputStream.read_value(). (Le point read_value a également été mentionné dans le sujet de 2019.)

Ici, la requête est d'abord traitée par clusterableServerRef.invoke(), qui appelle this.invoker.invoke() selon l'invocateur. Ensuite, Mejb_dj5nps_HomeImpl_WLSkel.invoke() est appelé. Comme il s'agit de "remove", on entre dans la branche case 6 et on appelle IIOPInputStream.readObject(). La méthode read_value() analyse les données IIOPInputStream et déclenche la désérialisation. Voici le POC utilisant la méthode remove().
L'[article d'analyse](https://lucifaer.com/2020/02/25/WebLogic WLS核心组件RCE分析(CVE-2020-2551)/?from=timeline&isappinstalled=0#2-2-Weblogic解析流程) du maître Lucifaer mentionne l'utilisation de la méthode bind() pour l'exploitation. C'est également la méthode d'exploitation courante sur Internet. Suivons la pile d'appels :

Comme précédemment, la requête est d'abord traitée par clusterableServerRef.invoke(), qui appelle this.invoker.invoke() selon l'invocateur. Ici, CobraServerRef.invoke() est appelé, puis dans _NamingContextAnyImplBase._invoke(), comme va1 est "bind_any", on entre dans la branche case 0 et on appelle IIOPInputStream.read_any(). Ensuite, cela finit par appeler IIOPInputStream.read_value() et déclenche la désérialisation. On avait dit précédemment qu'on ne voyait pas le header magique aced dans le trafic, c'est parce qu'il y a un ensemble de méthodes d'analyse dans IIOPInputStream. La forme hexadécimale de IIOPInputStream est la suivante, contenant le nom de classe et les informations de champ :

Finalement, la méthode readObejct() de la classe malveillante est appelée :

En regardant le correctif, on constate qu'il se trouve au même endroit que le correctif de l'exploitation de désérialisation T3 de 2015 :

Dans les tests de l'analyse de la vulnérabilité WebLogic CVE-2020-2551, on voit que l'emplacement du filtrage de CVE-2020-2551 est également dans la classe weblogic.iiop.Utils :

Mais lors des tests locaux avec weblogic 10.3.6 après avoir appliqué le correctif de 2015, la fonction isBlacklisted() n'a pas été déclenchée (pourtant, dans MsgAbbrevInputStream et InboundMsgAbbrev, isBlacklisted() est appelée pour la validation de la liste noire, étrange...).

Le correctif de CVE-2020-2551 ajoute la méthode verifyclassermitted() de filtrage dans weblogic.iiop.Utils.LoadClass() :

La liste noire filtre les classes malveillantes, y compris la classe parent de JtaTransactionManager : com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager. Cette classe est intégrée à weblogic et est très dangereuse. En regardant le correctif, une idée m'est venue : puisque la validation à la ligne 606 se fait après LoadClass(), si lors du chargement du nom de classe, la classe est chargée et exécute un bloc de code statique malveillant, on pourrait contourner la défense. Nous en reparlerons plus tard.
Le POC écrit en Java a des problèmes réseau : il fonctionne en attaquant le service weblogic local, mais pas contre un conteneur Docker ou une machine externe. Articles d'analyse à ce sujet :
Voici le débogage du POC. On peut se référer à celui de remove() précédent, ou à celui de Y4er. Les deux articles précédents mentionnent deux solutions :
J'ai essayé les deux. Après avoir reconstruit le package weblogic, une erreurjava.lang.NoSuchMethodError:weblogic.security.subject.SubjectManager.installCESubjectManager est apparue, mais je n'ai pas trouvé de solution.

J'ai donc essayé de simuler le protocole IIOP. Placez d'abord un point d'arrêt dans le POC :

On constate que lors du new InitialContext(env), EndPointImpl.sendReceive() envoie et reçoit deux paquets :

LocateReply contient des informations IOR. Il faut comprendre ce qu'est IOR. Son rôle est, lorsque le client RMI-IIOP interagit avec l'objet serveur via le protocole IIOP, de fournir le host et le port nécessaires à la communication IIOP, ainsi que la partie en rouge Object_key, qui sert à distinguer différents objets côté serveur :

Lors de la simulation du protocole IIOP, il faut se concentrer sur Object_key. Le host et l'IP n'ont pas vraiment d'importance. Au début des tests, j'ai simplement rejoué tous les paquets, et lors de l'envoi de resolve_any, j'ai reçu un location forward :

La documentation officielle de GIOP explique que location forward indique que Object_key peut changer ; les Object_key renvoyés lors de différentes requêtes peuvent être différents (ici, Object_key est l'adresse key dans le paquet de données). Comme mentionné précédemment, ce Object_key est utilisé dans le protocole IIOP pour distinguer avec quel objet communiquer. Cette valeur doit être obtenue dynamiquement dans LocateReply :

Finalement, je n'ai pas choisi de simuler remove(), mais plutôt la requête IIOP émise par la méthode bind(), car il y a moins de requêtes. Regardons le paquet de données d'exploitation locale normale :

Envoyez LocateRequest, recevez les données, et obtenez l'adresse key dans LocateReply par correspondance de motif :

Définissez manuellement l'adresse du serveur jrmp malveillant (rmi://...), et envoyez le paquet bind_any toutes les secondes. Comme la chaîne d'exploitation ici n'utilise pas DGCClient pour envoyer la requête jrmp, elle n'est pas affectée par JEP290, et peut être exploitée via jrmplisten :

Le POC a fonctionné dans l'environnement Docker. Vous pouvez utiliser l'environnement vulhub SSRF, définir l'IP sur l'hôte, et Docker obtient avec succès la requête jrmp de l'hôte. Le code spécifique se trouve sur Github :

Comme mentionné précédemment, l'idée d'utiliser le codebase pour charger du code distant et contourner la détection. Les personnes qui ont étudié l'attaque JNDI savent que le codebase peut être utilisé pour spécifier l'emplacement d'une classe distante. Si le codebase est contrôlable et que le programme autorise le chargement de classes distantes, on peut charger une classe distante malveillante et exécuter du code malveillant dans le bloc statique.
En lisant le code, on voit que le second paramètre de weblogic.iiop.Utils.lodaClass() représente le codebase. Ce paramètre est lu dans IIOPInputStream.read_value() comme paramètre var8. La ligne 1659 appelle readIndirectingRepositoryId(var8), qui finit par appeler weblogic.iiop.Utils.lodaClass(). Pour exécuter les lignes 1644 et 1659, il faut que (va4r & 1) = 1 et (va4 & 6) = 2, donc var4 doit valoir 3 :

La pile d'appels de readIndirectingRepositoryId à getClassFromId, qui finit par exécuter loadclass() à la ligne 304 :

Regardons le paquet bind_any. Il est en fait composé d'un en-tête GIOP Header et d'une requête GIOP Request. La requête GIOP Request contient l'adresse key (identique à celle de LocateReply), ServiceContextList et stub_data. La valeur de var4 est \x7f\xff\xff\x02 dans stub_data, donc (va4r & 1) = 0, (va4 & 6) = 2, et le code à la ligne 1644 pour définir le codebase ne sera pas exécuté :

Nous modifions le premier champ encadré \x7f\xff\xff\x02 en \x00\x00\x00\x03, et dans le deuxième champ encadré, nous ajoutons la longueur et la valeur du codebase. Le code spécifique se trouve sur Github. On voit qu'une opération d'alignement est également effectuée. C'est un point délicat, car avant de lire les informations de classe suivantes, on vérifie si la position du prochain octet est un multiple de 4. Si ce n'est pas le cas, certains octets sont ignorés. Par exemple, si la position du prochain octet est 1, trois octets sont ignorés, et on lit à partir de l'octet à la position 4. Cette position est relative à l'ensemble du paquet bind_any. Si l'octet n'est pas un multiple de 4, on ajoute des zéros :

Un autre problème : regardez la pile d'appels précédente de readIndirectingRepositoryId à getClassFromId. Elle passe par la fonction findClassInfo(). Ici, si une classe a déjà été chargée, les informations de l'ID de classe sont sauvegardées, et findClassInfo() les retourne directement sans entrer dans la fonction weblogic.iiop.Utils.getClassFromID() :

Donc lors des tests, il faut changer le nom de classe à chaque fois :

Quoi qu'il en soit, finalement, nous avons réussi à simuler le protocole IIOP pour modifier la valeur du codebase et exécuter la fonction weblogic.iiop.Utils.getClassFromId().
Malheureusement, lors de l'obtention de RMIURLClassFinder, la valeur retournée est NULL, et la fonction RMIEnvironment.getEnvironment().isNetworkClassLoadingEnabled() renvoie false :

La raison est que le paramètre _NetworkClassLoadingEnable dans ServerMBeanImpl a la valeur False :

J'aurais aimé voir dans quel fichier de configuration de weblogic ce paramètre est défini, mais je ne l'ai pas trouvé...
Au cours de l'étude de cette vulnérabilité, j'ai constaté qu'il fallait beaucoup de connaissances préalables, comme la désérialisation Java, RMI, JNDI, etc. Pour les apprentissages associés, on peut se référer à cette colonne d'articles. Bien que la tentative d'exploitation par modification du codebase ait finalement échoué, j'en ai retiré beaucoup de choses.