一:起因
最近公司代码被扫出有一个xss漏洞,检查之后,发现大致是这样一个页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
模板用的是freemarker,大致是这样子:
1 2 3 4 5 6 7 8 9 10 |
|
如果我们访问url/demo?id=<script>alert("xss!")</script>
,会发现id的参数原封不动的打印到了页面上,就会出现反射型xss!
代码在https://github.com/code4craft/xssdemo里的xss-demo tag下。
二:问题流程
分析漏洞原因前,先要稍微看一下struts结构(自己画的,可能不严谨):
OGNL是底层的表达式引擎,是联系起上下文和模板输出的桥梁。
XWork是个什么东西呢?它可以理解为一个请求-响应模式的通用框架(不仅仅局限于Web),这个Action就是一个命令。而struts2可以说是XWork在web领域的一个特定实现。
XWork包括Action/Interceptor/Result几个大部分,还有用于执行流程的ActionProxy和ActionInvoker,以及处理数据的ActionContext和ValueStack。
所以参数的转换和注入是在XWork里进行。Struts的主要执行流程在DefaultActionInvocation
里。大致解释一下流程:
当Struts捕获到参数时,会交由ognl进行参数转换。我们都知道Struts是通过setter方法进行的参数注入,更进一步的,它是通过ognl表达式来查找方法,并进行属性注入,代码在OgnlRuntime.setProperty
里。
那么如果注入不成功呢?OgnlRuntime
会抛出MethodFailedException
,然后ConversionErrorInterceptor
会将原始参数
注入到invocation.getStack()
中去,而最终freemarker会读取这个原始数据,并打印到页面上(FreemarkerResult
)!这个时候,无论字段最终值是什么都不重要了,因为在ognl的Stack里,它已经用原始值给override了!插一句,其实这个值貌似是为了debug用的,会返回名字为”input”的result,这样会返回找不到方法的404页面,但是公司使用的貌似不太好用,仍然会正常返回!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
三:求解
那么怎么解决这个问题呢?本着回馈社区的精神,给Struts2官方发了一封邮件,并上传了demo到https://github.com/code4craft/xssdemo。然后过了一天有个叫Lukasz Lenart的大叔程序员回复我了,老外还是很客气的,回答也很及时(算上时差)。首先确认了问题的存在,然后说这不是一个bug,你可以用${id?html}来进行输出转义。我觉得这个解决方案虽然管用,但是是比较反直观的,因为一般人都会直觉上因为这里只是读取Action中的getter取值,既然是基本类型,哪会还需要转义?本来想喷回去的,又搜了一下这个Lukasz Lenart的来历,然后出来这么个:
被lead亮瞎了!亲力亲为,这才是开源项目的氛围嘛!
不过呢,即使大神&作者都发话了,我还是希望有框架内的方案,或许默认对ConversionErrorInterceptor
注入时进行HtmlEscape是个不错的主意?于是有了EscapedStrutsConversionErrorInterceptor
:
1 2 3 4 5 6 7 8 9 10 |
|
然后在项目里将默认的converionError
替换为我们的新类就可以了!怎么定义stack?看看struts-core包里的struts-default.xml就知道了!(PS:自定义interceptor是Struts里很有用的技巧,大家不妨自己研究一下)。修改过的代码:https://github.com/code4craft/xssdemo/blob/master/src/main/resources/config/struts/struts.xml
当然还有一种办法,就是直接在request参数中过滤。但是这里就要区分哪些需要过滤,哪些不要过滤。我们公司使用了一套在字段上加注解的方法,解决了这个问题,也是可行的,不过就需要修改代码了。
参考资料:
- 《Struts2技术内幕》http://book.douban.com/subject/7154446/