struts2漏洞_Struts2漏洞系列之「S2-057」namespace可控导致的OGNL表达式执行

Smi1e@Pentes7eam

漏洞信息:https://cwiki.apache.org/confluence/display/WW/S2-057

当Struts2的配置满足以下条件时:alwaysSelectFullNamespace值为true、action元素未设置namespace属性或使用了通配符、result typechain、redirectAction、postback之一 。namespace将由用户从uri传入,并作为OGNL表达式计算,最终造成命令执行漏洞。

漏洞复现

379169402df2f6bcaca47371863872e6.png

影响范围

Struts 2.0.4 - 2.3.34,Struts 2.5.0 - 2.5.16

漏洞分析

struts.xml

="struts.mapper.alwaysSelectFullNamespace" value="true" />

="S2-032" extends="struts-default">

="login" class="com.demo.action.LoginAction">

="success" type="redirectAction">

="actionName">page?username=${username}

="error">index.jsp

="page" class="com.demo.action.PageAction">

="success">welcome.jsp

struts2提供了一个命名空间的概念,可以通过 packagenamespace属性来设置,使用它可以避免action的名字冲突,同时也可以在逻辑上给action分类。alwaysSelectFullNamespace用来设置命名空间是否必须进行精确匹配,默认为false。进行精确匹配时要求请求url中的命名空间必须与配置文件中配置的某个命名空间必须相同,如果没有找到相同的则匹配失败。

org.apache.struts2.dispatcher.mapper.DefaultActionMappergetMapping方法用来从 uri 中解析ActionMapping对象,跟进用来从uri中解析出actionNamenamespaceparseNameAndNamespace方法。

d9aa785c615b0baa947433f7a5372c28.png

alwaysSelectFullNamespace为true时,namespace的值可以通过uri控制

e14b1be3f10b8d3bebbde14ef02f9ce4.png

在S2-012中我们知道 Result接口的实现类用于对action类实例执行之后结果的处理。struts-default.xml中默认有以下几种,存在漏洞的有chain、redirectAction、postback

="struts-default" abstract="true">

="chain" class="com.opensymphony.xwork2.ActionChainResult"/>

="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>

="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/>

="httpheader" class="org.apache.struts2.dispatcher.HttpHeaderResult"/>

="redirect" class="org.apache.struts2.dispatcher.ServletRedirectResult"/>

="redirectAction" class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>

="stream" class="org.apache.struts2.dispatcher.StreamResult"/>

="velocity" class="org.apache.struts2.dispatcher.VelocityResult"/>

="xslt" class="org.apache.struts2.views.xslt.XSLTResult"/>

="plainText" class="org.apache.struts2.dispatcher.PlainTextResult" />

="postback" class="org.apache.struts2.dispatcher.PostbackResult" />

5de7a431890f11fb34d20ab4e029860d.png

com.opensymphony.xwork2.DefaultActionInvocation类的executeResult方法中下断点,因为我们配置的是type="redirectAction"所以这里会调用org.apache.struts2.dispatcher.ServletActionRedirectResult来处理我们的action实例执行结果。

fbfa0a252af3b1c9997f4f84008e9147.png

跟进其 execute方法

public void execute(ActionInvocation invocation)throws Exception{

this.actionName=this.conditionalParse(this.actionName,invocation);

if (this.namespace==){

this.namespace=invocation.getProxy.getNamespace;

}else{

this.namespace=this.conditionalParse(this.namespace,invocation);

}

if (this.method==){

this.method="";

}else{

this.method=this.conditionalParse(this.method,invocation);

}

String tmpLocation =this.actionMapper.getUriFromActionMapping(new ActionMapping(this.actionName,this.namespace,this.method,(Map)));

this.setLocation(tmpLocation);

super.execute(invocation);

}

解析出 actionName后由于我们的struts.xml中没有配置namespace,所以this.namespace=会进入invocation.getProxy.getNamespace获取前面解析出的namespace

public String getNamespace{

return this.namespace;

}

