当前位置: 移动技术网 > IT编程>开发语言>Java > SpringBoot SpEL表达式注入漏洞-分析与复现

SpringBoot SpEL表达式注入漏洞-分析与复现

2018年12月27日  | 移动技术网IT编程  | 我要评论

目录

影响版本:
1.1.0-1.1.12
1.2.0-1.2.7
1.3.0
修复方案:升至1.3.1或以上版本
我的测试环境:springboot 1.2.0

0x00前言

这是2016年爆出的一个洞,利用条件是使用了springboot的默认错误页(whitelabel error page),存在漏洞的页面在:/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/errormvcautoconfiguration.java

0x01触发原因

本次漏洞的触发点在springboot的自定义错误页面,功能是页面返回错误,并提供详细信息,信息中包括错误status("status"->500)、时间戳("timestamp"->"fri dec.....")、错误信息("error"->"internal server error")、和用户输入的参数("message"->"abcd"),这些参数在模板文件中以类似于以下形式存在:”error 1234 ${status}---${timestamp}---${error}---${message}“。

后端进行渲染视图时,首先,解析错误页面模板中的参数名(status、timestamp、error、message),即判断模板中每个${的位置,然后再判断最近的}的位置,从而将参数名一个个读取出来,然而这里使用了递归,也就是说如果参数名中还包含${和}的话,这个解析引擎会再次递归一次,再次解析这个值,如,模板中有个值为${${abc}},由于使用了递归,解析引擎会对其解析两次,第一层去掉最外层的{}解析成${abc},然后将其作为参数进行第二次解析。在第二次解析中将里层的{}去掉,变成abc。

每次将一个参数名解析出来之后,就将参数名传入spel引擎,解析成context中对应参数名的值(如"status"->500),完成之后返回参数值给第一步中的解析引擎(返回500)。

解析引擎收到spel传回的参数值之后,再次进行递归,以防参数值中也存在${和},存在则去之,然后在递归过程中再次传入spel引擎进行解析。这里就是触发点了。假设用户的输入中包含${payload},则spel第一次message解析成${payload}之后,解析引擎进行递归,去掉${和}后将payload传入spel引擎,spel引擎将将直接对payload进行解析,从而触发了漏洞,触发点如下图所示。

0x02调试分析

先搭好存在漏洞的springboot版本的环境,使用其自带的sample搭建一个服务器,然后自己写一个控制器,抛出异常即可。

开启调试,使用浏览器访问

http://127.0.0.1:8080/?payload=${new%20java.lang.processbuilder(new%20java.lang.string(new%20byte[]{99,97,108,99})).start()}

首先,将context赋值到this.context中,然后以this.template和this.resolver为参数调用replaceplaceholders方法。

this.template="<html><body><h1>whitelabel error page</h1><p>this application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id='created'>${timestamp}</div><div>there was an unexpected error (type=${error}, status=${status}).</div><div>${message}</div></body></html>"

跟进replaceplaceholders方法,进入了propertyplaceholderhelper文件

继续跟进parsestringvalue方法(这就是分析中说的存在递归的方法),strval的值为之前的this.template,将之赋值给result,然后通过判断result中${和}的位置,开始解析result中的第一个参数名,并赋值给placeholder,本次的值为"timestamp",然后将placeholder作为第一个参数,再次调用本方法(递归,以防字符串placeholder中包含${})。

跟进递归,由于placeholder的值为"timestamp",其中不包含${,导致startindex为-1,故不进入while语句,直接到了return,因此可以发现,在递归时,如果第一个参数中不包含${,则直接将第一个参数返回。

再次回到之前的点,下一步是调用resolveplaceholder方法,此函数的作用是查找this.context中对应参数的值并返回,跟进看一下

首先看一下this.context,发现"timestamp" -> "sat dec 15 10:49:02 cst 2018"

继续跟进,发现value被赋值成spel解析后的值,然后return

回到parsestringvalue方法,将经过spel解析后return的值赋值给propval,由于propval != null,故跳过第一个if语句,进入第二个语句,将propval作为第一个参数再次递归。通过上一次递归我们发现,如果第一个参数中没有${,则直接返回第一个参数的值,因此这次就不再跟进了。

递归回来后propval的值没变,使用replace将propval替换到result中的对应的参数位。接着寻找template中的下一个参数位,赋值给startindex,用于下一次while条件判断。

进入第二次循环,这次的参数是error,和之前的timestamp过程一样,就不具体分析了

第三次while循环,参数是status,同上,不具体分析

进入第四次循环,重头戏来啦,这次的参数是message,其值是用户输入的值。

跟进到第一次递归,防止参数名中含有${},由于参数名士message,故略过这一步。然后到了resolveplaceholder方法,用于使用spel表达式引擎解析message的值,再跟进一下。

发现value的值为用户传入的payload

其中包含${},是一个spel表达式。继续跟进,返回到parsestringvalue方法。

可以发现,为了防止propval中包含${},再次进行一次递归。下面就是漏洞关键点了,跟进这次递归。

此时placeholder的值为去掉${}的payload,即:"new java.lang.processbuilder(new java.lang.string(new byte[]{99,97,108,99})).start()"。将placeholder作为第一个参数传入spel解析函数(147行)

可以发现,这里直接使用parseexpression(name),而name的值就是我们的payload。接着使用getvalue解析payload:

expression expression = this.parser.parseexpression("new java.lang.processbuilder(new java.lang.string(new byte[]{99,97,108,99})).start()");

object value = expression.getvalue(this.context);

然后触发payload,漏洞利用完成。

0x03补丁分析

补丁创建了一个新的nonrecursivepropertyplaceholderhelper类,用于防止parsestringvalue进行递归解析。

0x04参考文章

spring boot框架whitelabel error page spel注入漏洞分析

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网