This post describes how to fix the PrimeFaces Expression Language Remote Code Execution bug (CVE-2017-1000486) when an update to the latest / fixed PrimeFaces version is not easily possible. This solution also needs no patching of the PrimeFaces library itself.
The preferred / advised solution for fixing the issue is of course to do the update. The bug was already fixed over a year ago. However, only recently (beginning of 2018), more details and public exploits for this vulnerability have been published.
See: https://www.primefaces.org/primefaces-el-injection-update/
In one of our projects we build a set of own components with custom design based on Primefaces. An update to the latest version would have taken a couple of weeks to implement. Therefore we needed an easy quick solution to fix this vulnerability. We also could not use the solution described in the article above (disabling dynamic content), because we rely on that functionality.
The examples in this article are based on PrimeFaces 4, but can also be applied to later versions.
Problem:
PrimeFaces uses links like below to load dynamic resources.
1 |
/javax.faces.resource/dynamiccontent.properties.jsf?ln=primefaces&pfdrid=dY1NMdRjZ4sqkGAPrRzKkmxm5b1DSrlJ7VXTVU2i7gmrhtVAkmH7aR8cUk7h%2BTgl&forcereload=1523006610695&pfdrid_c=true |
The problem here is the GET parameter pfdrid
1 |
pfdrid=dY1NMdRjZ4sqkGAPrRzKkmxm5b1DSrlJ7VXTVU2i7gmrhtVAkmH7aR8cUk7h%2BTgl |
This is an encrypted Expression Language value. When PrimeFaces receives this, it will be decrypted and executed. There is no check what value expression is in there and unfortunately the encryption password is hardcoded into PrimeFaces.
So if you construct a value expression shellcode and encrypt it with the hardcoded password and pass it via this parameter it will be executed on the server.
How to fix this
Step 1: Change the hardcoded password
The password for the encryption / decryption can be set via a configuration property in the web.xml.
1 2 3 4 |
<context-param> <param-name>primefaces.SECRET</param-name> <param-value>YourPassword</param-value> </context-param> |
Since the attacker does not know this new password, he cannot correctly encrypt arbitrary expression language values, so that they will be executed on the server.
Remarks: The fix described here only works for closed source projects. Everyone who knows the new password, still is able to execute arbitrary code. Luckily for us, this was no problem.
Step 2: Fixing the Padding Oracle
Even when the attacker does not know the encryption password, there still is a problem. PrimeFaces is vulnerable to a so called padding oracle attack.
The short version…
Many cryptographic algorithms use fixed size blocks for encryption. If the text is not of that exact size the last block is “filled” with additional bytes (padded).
If you change specific bytes in the vulnerable parameter, in my example, sometimes the result will be an empty page, but other times result in the following exception and therefore in an Internal Server Error page.
javax.crypto.BadPaddingException: Given final block not properly padded
These 2 different responses are enough to decrypt the parameter and even re-encrypt new expression language values by doing a couple of thousand requests to the server.
For more information see: https://en.wikipedia.org/wiki/Padding_oracle_attack
Padbuster
To demo this i will use a tool called padbuster. This tool can decrypt and re-encrypt parmeters like described above without knowing the password.
1 |
padbuster "http://192.168.56.1:8080/javax.faces.resource/dynamiccontent.properties.jsf?ln=primefaces&pfdrid=dY1NMdRjZ4sqkGAPrRzKkmxm5b1DSrlJ7VXTVU2i7gmrhtVAkmH7aR8cUk7h%2BTgl&forcereload=1523005752379&pfdrid_c=true" "dY1NMdRjZ4sqkGAPrRzKkmxm5b1DSrlJ7VXTVU2i7gmrhtVAkmH7aR8cUk7h%2BTgl" 8 |
First parameter is the url, second parameter the value to attack, third the block size.
Output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
+-------------------------------------------+ | PadBuster - v0.3.3 | | Brian Holyfield - Gotham Digital Science | | labs@gdssecurity.com | +-------------------------------------------+ INFO: The original request returned the following [+] Status: 500 [+] Location: N/A [+] Content Length: 80 INFO: Starting PadBuster Decrypt Mode *** Starting Block 1 of 5 *** INFO: No error string was provided...starting response analysis *** Response Analysis Complete *** The following response signatures were returned: ------------------------------------------------------- ID# Freq Status Length Location ------------------------------------------------------- 1 1 500 80 N/A 2 ** 255 200 0 N/A ------------------------------------------------------- Enter an ID that matches the error condition NOTE: The ID# marked with ** is recommended : |
The important thing here is that padbuster detected, that there are 2 different responses from the server by changing the encrypted parameter (Status 500 internal server error, status code 200 empty response).
If you continue this:
1 2 3 4 5 6 7 8 9 10 |
[+] Success: (17/256) [Byte 8] [+] Success: (217/256) [Byte 7] [+] Success: (251/256) [Byte 6] [+] Success: (73/256) [Byte 5] [+] Success: (171/256) [Byte 4] [+] Success: (218/256) [Byte 3] [+] Success: (61/256) [Byte 2] ... [+] Decrypted value (ASCII): ...imageBean.pictureIcon} ... |
It will not be able to decrypt the first block since PrimeFaces also uses an initialization vector for the encryption, but this can be broken as well. I won’t go into how to do this here.
The same way new expression language values can be encrypted using this tool without knowing the encryption password.
So in order to fix this, we must make sure, the server response is the same when nothing can be decrypted or if the padding is incorrect.
Implementing a Custom ResourceHandlerWrapper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class CustomResourceHandler extends ResourceHandlerWrapper { private final ResourceHandler wrapped; @SuppressWarnings("javadoc") public CustomResourceHandler(ResourceHandler wrapped) { this.wrapped = wrapped; } @Override public ResourceHandler getWrapped() { return this.wrapped; } @Override public void handleResourceRequest(FacesContext context) throws IOException { try { super.handleResourceRequest(context); } catch (IOException e) { logger.... } } } |
Adding the new ResourceHandlerWrapper to faces-config.xml
1 |
<resource-handler>com.illucit.handler.CustomResourceHandler</resource-handler> |
Unfortunately we cannot catch the BadPaddingException in the handler, because it is internally caught in PrimeFaces. Therefore we unfortunately have to catch IOException. You can adjust this to your needs if you need more fine-grained control over error handling here.
So after putting this new resource handler in, let’s take a look at the output of Padbuster again:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
+-------------------------------------------+ | PadBuster - v0.3.3 | | Brian Holyfield - Gotham Digital Science | | labs@gdssecurity.com | +-------------------------------------------+ INFO: The original request returned the following [+] Status: 200 [+] Location: N/A [+] Content Length: 0 INFO: Starting PadBuster Decrypt Mode *** Starting Block 1 of 5 *** INFO: No error string was provided...starting response analysis *** Response Analysis Complete *** The following response signatures were returned: ------------------------------------------------------- ID# Freq Status Length Location ------------------------------------------------------- 1 256 200 0 N/A ------------------------------------------------------- ERROR: All of the responses were identical. |
As you can see, all response are the same now and padbuster cannot attack the padding oracle vulnerability any more 🙂
So since we changed the password and we fixed the padding oracle, this vulnerability is not exploitable any more.
Hope this article helps to understand this vulnerability and maybe saves someone from actually having to update to a newer PrimeFaces version (but only if this is no option for you).
If you have any questions or comments please leave a message below.