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










