漏洞概要
可参考官方安全公告:https://cwiki.apache.org/confluence/display/WW/S2-001
漏洞分析
在HTTP请求被Struts2处理时,首先读取web.xml
文件,这个是网站配置文件,里面有个过滤器,叫:org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
然后这个过滤器执行完之后,会经过一系列的拦截器,这些拦截器可以是默认的,也是可以用户自定义的。
Struts2请求处理流程(来自攻击JavaWeb应用[5]):
1 | 这里科普几个概念 |
例如下图struts.xml
中的package
继承了struts
默认的拦截器(struts-default),具体可以查看struts-default.xml
文件。
这里我们要关注params
这个拦截器,代码位置:xwork-2.0.3.jar!\com\opensymphony\xwork2\interceptor\ParametersInterceptor.class
经过一系列的拦截器处理后,数据会成功进入实际业务 Action
。程序会根据Action
处理的结果,选择对应的 JSP
视图进行展示,并对视图中的 Struts2
标签进行处理。
在本实例中Action
处理用户登录是返回error
根据返回结果以及先前在struts.xml
中定义的视图,程序将开始处理 index.jsp
从代码里我们可以看得到,struts2
使用了自定义标签库,也就是/struts-tags
, 通过阅读 struts2-core-2.0.8.jar!/META-INF/struts-tags.tld
文件,我们得知这个textfield
标签实现类是org.apache.struts2.views.jsp.ui.TextFieldTag
了解jsp自定义标签的同学应该知道,这时候我们需要找的是doStartTag
方法,因为解析标签是从这个方法开始,具体可以参考 TagSupport详解, 通过在TextFieldTag
类的ComponentTagSupport
父类我们找到doStartTag
方法
当在JSP
文件中遇到 Struts2
标签 时,由于s2的标签库都是集成与ComponentTagSupport
类,程序会先调用 doStartTag
,并将标签中的属性设置到 TextFieldTag
对象相应属性中。
最后,在遇到 />
结束标签的时候调用 doEndTag
方法。
1 | public int doEndTag() throws JspException { |
我们跟进this.component.end
方法,该方法调用了 this.evaluateParams();
方法来填充JSP
中的动态数据。
跟进this.evaluateParams
方法,发现如果开启OGNL
表达式支持(this.altSyntax()),会进行属性字段添加OGNL
表达式字符(%{name})
然后使用findValue
方法从值栈中获得该表达式所对应的值,跟进findValue
方法
findValue
在开启了altSyntax
且toType
为class.java.lang.string
时调用TextParseUtil.translateVariables
方法
跟进该方法
发现该方法重名加载
我们传入translateVariables
方法的表达式 expression
为 %{password}
,经过 OGNL
表达式解析,程序会获得其值 %{1+1}
(这里就是我们传入的payload)。由于此处使用的是 while
循环来解析OGNL
,所以获得的%{1+1}
又会被再次循环解析,最终也就造成了任意代码执行。
关键代码:
1 | public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, TextParseUtil.ParsedValueEvaluator evaluator) { |
因此究其原因,在于在translateVariables
中,递归解析了表达式,在处理完%{password}
后将password
的值直接取出并继续在while
循环中解析,若用户输入的password
是恶意的OGNL
表达式,比如%{1+1}
,则得以解析执行。
POC:
1 | %{1+1} |
修复
增加了了递归解析的判断