Spring Security Oauth RCE(CVE-2018-1260) 漏洞复现与分析
Spring Security OAuthe 使用标准的 Spring 和 Spring 安全编程模型和配置习语支持使用 OAuth1 ( 1A )和 OAuth2 使用 Spring 安全性
Features
* Support for OAuth providers and OAuth consumers
* Oauth 1(a) (including two-legged OAuth, a.k.a. "Signed Fetch")
* OAuth 2.0.
Spring Security OAuth, versions 2.3 prior to 2.3.3 and 2.2 prior to 2.2.2 and 2.1 prior to 2.1.2 and 2.0 prior to 2.0.15 and older unsupported versions, contains a remote code execution vulnerability. A malicious user or attacker can craft an authorization request to the authorization endpoint that can lead to a remote code execution when the resource owner is forwarded to the approval endpoint.
This vulnerability exposes applications that meet all of the following requirements:
· Act in the role of an Authorization Server (e.g. @EnableAuthorizationServer)
· Use the default Approval Endpoint
This vulnerability does not expose applications that:
· Act in the role of an Authorization Server but override the default Approval Endpoint
· Act in the role of a Resource Server only (e.g. @EnableResourceServer)
· Act in the role of a Client only (e.g. @EnableOAuthClient)
** 漏洞环境用的是 spring-security-oauth-2.3.2.RELEASE**
** 使用其中的 samples/oauth2/sparklr 项目 **
首先需要自己重写一次 SparklrUserApprovalHandler 里的 getUserApprovalRequest ,如下图
( 原因在后面再说 )
Samples/oauth2/sparklr/src/main/java/org/springframework/security/oauth/examples/sparklr/mvc/AccessConfirmationController 需要改一改
注释掉这个注解
在开始分析之前可以思考下, spring 的子模块出现了 rce ,按理说应该不会是 exec 之类的显示调用,那么有两种可能:
1. 反序列化
2. spring 的 spel 表达式注入
直接跟踪关键字: * approval endpoint *
找到 1 个文件如下:
这个 endpoint 与外界开放的只有 getAccessConfirmation 函数,如下:
注意图中, template 变量是由 request 和 model 得来,先不管其中流程如何,一定程度说明了 template 是我们间接可控的,继续跟踪发现传入了 SpelView 的构造函数,这个类名就和 spel 表达式有关系,那么由此猜测应该就是 spel 造成的 rce 了。
跟进 SpelView 构造函数:
如上图红框,表达式注入的触发函数已经有了,看看 parser 是啥
Parser 也满足触发条件,那么看看表达式是否可控,就是上图中的 name 变量,我们间接可控的是 template 变量,看似两者毫无干系,并且 name 还是在一个类中被重写的 resolvePlaceholder 函数里使用的 .... 不方,我们先看看这个 resolver 的使用情况,如下
我们间接可控的 template 和 resolver 同时被带入了 replacePlaceholders 函数,跟进去
如上图,又带入了 parseStringValue 中,跟进去
如图中, template 传入的就是 strVal , resolver 传入的就是 placeholderResolver ,从图中看出大致流程就是可控的 strVal 变量最终进入了被重写的 resolvePlaceholder 函数。
但是现在有一个问题就是,这个后续触发 resolvePlaceholder 是在 SpelView 类中的 render 函数里,并且我没有反向跟踪到有什么地方调用了 render 函数,那么怎么样才会触发最终的代码执行呢
我们注意到这是一个类似 View 视图处理的类,那么在构造函数完成功能之后,肯定会将其处理的结果进行输出,也就是说 render 一定会被框架调用,那就不管了
那么现在就是从可控参数到触发点都满足 spel 表达式注入的条件了
看着是挺简单的吧,所以为啥会造成表达式注入的呢,我们回到 WhitelableApprovalEndpoit 类中
可控变量 template 由 createTemplate 函数调用得到,跟进去看看
如上图, model 我们暂时不知道是否可控,所以紧盯 request 变量,注意到在此函数中, request 仅仅是提取了一些属性值,这个我们肯定不可控,跟进 createScopes 看看
如上图,大致流程就是,从 model 或者 request 中获取 scopes ,然后再将其与 SCOPE 字符串中特定的字符做替换处理,最后返回替换的结果,但是我们已知可控的 request 在这个函数里还是仅仅是做属性值提取,所以逆向思维一下, model 可控,从 model 中获取了 scopes
可以看一下 SCOPE 的值
这明明就是 html ,大致能猜测到先给出特定的模板字符串,然后根据参数修改一定内容,所以呢,我们知道模板内容在最终输出前, spring 里是会对其进行一次 spel 解析处理的,那么就造成了 rce
构造 payload 只需要满足漏洞触发所需条件就行了,不难 ..... 才怪,需要很多条件 ..
主要是需要业务逻辑层重写 UserApprovalHandler 类的 getUserApprovalRequest 函数,将 request 中的某些字段,手动加入到 model 映射中的 scopes 字段中,如下图
但是这还不够,我们之前分析的路径是: /oauth/confirm_access
而从这个路径进去, model 变量是为 null 的,所以也按照漏洞详情里说的那样,先去访问 /oauth/authorize ,得到我们可控的 model 后会自动转到 /oauth/confirm_access
那么这个流程是如何的跑起来的,进 AuthorizationEndpoint 看看
(简单的处理流程就不说了,直接看 model 的生成和跳转)
如上图,这个过程就是生成 model + 设置跳转的
先看 model 生成
其 userApprovalHandler 是可以自定义的,跟进 getUserApprovalRequest
上图就是我自定义的 getUserApprovalRequest 处理流程,将 requestParam 中的 test 字段放入了 model 映射中的 scopes 里
看看跳转:
正好跳到了 /oauth/confirm_access 里,所以我们间接控制的 model 就带入了 getAccessConfirmation 函数里,后面模板解析造成了 rce
但是这里的跳转,通过调试发现, model 并不是我们之前已经构造好的那个 model 了 .... 所以需要手动跳一次 ....
如下图,我又改代码了 ......AuthorizationEndpoint 里的 getUserApprovalPageResponse
最后的 payload :
http://localhost:8080/oauth/authorize?client_id=tonr-with-redirect&redirect_uri=http://localhost:8080/oauth/confirm_access&state=orich1_test&response_type=token&scope=read&test=%24%7b(new+java.lang.ProcessBuilder(%22calc.exe%22)).start()%7d
访问的时候,如果没有登录的话,会跳到登录框,登录一下就 OK 了
和 0c0c0f 师傅讨论的过程中也发现, model 映射中的 scopes 如果不是业务逻辑层手动将 requestParam 塞进去的话,根本不可控,虽说按照开发逻辑 scopes 是会适当的获取一些 requestParam 的,但是获取的是什么字段也无从得知。在 examples/oauth2/sparklr 项目中,确实将 requestParam 中的 scope 字段提取出来了,但是跟踪发现这个 scope 字段早已验证,如果不是服务器配置中相关字符串,就抛出异常,所以也是不可控的
即使在业务逻辑中将 requestParam 某些字段放入了 model 对应的 scopes 中,但是如果不手动转过去的话,根本无法触发,只能说是 SpelView 存在潜在威胁。
相关参考: https://pivotal.io/security/cve-2018-1260