Lucifaer's Blog.

S2-057(CVE-2018-11776)流程分析

Word count: 1,580 / Reading time: 6 min
2018/12/25 Share

S2-057的流程分析,该漏洞不好利用,但是整个调用流程是非常值得去研究的,跟一边后基本上对Struts2的执行流程就非常清楚了,关于POC将在后面一篇文章具体说明。

0x00 漏洞描述

Problem
It is possible to perform a RCE attack when alwaysSelectFullNamespace is true (either by user or a plugin like Convention Plugin) and then: namespace value isn’t set for a result defined in underlying configurations and in same time, its upper package configuration have no or wildcard namespace and same possibility when using url tag which doesn’t have value and action set and in same time, its upper package configuration have no or wildcard namespace.

-w1066

从漏洞简述中可以看出漏洞在当应用设置alwaysSelectFullNamespacetrue时有两种情况可以触发RCE:

  • 在xml配置中未设置namespace值,且上层包中未设置namespace或其未使用通配符所设置的namespace
  • 当使用未设置valueaction值的url标签,且上层包中未设置namespace或其未使用通配符所设置的namespace

当然说啥都不如看diff来的更加直观,最直观的就是在org.apache.struts2.dispatcher.mapper中在DefaultActionMapper设置namespace时增添了对于namespace的校验:

0x01 调用流程分析

从简述中我们看到比较关键的点是对setNamespace所设置的namespace进行了一次校验,那么漏洞可能就出现在这个地方。

而让我觉得比较好玩的地方就是我在找mapping.setNamespace()的调用栈时也同时找到了整条触发链以及ognl执行的点。我不知道其他的分析是如何在分析之初就可以准确定位到org.com.opensymphony.xwork2.DefaultActionInvocation$executeResult的,所以我想通过这一个diff出来的信息来找到整个漏洞的调用过程,以及解释为什么S2-057需要如此苛刻的条件。

漏洞修补的点在parseNameAndNamespace,也就是在mapping.setNamespace()前首先对namespace进行校验,校验这个namespace是否在allowedActionNames中。那么我们来顺着这个思路看一下parseNameAndNamespace的调用栈以及mapping的调用关系。

mapping是一个ActionMapping实例,而parseNameAndNamespace的作用就是从URI中设置ActionMappingnamenamespace。现在知道了这个方法大致的调用栈和作用后,我们看一下哪里调用了这个方法:

getMapping()调用了这个方法,我们跟进一下,看看这个方法干了点什么。

可以看到就是根据当前的请求和设置,生成该请求的mapping,这个mapping可以用来调用相关的Action,最后通过ActionProxy来根据struts.xml找到相应的Action,最后再通过ActionProxy来创建一个ActionInvocation实例来调用相应的拦截器。具体可以参考这张经典的图:

ok,回到我们的漏洞,现在也就意味着我们需要跟到Struts2的运行流程中的ActionMapper流程这一块,来找哪里调用了getMapping()方法。

接下来我们看一下RepareOperationsfindActionMapping方法,该方法查询并选择性的创建ActionMapping是处于主运行流程中的一个方法。

可以看到这个方法有一个布尔变量forceLoopup,如果为true,则将会从ActionMapper实例中查找操作映射,并且该方法最后是返回一个mapping的,那么我们继续向上跟,看看哪里调用了此方法。

可以看到分别在准备Filter和执行Filter的过程中调用了findActionMapping()方法,从整个Struts的流程图中我们可以得知这两个调用点是在ActionMapper前后执行的,我们跟进StrutsExecuteFilter看一下:

可以看到这里校验mapping是否获取成功,如果获取成功,则执行execute.executeAction()跟进看一下这个方法具体的执行流:

可以看到最后将执行到Dispatcer.serviceAction中,而这个方法为指定的Action生成了ActionProxy,也就是Struts2整体运行流程中通过FilterDispatcher生成ActionProxy的过程。我们具体看一下当mapping中存在getResult的结果时,所执行的proxy.execute()。首先看一下execute()的继承关系:

跟进StrutsActionProxy看一下具体实现:

跟进invoke()看一下:

我们到DefaultActionInvocation来看一下invoke()的具体实现,代码很长,这里只截一部分:

可以看到如果ActionInvocation如果并未执行的话,是通过executeResult()来执行的,跟进看一下具体的操作:

可以看到这里首先会调用createResult()获取相应的result对象,如果result不为null则执行result.execute(this),这里的execute()是由具体的result对象实现的。我们看一下execute()的继承关系,来看看有多少地方是可以被我们调用的:

根据漏洞发现者的博客所述以上红框所标注的类都是可以触发漏洞的点,我们选ServletActionRedirectResult这个类来看一下:

注意到如果namespace不为null时,就会调用namespace = conditionalParse(namespace, invocation);,同时这段代码的关键性操作就是调用setLocation()tmpLocation进行赋值,然后我们顺着super向上看:

而这里的location就是我们上面setLocation()所设置的tmpLocation,我们跟进conditionalParse()来看一下具体做了什么操作:

看到了熟悉的TextParseUtil.translateVariables,而这里的paramtmpLocation,而tmpLocation是通过getUriFromActionMapping来设置的,而和ActionMapping是有关的,这样一来就绕了回来,也就是说我们如果构造了一个存在ognl表达式的namespace,那么最后这个namespace会被作为参数带入到TextParseUtil.translateVariables()中进行ognl表达式解析,并执行表达式。

0x02 利用条件

根据上面对于流程的分析,我们来梳理一下利用条件。

  • 我们需要在namespace中构造污点即包含ognl表达式,这需要我们自定义namespace,而这就需要我们在default.properties中设置struts.mapper.alwaysSelectFullNamespace为true,默认是为false的。

  • 之后为了我们构造的ognl表达式可以执行也就是执行到这四个触发点,我们需要在Struts2执行时让Struts2找到这些触发的action,而这就意味着我们需要修改struts-actionchaining.xml,而那里看这几个action的类型呢?——struts-default.xml(没找到漏洞作者说的Portlet的点):

    也就是说需要根据类型修改struts-actionchaining.xml,才能完成action的跳转。

所以这也是为什么这个漏洞利用条件苛刻的原因。

0x03 Reference

CATALOG
  1. 1. 0x00 漏洞描述
  2. 2. 0x01 调用流程分析
  3. 3. 0x02 利用条件
  4. 4. 0x03 Reference