尽管现在struts2用的越来越少了,但对于漏洞研究人员来说,感兴趣的是漏洞的成因和漏洞的修复方式,因此还是有很大的学习价值的。毕竟Struts2作为一个很经典的MVC框架,无论对涉及到的框架知识,还是对过去多年出现的高危漏洞的原理进行学习,都会对之后学习和审计其他同类框架很有帮助。
目录
- 前言
- S2-001
- S2-003
- S2-005
- S2-007
- S2-008
- S2-009
- S2-012
- S2-013
- S2-015
- S2-016
- S2-032
- S2-045
- S2-052
- S2-053
- S2-057
- S2-059
- S2-061
- 小结
- Reference
前言
尽管现在struts2用的越来越少了,但对于漏洞研究人员来说,感兴趣的是漏洞的成因和漏洞的修复方式,因此还是有很大的学习价值的。毕竟Struts2作为一个很经典的MVC框架,无论对涉及到的框架知识,还是对过去多年出现的高危漏洞的原理进行学习,都会对之后学习和审计其他同类框架很有帮助。
PS: 本系列分析的漏洞均为已公开的漏洞,Struts2官方都早已发布修复版本。建议直接使用最新版本。
S2-001
官方漏洞公告:
https://cwiki.apache.org/confluence/display/WW/S2-001
影响版本:Struts 2.0.0 - Struts 2.0.8
漏洞复现和分析
根据漏洞描述,可知struts2中有个名为altSyntax
的特性,该特性允许在表单中提交包含OGNL
表达式的字符串(一般是通过文本字段,即struts2的<s:textfile>
标签),且可对包含OGNL的表达式进行递归计算。
漏洞复现环境使用的是docker镜像:medicean/vulapps:s_struts2_s2-001
这里先使用最简单的PoC
进行调试:%{2+5}
Submit提交后,OGNL表达式返回结果并填充在textfield
文本框中:
下面就来调试分析一下。
由于漏洞是在struts2对文本标签<s:textfield>
处理的过程中触发的,所以先找到相对应的处理类。在IDEA里,对着<s:textfield>
处点击便可定位到文件struts-tags.tld
,其中可看到该标签相关的一些属性定义,包括该标签的对应的处理类为:org.apache.struts2.views.jsp.ui.TextFieldTag
。
在该类中搜索处理开始标签和结束标签的方法,发现其使用的是父类ComponentTagSupport
的处理方法:doStarTag
和doEndTag
。
在这两个方法中下断点。经调试发现,触发漏洞是在doEndTag
方法中。因此,当当前标签时TextField
类型时,单步跟进调试。
调试进入UIBean#evaluateParams()
方法中,当请求的参数中value
为null时,则会根据name
属性的值去获取对应的value
属性的值。且altSyntax
特性默认是开启的(该属性设置在struts2的文件default.properties
中),所以这里会用OGNL
表达式的标识符%{}
把name
属性的值包住,比如当前表单的用户名文本输入框中,name
属性的值为username
,则加了OGNL
表达式标识符后变为:%{username}
,如下图:
继续跟进findValue()
方法,后面会进入到TextParserUtil#translateVariables()
方法中,如下图:
在TextParserUtil#translateVariables()
方法中,有一个while(true)
循环,这里会调用OgnlValueStack#findValue()
方法来计算OGNL
表达式(其实底层调用的还是OGNL
的API)计算。<br>
计算%{username}
,截取%{}
里面的内容username
,会从值栈ValueStack的Root
对象中获取key为username
的值,即%{2+5}
。由于获取到的值%{2+5}
仍然是一个OGNL
表达式,故会再次进行计算,此时便是计算2+5
得到值7
。
PS:本文不会详细讨论struts2的ValueStack、OGNL等知识点。
想了解的朋友可参考陆舟的《Struts2技术内幕》一书中的第6章, 以及第8章的8.2小节。
到此,漏洞原理的部分已经分析完了。
由于比较好奇这里为什么表单文本框的内容提交后OGNL
表达式的计算结果会以替换文本输入框内容的方式进行回显。于是便进一步调试。
发现在UIBean#evaluateParams()
计算完成后,会进入UIBean#mergeTemplate()
方法构造一个页面返回到客户端。跟进该方法,如下图:
可看到该方法中使用了模板引擎Freemarker进行页面的构造,这里主要先针对用户名的文本框进行构造,所需参数由getParameters()
方法返回,返回的值里就包含了上面OGNL表达式%{2+5}
的计算结果7
,保存在key
为nameValue
的值中。
再来看看此时使用的模板template
参数的值/template/xhtml/text
,最后定位到具体的模板文件/template/simple/text.ftl
,内容如下图:
这就一目了然了:这里会判断参数parameters
中的nameValue
的值是否存在,存在的话便填充到该文本输入框的value
属性中。
可回显PoC
这里使用OGNL上下文对象context
去获取HttpServletResponse
对象,如下图:
于是有:
%