前言
发现之前没有系统的对struts2漏洞有一个分析,因此决定抽空分析一下struts2,防止以后需要调试过waf等的时候一脸茫然。
影响范围&&环境
WebWork 2.1 (with altSyntax enabled), WebWork 2.2.0 - WebWork 2.2.5, Struts 2.0.0 - Struts 2.0.8
漏洞环境:https://github.com/vulhub/vulhub/tree/master/struts2/s2-001
分析
首先引入知识点—在Struts2框架中是通过包来管理action、result、interceptor、interceptor-stack等配置信息的。并且通常配置struts.xml的时候,都继承一个名为“struts-default.xml”的包,这是struts2中内置的包。

首先查看strtus2的配置文件,可以看到我们配置了一个包,该包果然是继承了struts-default这个包

打开struts2-core-2.0.8.jar,可以发现根目录下存在一个struts2-default.xml,这个就是我们上一步继承的父包。
可以看到这个包中以及定义了很多拦截器,拦截器是每个http请求都要通过的。分析s2-001这个漏洞最主要就是看ParametersInterceptor这个拦截器。

在ParametersInterceptor.doIntercept设置断点,在username和password随便一个地方输入%{1+2}

走到this.setParameters(action, stack, parameters),该方法的作用为给action对象赋值

原本action中的username和password是空的,可以看到经过了setParameters后,username和password字段都填充上了值。然后接下来进入invocation.invoke()。

进入com.opensymphony.xwork2.DefaultActionInvocation.invoke()
箭头标注的if判断目的是将剩下所有的拦截器逻辑执行完

this.invokeActionOnly()的作用为调用action方法,在本环境中就是调用LoginAction的execute()方法

然后走到了this.executeResult()方法,该方法的作为是解析返回结果,s2-001的漏洞就出在这里面

进入executeResult(),获取了ServletDispatcherResult对象,并调用其execute方法

进入execute方法后,执行到this.doExecute(this.lastFinalLocation, invocation)

进入ServletDispatcherResult.doExecute后执行到dispatcher.forward(request, response)

在ComponentTagSupport的doStartTag()和doEndTag()方法设置上断点,这里是解析jsp中struts标签的地方。
第一个解析的标签是FormTag,对应着jsp里面的<s:form>


第二个解析的标签为TextFieldTag,也就是username那一行

doStartTag执行完就到了doEndTag,执行到this.component.end(this.pageContext.getOut(), this.getBody())方法

进入UIBean.end方法中,执行到this.evaluateParams()

进入UIBean.evaluateParams,this.altSyntax表示是否支持ognl表达式,默认是支持的,所以expr为%{username},最后执行到this.findValue(expr, valueClazz)。

进入Component.findValue方法,执行到TextParseUtil.translateVariables(‘%’, expr, this.stack)

进入TextParseUtil.translateVariables方法,需要注意的是result为while循环外层定义的变量,此刻被赋值为了%{username}

最后执行到stack.findValue(var, asType),此时的var为username,从值栈中取出的值应当为LoginAction方法的username属性的值也就是之前在PrepareInterceptor中赋的值,也就是%{1+2}


在下图箭头处,o的值%{1+2}被赋给了result,之前提过result是while循环外的变量,因此result值修改后再次进入循环

此时再次走到stack.findValue(var, asType),只不过var的值为1+2了

进入OgnlValueStack.findValue,执行到OgnlUtil.getValue(expr, this.context, this.root, asType),此时expr的值为1+2,这个内容是攻击者可控的。熟悉Ognl表达式执行命令的朋友知道,expr值可控,就可以执行任意命令了。而在s2-001中,这个值之所以可控,是因为while迭代解析了标签中的值。从username到1+2,然后还在使用Ognl解析,最后导致了执行用户可控的ognl表达式,进而命令执行。

执行完毕的结果如下

回显命令执行poc
| 1 | %{ | 

Ognl执行命令
这个环境和之前一样,也是expr参数可控,这样就可以执行命令了。