然后会执行 this.actionMapper.getUriFromActionMapping(new ActionMapping(this.actionName, this.namespace, this.method, (Map)))根据actionName、namespace、method组成一个用于跳转的uri,然后调用this.setLocation(tmpLocation)赋值给ServletActionRedirectResult对象的location属性。这个时候后面的流程就和S2-012一样了。

37eba39bcd5419b90b80d5a185563cd3.png

调用 super.execute(invocation)执行org.apache.struts2.dispatcher.StrutsResultSupport类的execute方法,然后在conditionalParse方法中调用了熟悉的TextParseUtil.translateVariables把上面设置的location属性值进行ognl表达式解析。

12ce2ce00cf780c17cc3622cf16d0daa.png

调用栈

f95bb3d5c34aae4e7757b09869b11802.png

com.opensymphony.xwork2.ActionChainResultorg.apache.struts2.dispatcher.PostbackResult的触发流程跟ServletActionRedirectResult大致一样。

com.opensymphony.xwork2.ActionChainResultexecute方法中直接执行了。

0fa4b31838d3fde0c81ba1cdac628354.png

org.apache.struts2.dispatcher.PostbackResult

9f3ec0a41d4d2349076477eb55ec5633.png

虽然漏洞的影响版本是Struts 2.5.0 - 2.5.16,然而在2.5.13之后禁止使用 #context来获取OgnlContext对象

bef44ffe5bdcc067f7e2a35f9d59c998.png

2.5.20版本后 excludedClasses不可变了

dba54501d62d039672af4138aa295dc5.png

所以S2-045的绕过方法只能使用到2.5.12。不过在这篇文章中提到2.5.13到2.5.16的绕过方式:作为武器的CVE-2018-11776:绕过Apache Struts 2.5.16 OGNL 沙箱

首先因为 context无法访问,所以第一步是寻找context的替代方案。

作者发现在 attr中有一个keystruts.valueStack,保存着OgnlValueStack对象。

3d3ac7d891cff71d0afcee46216170b7.png

我们可以利用 OgnlValueStack获取context(#context=#attr['struts.valueStack'].context)

而虽然 excludedClasses等属性不可变,但我们可以利用setter将其置空。

(#context=#attr['struts.valueStack'].context).(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.setExcludedClasses('')).(#ognlUtil.setExcludedPackageNames(''))

虽然这里调用 setterexcludedClasses等属性清空了,但是这里相当于又给excludedClasses创建了一个新的集合,所以这里excludedClasses的清空并不会令_memberAccess中的excludedClasses清空。

be838a94145b30e357769fe7084c9889.png

但是我们可以再发送一次请求 (#context=#attr['struts.valueStack'].context).(#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)).(@java.lang.Runtime@getRuntime.exec('xcalc'))来令_memberAccess中的excludedClasses清空。这是因为上面说过OgnlUtil使用的是单例模式,每一次请求用的都是同一个OgnlUtil对象。我们第一次请求清空其属性后,在随后的请求中它的属性依然是被清空的。而_memberAccess是一个短暂的对象,每一次请求都会重新创建一个ActionContext以及ValueStack对象

4ca6465fc78596234b181a3ac36de762.png

同样每次请求都会对 OgnlValueStack进行依赖注入,所以第二次请求就会将全局对象OgnlUtil被清空后的excludedClasses、excludedPackageNamePatterns、excludedPackageNames赋值给了该请求的_memberAccess,从而完成绕过。

32d02b4a0fa61e677b0ae0b497de791f.png

漏洞修复

https://github.com/apache/struts/commit/4a3917176de2df7f33a85511d067f31e50dcc1b2

org.apache.struts2.dispatcher.StrutsResultSupport添加了一个parseLocation属性用来表示是否对跳转的location属性进行OGNL表达式解析

a2211b5737ab99668f10e3be8f7b28f0.png

org.apache.struts2.dispatcher.PostbackResultorg.apache.struts2.dispatcher.ServletActionRedirectResult中的parseLocation属性都设置成了false。

com.opensymphony.xwork2.ActionChainResult在配置了namespace的情况下才会对其进行OGNL表达式解析。

5e36c9d31f9954eaec954565991f7a4d.png ee9a923c15db85e36dd5293f0915349a.png
https://www.easyaq.com
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值