JavaStruts2 学习与环境搭建

0x00 Struts2 基础

Struts2 简介

Apache Struts2 是一个非常优秀的 JavaWeb MVC 框架,2007 年 2 月第一个 full release 版本发布,直到今天,Struts发布 2.5.26 版本,而这些版本中,安全更新已至 S2-061,其中包含了非常多的 RCE 漏洞修复
基础资料

Struts2 执行流程

Struts2 是一个基于 MVC设计模式的 Web 应用框架,它的本质就相当于一个 servlet,在 MVC 设计模式中,Struts2 作为控制器(Controller)来建立模型与视图的数据交互。Struts2是在 Struts 和 WebWork 的技术的基础上进行合并的全新的框架。Struts2 以WebWork 为核心,采用拦截器的机制来处理的请求。这样的设计使得业务逻辑控制器能够与 ServletAPi 完全脱离开
Struts2 的执行流程
Filter:首先经过核心的过滤器,即在web.xml中配置的filterfilter-mapping。这部分通常会配置/*全部的路由交给 Struts2来处理
Interceptor-stack:执行拦截器,应用程序通常会在拦截器中实现一部分功能。也包括在struts-core包中struts-default.xml文件配置的默认的一些拦截器
配置 Action:根据访问路径,找到处理这个请求对应的 Action 控制类,通常配置在struts.xml中的package
最后由Action 控制类执行请求的处理,执行结果可能是视图文件,可能是去访问另一个 Action,结果通过HTTPServletResponse响应

如何实现 Action 控制类

通常有以下的方式
Action 写微一个 POJO类,并且包含execute()方法
Action 类实现Action接口
Action 类继承ActionSupport

0x02 环境搭建

IDEA选中web-app
导入Struts2的核心依赖

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

修改web.xml,在这里配置struts2的过滤器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<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>  

main下添加 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
package com.test.s201.action;    
    
import com.opensymphony.xwork2.ActionSupport;    
    
public class LoginAction extends ActionSupport {    
    private String username;    
    private String password;    
    
    public String getUsername() {    
        return username;    
    }    
    
    public void setUsername(String username) {    
        this.username = username;    
    }    
    
    public String getPassword() {    
        return password;    
    }    
    
    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.equalsIgnoreCase("admin"))){    
            return "success";    
        }    
        return "error";    
    }    
}  

然后,在webapp目录下创建&修改两个文件-index.jsp&welcome.jsp,内容如下
index.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" %>  
  
<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>  

main文件夹下创建一个resources文件夹,内部添加一个struts.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?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.test.s2001.action.LoginAction">  
            <result name="success">welcome.jsp</result>  
            <result name="error">index.jsp</result>  
        </action>  
    </package>  
</struts>  

在配置tomcat就能正常启动

0x03 OGNL表达式

OGNL 是 Object-Grap Navagation Language 的缩写,它是一种功能强大的表达式语言,通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去读取对象的属性。

OGNL三要素

表达式:
表达式是整个 OGNL 的核心内容,所有的 OGNL 操作都是针对表达式解析后进行的。通过表达式来告诉 OGNL 操作到底要干些什么。因此,表达式其实是一个带有语法含义的字符串,整个字符串将规定操作的类型和内容。OGNL 表达式支持大量的表达式,如“链式访问对象”、表达式计算、甚至还支持 lambda 表达式。
Root 对象:
OGNL 的 Root 对象可以理解为 OGNL 的操作对象。当我们指定了一个表达式的时候,我们需要指定这个表达式针对是哪个具体的对象。而这个具体的对象就是 Root 对象,这就意味着,如果有一个 OGNL 表达式,那么我们就需要针对 Root 对象来进行 OGNL 表达式的计算并且返回结果
上下文环境:
有个 Root 对象和表达式,我们就可以使用 OGNL 进行简单的操作,如对 Root 对象的赋值与取值操作。但是,实际上在 OGNL 的内部,所有的操作都会在一个特定的数据环境中运行。这个数据环境就是上下文环境(Context)。OGNL 的上下文环境是一个 Map 结构,称之为OrgnlContext。Root 对象也会被添加到上下文环境当中去。
说白了上下文就是一个 MAP 结构,它实现了java.utils.Map的接口。

OGNL的基础使用

导入 pom.xml

1
2
3
4
5
<dependency>  
	<groupId>ognl</groupId>  
	<artifactId>ognl</artifactId>  
	<version>3.1.19</version>  
</dependency>  

先创建两个实体类
Address.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
package com.test.s201.pojo;    
    
public class Address {    
    private String port;    
    private String address;    
	public Address(String port, String address) {    
	    this.port = port;    
	    this.address = address;    
	}    
    
	public Address() {    
	}  
    public String getPort() {    
        return port;    
    }    
    
    public void setPort(String port) {    
        this.port = port;    
    }    
    
    public String getAddress() {    
        return address;    
    }    
    
    public void setAddress(String address) {    
        this.address = address;    
    }    
}  

User.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
36
37
38
39
package com.test.s201.pojo;    
    
public class User {    
    private String name;    
    private int age;    
    private Address address;    
    
    public User(int age, String name) {    
        this.age = age;    
        this.name = name;    
    }    
    
    public User() {    
    }    
    
    public String getName() {    
        return name;    
    }    
    
    public void setName(String name) {    
        this.name = name;    
    }    
    
    public int getAge() {    
        return age;    
    }    
    
    public void setAge(int age) {    
        this.age = age;    
    }    
    
    public Address getAddress() {    
        return address;    
    }    
    
    public void setAddress(Address address) {    
        this.address = address;    
    }    
}  

OGNL使用getValue()方法来获取对象,并且访问对象当中的值。

对Root对象的访问

OGNL 使用的是一种链式风格进行对象的访问
所谓的链式风格,则是累死 StringBuffer 的 append 方法的写法

1
2
StringBuffer buffer = new StringBuffer();    
buffer.append('a').append('b').append('c').append('d');  

VisitRoot.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.test.s201;    
    
import com.test.s201.pojo.Address;    
import com.test.s201.pojo.User;    
import ognl.Ognl;    
    
public class VisitRoot {    
    public static void main(String[] args) throws Exception {    
        User user = new User("Huahua",18);    
        Address address = new Address("1001","翻斗花园");    
        user.setAddress(address);    
        System.out.println(Ognl.getValue("name",user));    
        System.out.println(Ognl.getValue("name.length()",user));    
        System.out.println(Ognl.getValue("address",user));    
        System.out.println(Ognl.getValue("address.port",user));    
    }    
}  

对上下文对象的访问

使用 OGNL 的时候如果不设置上下文对象,系统会自动创建一个上下文对象,如果传入的参数当中包含了上下文对象则会使用传入的上下文对象
当访问上下文环境当中的参数时候,需要在表达式前面加上"#",表示了与访问 Root 对象的区别
VisitContext.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.test.s201;    
    
import com.test.s201.pojo.Address;    
import com.test.s201.pojo.User;    
import ognl.Ognl;    
    
import java.util.HashMap;    
import java.util.Map;    
    
public class VisitContext {    
    public static void main(String[] args) throws Exception {    
        User user = new User("Huahua",18);    
        Address address = new Address("1001","翻斗花园");    
        user.setAddress(address);    
        Map<String,Object> context = new HashMap<String,Object>();    
        context.put("init","hello");    
        context.put("user",user);    
        System.out.println(Ognl.getValue("init",context,user));    
        System.out.println(Ognl.getValue("#user.name",context,user));    
        System.out.println(Ognl.getValue("name",context,user));    
    }    
}  

对静态变量与静态方法的访问

在 OGNL 表达式当中也可以访问静态变量或者调用静态方法,格式如@[class]@[field/method()]

创建对象

OGNL 支持直接使用表达式来创建对象。主要有三种情况
构造 List 对象
构造 Map 对象
构造任意对象

1
2
3
4
5
6
7
public class CreateClass {    
    public static void main(String[] args) throws Exception{    
        System.out.println(Ognl.getValue("#{'key1':'value1'}", null));   
        System.out.println(Ognl.getValue("{'key1','value1'}", null));    
        System.out.println(Ognl.getValue("new com.test.s201.pojo.User()", null));    
    }    
}  

弹计算器的 EXP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package com.test.s201;    
    
import ognl.Ognl;    
import ognl.OgnlException;    
    
public class EvilCalc {    
    public static void main(String[] args) throws OgnlException {    
        Ognl.getValue("new java.lang.ProcessBuilder(new java.lang.String[]{\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"}).start()", null);    
    }    
}  

0x04 OGNL表达式总结

表达式功能操作清单

 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
36
1. 基本对象树的访问  
对象树的访问就是通过使用点号将对象的引用串联起来进行。  
例如:xxxx,xxxx.xxxx,xxxx. xxxx. xxxx. xxxx. xxxx  
  
2. 对容器变量的访问  
对容器变量的访问,通过#符号加上表达式进行。  
例如:#xxxx,#xxxx. xxxx,#xxxx.xxxxx. xxxx. xxxx. xxxx  
  
3. 使用操作符号  
OGNL表达式中能使用的操作符基本跟Java里的操作符一样,除了能使用 +, -, *, /, ++, --, ==, !=, = 等操作符之外,还能使用 mod, in, not in等。  
  
4. 容器、数组、对象  
OGNL支持对数组和ArrayList等容器的顺序访问:例如:group.users[0]  
同时,OGNL支持对Map的按键值查找:  
例如:#session['mySessionPropKey']  
不仅如此,OGNL还支持容器的构造的表达式:  
例如:{"green", "red", "blue"}构造一个List,#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}构造一个Map  
你也可以通过任意类对象的构造函数进行对象新建  
例如:new Java.net.URL("xxxxxx/")  
  
5. 对静态方法或变量的访问  
要引用类的静态方法和字段,他们的表达方式是一样的@class@member或者@class@method(args):  
  
6. 方法调用  
直接通过类似Java的方法调用方式进行,你甚至可以传递参数:  
例如:user.getName(),group.users.size(),group.containsUser(#requestUser)  
  
7. 投影和选择  
OGNL支持类似数据库中的投影(projection) 和选择(selection)。  
投影就是选出集合中每个元素的相同属性组成新的集合,类似于关系数据库的字段操作。投影操作语法为 collection.{XXX},其中XXX 是这个集合中每个元素的公共属性。  
例如:group.userList.{username}将获得某个group中的所有user的name的列表。  
选择就是过滤满足selection 条件的集合元素,类似于关系数据库的纪录操作。选择操作的语法为:collection.{X YYY},其中X 是一个选择操作符,后面则是选择用的逻辑表达式。而选择操作符有三种:  
? 选择满足条件的所有元素  
^ 选择满足条件的第一个元素  
$ 选择满足条件的最后一个元素  
例如:group.userList.{? #txxx.xxx != null}将获得某个group中user的name不为空的user的  
0%