struts的历史和用途

Struts 是一个开源框架,用于扩展 Java Servlet API 并使用模型、视图、控制器(MVC) 体系结构。通过此框架,您可以基于各种标准技术(如 JSP 页、JavaBeans、资源包和 XML)创建可维护、可扩展并且灵活的 Web 应用程序。

使用 Struts 时,此框架将为您提供一个控制器 Servlet (ActionServlet),它在 IDE 中包含的 Struts 库中定义,并自动在 web.xml 部署描述符中注册

workflow

该控制器 Servlet 使用 struts-config.xml 文件将传入请求映射到 Struts Action 对象,并实例化与暂时存储窗体数据的操作相关联的任何 ActionForm 对象。Action 对象在使用窗体 Bean 中存储的任何数据的同时,使用其 execute 方法处理请求。一旦 Action 对象处理了请求,它就将存储任何新数据(即,存储在窗体 Bean 或单独的结果 Bean 中),并将结果转发到相应的视图中。

Struts 是 Apache 软件基金会(Apache Software Foundation)资助的一个为开发基于MVC模式应用架构的开源框架,是利用Java Servlet和JSP、XML等方面的技术来实现构建Web应用的一项非常有用的技术,它也是基于MVC模式的Web应用最经典框架。

Struts框架的主要架构设计和开发者是Craig R.McClanahan。Struts 是目前Java Web MVC框架中不争的王者。经过长达五年的发展,Struts已经逐渐成长为一个稳定、成熟的框架,并且占有了MVC框架中最大的市场份额。

Struts把Servlet、JSP、自定义标签和信息资源(message resources)整合到一个统一的框架中,主要由一系列的框架类、辅助类和定制的JSP标记库构成。开发人员利用其进行开发时不用再自己编码实现全套MVC模式,极大的节省了时间。

ognl表达式

OGNL是Object-Graph Navigation Language的缩写,它是一种功能强大的表达式语言,通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。
功能:获取和设置java对象的属性、列表映射和选择等。

对于开发者来说,使用 OGNL,可以用简洁的语法来完成对 java 对象的导航。通常来说:通过一个 “路径” 来完成对象信息的导航,这个 “路径” 可以是到 java bean 的某个属性,或者集合中的某个索引的对象,等等,而不是直接使用 get 或者 set 方法来完成。

OGNL 三要素

表达式

通过表达式告诉ognl做什么。表达式是带有语法含义的字符串。表达式支持:链式访问对象、表达式计算、Lambda表达式

Root对象

OGNL的操作对象。

上下文环境

所有操作需要在一个特定的数据环境中运行。比如对root对象赋值、取值

OGNL的使用

Maven:项目管理工具。通过pom.xml文件的配置获取jar包,不用手动的去添加jar包。maven项目是在java项目和web项目上裹了一层maven。maven的功能。 构建工程, 管理jar,编译代码,自动运行单元测试, 打包 ,生成报表,部署项目,生成web站点。

引入Maven

<dependency>
    <groupId>ognl</groupId>
    <artifactId>ognl</artifactId>
    <version>3.1.19</version>
</dependency>

示例类

sample.ognl.Address

@Data
public class Address {

    private String port;
    private String address;
    
    public Address(String port,String address) {
        this.port = port;
        this.address = address;
    }
}

OGNL基本语法

对root对象的访问

使用的是链式风格

User user = new User("test", 23);
Address address = new Address("330108", "杭州市滨江区");
user.setAddress(address);
System.out.println(Ognl.getValue("name", user));    // test
System.out.println(Ognl.getValue("name.length", user));        // 4
System.out.println(Ognl.getValue("address", user));        // Address(port=330108, address=杭州市滨江区)
System.out.println(Ognl.getValue("address.port", user));    // 110003

访问context中的参数

当访问上下文环境当中的参数时候,需要在表达式前面加上 ‘#’ ,表示了与访问 Root 对象的区别。

