前言
发现之前没有系统的对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参数可控,这样就可以执行命令了。