• 本文作者: 
  • |
  • 2021年12月17日
  • |
  • 未分类
  • |

SPEL表达式注入漏洞深入分析

1.SPEL简介

SPEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言。从Spring 3开始引入了Spring表达式语言,它能够以一种强大而简洁的方式将值装配到Bean属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算得到值。使用SPEL你可以实现超乎想象的装配效果,这是其他装配技术很难做到的。

2.SPEL使用

SPEL的使用可以分为两种方式,第一种是在注解中进行使用,另一种是通过SPEL组件提供的接口来进行解析。

在注解中使用的情况

 

通过接口的使用情况

这段代码是执行一段简单的SPEL表达式“3*3”,最终执行结果如下所示

11

SPEL表达式还能执行一些更复杂的命令,例如对一个对象进行操作,代码如下所示,首先是一个pojo类

然后是通过SPEL表达式操作user对象的属性

可以操作对象的属性SPEL同样也可以操作对象的方法,例如我们的pojo类User中就有一个成员方法sayHi,和一个静态方法sayBye,我们使用SPEL表达式来分别调用一下

首先是调用成员方法,也就是动态方法

运行结果如下

12

然后是调用静态方法,代码如下所示

除了以上两种方可以调用静态方法以外还有一种方法

 

3.CVE-2016-4977 漏洞分析

根据网上爆出得漏洞相关信息,POC如下所示

目前我们对漏洞的详细情况一无所知,首先我们根据请求路径的映射,找到后来用来接收该请求的方法,经过一番搜索我门找到了“/oauth/authorize”这个路径映射的是AuthorizationEndpoint.authorize方法。

1

我们在该方法中打上断点,然后发送poc即可看到程序执行到断点处,这里有一个需要注意的值,就是errorPage这个属性的值,其值为“forward:/oauth/error”,这个值后续会使用到。

2

程序往下执行,来到一个if判断,这里判断的值就是我们poc中传递的response_type值,这里主要判断response_type的值是不是“token”或者“code”,很明显不是,这里传递的response_type的值是“${3*10}”,所以会抛出一个“Unsupported response types”,也就是“不支持的返回类型错误”。

3

然后就是一系列的异常操作,没什么特别值得讲的,接下来我们的断点下在DispatherServlet.processDispatchResult方法里,由于之前在AuthorizationEndpoint.authorize方法中执行出现了异常,所以Spring Security会返回一个认证错误的执行页面,而跳转的方式和地址就是我们刚才看到的errorPage这个属性的值,也就是“forward:/oauth/error”,这里指定了跳转方式,和跳转的路径,跳转方式为“forward”,也就是服务器内部跳转,而跳转的路径就是“/oauth/error”,后续的执行就是Spring Security在内部。最终发起转发的位置在哪呢?在InternalResourceView.renderMergedOutputModel方法中,

4

可以看到真正出发服务器内部转发的代码是最后一行的rd.forward(request, response);,rd变量是一个ApplicationDispatcher对象,ApplicationDispatcher.forward方法的作用就是处理服务器内部的请求转发,而需要请求的路径”/oauth/error” 在执行getRequestDispatcher方法中传入了进去 并最终返回一个ApplicationDispatcher对象,然后调用了ApplicationDispatcher.forward方法进行服务器内部请求转发,这个转发的过程就不做过多赘述了,不是我们研究的重点。

现在我们已知转发的路径为”/oauth/error”,那我们就去搜索这个路径,经过搜索找到的该路径对应的方法,为WhitelabelErrorEndpoint.handleError方法。

5

这里我们需要留意的就是这个error变量,可以看到就是之前在AuthorizationEndpoint.authorize方法中抛出的Unsupportedresponsetypes异常,其中有一个detailMessage属性,其中封装的是一段字符串,而该段字符串中的${3*10}就是SPEL表达式,也是我们在poc中传递的response_type的值,而ERROR中的${error.summary}同样也是SPEL表达式,而ERROR属性则被传入了SPELVIew的构造方法中,进而生成了一个SPELView对象,该类从类型来分析很明显是用于处理SPEL表达式的,我们跟进该类。

SPELVIew在构造方法中实例化了一个匿名内部类对象并赋值给了resolver属性,这个对象就是SPEL代码执行的核心.为什么说这个PlaceholderResolver.resolvePlaceholder方法是核心关键就在于用红圈圈里来的这段代码。即Expression expression = parser.parseExpression(name); 这段代码的作用就是解析和执行SPEL表达式,至于parser属性是什么类型也可以截图看一下,从截图中看到是SPELExpressionParser类型。

6

7

在该处下断点,看下执行结果

8

这里看到parser属性是SPELExpressionParser类型,结合之前的SPEL使用介绍,可知这里就是要解析SPEL表达式了,而传入的name变量就是要解析的SPEL表达式,这个SPEL表达式就是“error.summary”,那么这个error是什么呢?在WhitelabelErrorEndpoint.handleError方法中,可以看到error就是封装进去的UnSupportedResponseTypesExpection对象,而UnSupportedResponseTypesExpection的父类OAuth2Exception有一个名为getSummary的方法,而在之前的截图中看到在SPELView.render方法中,调用了StandardEvaluationContext.setRootObject,传入的参数是一个Hashmap对象, 当map对象像以setRootObject方法传入SPEL上下文中的时候,就可以以key.valueProperty/valueMethod的形式进行反射调用,也就是反射调用属性或者调用对应的getter方法,注意这里能通过反射调用的方法只有getter方法,测试代码如下所示

所以解析“error.summary”这个SPEL表达式最终就会调用到OAuth2Exception.getSummary方法,最终得到的值如下所示

9

最终的到的value是一串字符串,而在这段字符串中,属于SPEL表达式的是“${3*10}”,如此以来就到达了代码执行的位置,执行结果如下图所示

10

最终执行的结果会返回至前端页面,至此spring-security-oauth2 SPEL表达式注入漏洞分析完毕

4.总结

其实经过以上分析,大家不难发现,可以执行代码和对类进行操作是SPEL表达式模块所提供的正常功能,但是问题出在哪呢?就出在了Spring-oauth2这个模块对response_type这个参数校验的不严格,在后续的操作中,仅仅只是将外部的“$”符号和“{}”进行了删除,除此以外就没有进行任何有效的过滤了,所以,表达式注入漏洞就产生了。

Written by