public static String demo2() throws OgnlException {
    User user = new User("test", 23);
    Address address = new Address("330108", "杭州市滨江区");
    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));    // hello
    System.out.println(Ognl.getValue("#user.name", context, user));    // test
    System.out.println(Ognl.getValue("name", context, user));    // test
    return "this is demo2 method";
}

访问静态变量

格式@[class]@[field/method()]

public static String ONE = "one";
// 对静态变量的访问(@[class]@[field/method()])
public static void demo3() throws OgnlException {
    Object object1 = Ognl.getValue("@sample.ognl.OgnlDemo@ONE", null);
    Object object2 = Ognl.getValue("@sample.ognl.OgnlDemo@demo2()", null);    // hello、test、test
    System.out.println(object1);    // one    
    System.out.println(object2);    // this is demo2 method
}

方法的调用

调用root对象或context对象当中的方法,可以使用.+方法的方式来调用。还可以传入参数。

赋值的时候,可以选择context当中的元素给root对象的name属性赋值。

User user = new User();
Map<String, Object> context = new HashMap<String, Object>();
context.put("name", "rcx");
context.put("password", "password");
System.out.println(Ognl.getValue("getName()", context, user));    // null
Ognl.getValue("setName(#name)", context, user);
System.out.println(Ognl.getValue("getName()", context, user));    // rcx

对数组和集合的访问

对数组按照数组下标的顺序进行访问。同样适用于集合的访问,对于Map支持用键进行访问

User user = new User();
Map<String, Object> context = new HashMap<String, Object>();
String[] strings  = {"aa", "bb"};
ArrayList<String> list = new ArrayList<String>();
list.add("aa");
list.add("bb");
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
context.put("list", list);
context.put("strings", strings);
context.put("map", map);
System.out.println(Ognl.getValue("#strings[0]", context, user));    // aa
System.out.println(Ognl.getValue("#list[0]", context, user));    // aa
System.out.println(Ognl.getValue("#list[0 + 1]", context, user));    // bb
System.out.println(Ognl.getValue("#map['key1']", context, user));    // value1
System.out.println(Ognl.getValue("#map['key' + '2']", context, user));     // value2

ognl表达式当中支持操作符的简单运算.比如:👇

2 + 4 // 整数相加(同时也支持减法、乘法、除法、取余 [% /mod]、)
"hell" + "lo" // 字符串相加
i++ // 递增、递减
i == j // 判断
var in list // 是否在容器当中

投影与选择

支持类似数据库当中的选择与投影功能

创建对象

  • 创建List对象。{},用,进行分割。{"aa","bb","cc"}
  • 创建Map对象。使用#{},用,进行分割键值对。#{"key1" : "value1", "key2" : "value2"}
  • 构造任意对象:直接使用已知的对象的构造方法进行构造
System.out.println(Ognl.getValue("#{'key1':'value1'}", null));    // {key1=value1}
System.out.println(Ognl.getValue("{'key1','value1'}", null));    // [key1, value1]
System.out.println(Ognl.getValue("new sample.ognl.User()", null));    
// User(name=null, age=0, address=null)

如何快速判断网站是否使用struts

URL中添加不存在路径

在URL的反斜杠部分添加网站不存在的路径,最好是随机字符串组成的较长路径,如果返回同样的页面,则大概率是Struts2框架。

如果返回的是404或者是报错,则大概率是spring框架

实验步骤

在最后右边反斜杠处添加一个不存在的路径 /xxxxxxxxxx/,如下所示:

http://127.0.0.1:9999/S2_016_war/barspace/xxxxxxxxx/login.do 返回与原 URL 相同页面,则是 Struts2 框架

http://127.0.0.1:9999/S2_016_war/barspace/xxxxxxxxx/login.do 返回与原 URL 异同页面,则是 Spring 框架

如果两个 URL 均报错、或者均正常,无法区分,那么继续在前一个反斜杠处添加一个不存在的路径

