Struts2漏洞系列
Struts2漏洞系列
前言
春节假期结束,回来不知道该干些什么,就找点事情做。之前没有怎么接触和了解过Struts2这个框架,只知道它好多洞,于是就趁现在学习一下。下面调试的是几个可以rce的漏洞。
各个漏洞详情查看:https://cwiki.apache.org/confluence/display/WW/Security+Bulletins
Struts 2 概述
Struts2是一个基于MVC(Models、View、Controller)设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。Struts 2是Struts的下一代产品,是在 Struts 1和WebWork的技术基础上进行了合并的全新的Struts 2框架。其全新的Struts 2的体系结构与Struts 1的体系结构差别巨大。Struts 2以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与Servlet API完全脱离开,所以Struts 2可以理解为WebWork的更新产品。虽然从Struts 1到Struts 2有着太大的变化,但是相对于WebWork,Struts 2的变化很小。
S2-001
环境搭建
环境没有使用现成的而是自己搭建一遍
这里参考了https://mengsec.com/2019/10/29/Java-Web-Struts2-Env-Build/
漏洞简介
WebWork 2.1+ 和 Struts 2 的“altSyntax”功能允许将 OGNL 表达式插入到文本字符串中并进行递归处理。这允许恶意用户通常通过 HTML 文本字段提交一个字符串,其中包含 OGNL 表达式,如果表单验证失败,服务器将执行该表达式。例如,假设我们有一个表单,要求“phoneNumber”字段不能为空:
<s:form action="editUser">
<s:textfield name="name" />
<s:textfield name="phoneNumber" />
</s:form>
用户可以将“phoneNumber”字段留空以触发验证错误,然后使用 %{1+1} 填充“name”字段。当表单重新显示给用户时,“名称”字段的值将为“2”。原因是默认情况下,值字段被处理为 %{name},并且由于 OGNL 表达式是递归求值的, 因此它的求值就像下面表达式 一样。
%{ %{1+1}}
OGNL解析代码实际上在XWork中,而不是在WebWork 2或Struts 2中。
影响范围
WebWork 2.1(启用 altSyntax)、WebWork 2.2.0 - WebWork 2.2.5、Struts 2.0.0 - Struts 2.0.8
漏洞分析
先跟一下整个流程
在web.xml中配置了/*
即全路径的filter,为org.apache.struts2.dispatcher.FilterDispatcher
类
<web-app>
<display-name>S2-001 Example</display-name>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
在这个类的doFilter下断点开始分析
根据漏洞描述传入%{1+1}
进行跟进分析
在org.apache.struts2.dispatcher.FilterDispatcher#doFilter
中获取到本次请求的上下文等信息
然后调用this.dispatcher.serviceAction(request, response, servletContext, mapping)
跟进serviceAction()
这里的proxy
获取到的是struts.xml
中是配置信息,因为mapping中的result属性为空进入了else
分支执行proxy.execute()
跟进execute()
调用了this.invocation.invoke()
在invoke()中会遍历struts-default.xml
配置的所有的拦截器,struts-default.xml
是Struts 2 框架的基础配置文件,为框架提供默认设置,这个文件包含在Struts2-core.jar中,由框架自动加载。
漏洞在ParametersInterceptor
中,ParametersInterceptor
拦截器会将接收到的请求数据设置到valueStack里,后面在jsp中会从valueStack中获取数据
然后进入ParametersInterceptor
中的doIntercept
方法
这里获取和设置一些参数等,然后调用this.setParameters()
在这里会通过setValue
给值栈中的action参数赋值
遍历完所有的拦截器后往下会调用this.invokeActionOnly()
跟进里面会发现其反射调用了action中的execute方法,这里就不进去看了
往下
检测这个代理的action是否有执行结果,如果没有进入到executeResult()
这里会根据struts.xml中给出的配置,根据返回结果跳转到对应的jsp执行
executeResult()如下
这里调用了execute(),往下
跟进 this.conditionalParse
前面的条件为true,进入到TextParseUtil.translateVariables()
往下跟进
继续跟进
这里就是解析表达式的地方,即漏洞触发的地方。但是现在还没解析到传入的参数%{1+1}
,先在这下个断点,跟着调试返回到execute(),并跟进doExecute()
这里调用dispatcher.forward(request, response);
进行转发到index.jsp
在index.jsp中使用了struts标签
解析标签使用的是org.apache.struts2.views.jsp.ComponentTagSupport
这个类
在标签的开始和结束位置,会分别调用对应实现类如org.apache.struts2.views.jsp.ComponentTagSupport
中的 doStartTag()
及 doEndTag()
方法:
doStartTag()
:获取一些组件信息和属性赋值,总之是些初始化的工作doEndTag()
:在标签解析结束后需要做的事,如调用组件的end()
方法
这里直接快进到解析username的地方,漏洞的触发点,就从 doEndTag()
开始
跟进end(),来到了org.apache.struts2.components.UIBean
跟进调用的evaluateParams()
在这个方法中判断了 altSyntax 是否开启,并调用 findValue()
方法寻找参数值,其实就是通过ogln表达式%{name}
来获取值
跟进findValue()
这里调用了刚刚调试到达过的 translateVariables() ,跟进
注意这里的while(true)
stack.findValue()是在值栈中查找username的值,即%{1+1}
往下将得到的%{1+1}当中表达式继续解析
这次跟进findValue()
看到value = OgnlUtil.getValue(expr, this.context, this.root, asType);
这里就解析了表达式
整个漏洞触发过程就是酱紫
总的来说就是:
用户通过使用 %{}
包裹恶意表达式的方式,将参数传递给应用程序,应用程序由于处理逻辑失误while(true)
,导致了二次解析,造成了漏洞
S2-003
漏洞简介
OGNL(Object-Graph Navigation Language)提供了广泛的表达式评估功能,其中包括(http://www.ognl.org/2.6.9/Documentation/html/LanguageGuide/expressionEvaluation.html)。该漏洞允许恶意用户绕过ParametersInterceptor内置的“#”使用保护,从而能够操纵服务器端上下文对象。
例如,要将#session.user设置为’0wn3d’,可以使用以下参数名称:
(’\u0023’ + ‘session’user’’)(unused)=0wn3d
在进行URL编码后,将如下所示:
(’\u0023’%20%2b%20’session’user’’)(unused)=0wn3d
影响范围
Struts 2.0.0 - Struts 2.1.8.1
环境搭建
搭个der,直接用S2-001的进行分析
漏洞分析
根据官方漏洞介绍,漏洞出现在com.opensymphony.xwork2.interceptor.ParametersInterceptor
中,并且是在对参数名称的处理中出的问题
在com.opensymphony.xwork2.interceptor.ParametersInterceptor
的doIntercept
方法下断点分析
doIntercept
方法中获取到请求中的参数和对应的值栈,然后调用setParameters
,根进
这个方法中会将参数Parameters进行遍历,然后将name传入acceptableName
检查其合法性
大概的意思是查name中是否包含=
、,
、#
、:
这几个字符,还有isExcluded()
检测是不是被排除的name
protected boolean isExcluded(String paramName) {
if (!this.excludeParams.isEmpty()) {
Iterator i$ = this.excludeParams.iterator();
while(i$.hasNext()) {
Pattern pattern = (Pattern)i$.next();
Matcher matcher = pattern.matcher(paramName);
if (matcher.matches()) {
return true;
}
}
}
return false;
}
然后返回true ,跳出循环
执行到getValue方法,获取name对应的值,然后通过调用setValue()
,对值栈中的参数赋值
在setValue中将name当作表达式进行解析了,如果能控制name就能进行OGNL注入了
这个name是参数名称,可以通过请求来构造即可
这个是2.0.8版本的,在高版本中这个过滤条件使用的是正则
这个this.acceptedPattern赋值是
而
payload需要绕过这个正则匹配 ,如上面漏洞简介所说的,通过unicode编码(u0023)或8进制(43)即可绕过安全限制
Tomcat-7.0.78本身限定了特殊字符;要验证此漏洞,需要安装Tomcat 6.0