http://127.0.0.1:9999/S2_016_war/xxxxxxxxx/barspace/login.do

原理1

Struts2 站点的 URL 路径包括四部分组成:工程名 + namespace 命名空间 + action 名 + Struts2 扩展名,举个例子,对于如下 URL:

http://127.0.0.1:9999/S2_016_war/barspace/login.action

/S2-016-war / 部分是 war 包部署的工程名,也可以说是项目名、上下文等等,说法不一。

/barspace/ 部分是 Struts2 的命名空间 namespace。

/login 部分是 Struts2 的 action 名,指向具体处理请求的 Java 类。

.action 部分是 Struts2 的扩展名,也可以定义为.do、.dw 等等。

原理2

按照struts2框架规则,首先会在当前路径下找action名login,如果没有找到去上一层找。会一直向上一层找,直到找到应用程序的跟路径为止或找到应用程序为止。

但前端如果有nginx,则这种方法无效,因为nginx可能会配置一些特殊的URL转发。

URL添加/struts/domTT.css

在 URL 的 Web 应用根目录下添加 /struts/domTT.css,如果返回 css 代码,那么 99% 是 Struts2。

因为do m TT.css文件在网站源码文件中是找不到的,用磁盘搜索的方式搜索不到,因为这个文件在struts2的jar包中。

404、500响应码返回信息

输入一个不存在的路径,返回404页面,或者传入一些乱码字符,造成当前页面500响应码报错,抛出异常信息。

Struts2 常用的关键字有: no action mapped、struts2、namespace、defined for action 等

img

img

spring报错:含有whitelabel Error Page

img

看网站图标favicon.ico

Struts2没有常用的favicon.ico图标

Spring的是一片绿叶

struts,ognl漏洞利用

恶意攻击者通过构造特定数据带入ognl表达式可能被解析并执行,而ognl可以用来获取和设置java对象的属性,同时也可以对服务端对象进行修改,所以只要绕过Struts2的一些安全策略,恶意攻击者甚至可以执行系统命令进行系统攻击。

虽然Struts2出于安全考虑,在SecurityMemberAccess类中通过设置禁止静态方法访问及默认禁止执行静态方法来阻止代码执行。即上面提及的denyMethodExecution为true,MemberAccess为false。但这两个属性都可以被修改从而绕过安全限制执行任意命令。

ognl表达式注入

符号的区别

#符主要有三种用途:

  • 访问非根对象属性,即访问OGNL上下文和Action上下文,由于Struts2中值栈被视为根对象,所以访问其他非根对象时需要加#前缀,#相当于ActionContext.getContext()
  • 用于过滤和投影(projecting)集合,如books.{? #this.price<100}
  • 用于构造Map,如#{'foo1':'bar1', 'foo2':'bar2'}

%

%符的用途是在标志的属性为字符串类型时,告诉执行环境%{}里的是OGNL表达式并计算表达式的值。

$

$符的主要作用是在相关配置文件中引入OGNL表达式,让其在配置文件中也能解析OGNL表达式。(换句话说,$用于在配置文件中获取ValueStack的值用的)。

常见payload

获取context里面的变量值

 #user
 #user.name

使用runtime执行系统命令

@java.lang.Runtime@getRuntime().exec("calc")

使用processbuilder执行系统命令

(new java.lang.ProcessBuilder(new java.lang.String[]{"calc"})).start()

获取当前绝对路径

@java.lang.System@getProperty("user.dir")

e-mobole带回显

@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('whoami').getInputStream())

参考文章

https://netbeans.apache.org/kb/docs/web/quickstart-webapps-struts_zh_CN.html

https://jueee.github.io/2020/08/2020-08-15-Ognl%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%9A%84%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95/

https://cryin.github.io/blog/struts2-vulnerability-analysis-and-OGNL-research/

https://www.wangan.com/p/7fy7f42bcd36404c

评论