GabrielBBaldez/spring-taint
GitHub: GabrielBBaldez/spring-taint
基于 Tai-e 的 Spring Boot 过程间污点分析工具,专门检测 SonarQube 等传统工具无法覆盖的跨层、跨服务数据流注入漏洞。
Stars: 0 | Forks: 0
# Spring Taint Analyzer
[](https://github.com/GabrielBBaldez/spring-taint/actions/workflows/ci.yml)
[](https://github.com/GabrielBBaldez/spring-taint/releases)
[](LICENSE)
[](#building)
检测跨 **7 个框架**的 **12 种漏洞类别**,包括跨层、
响应式、跨服务和跨请求的存储型注入——**37 个漏洞基准案例中检测出 36 个,且 0 误报**(差一点缺失层捕获了最后一个,并标记了*已尝试但不正确*的过滤)。以 CLI、独立 jar 包、
Docker 镜像和带有 SARIF 2.1 输出的 GitHub Action 形式发布。
## 问题所在
考虑以下看似无害的 Spring Boot 代码:
```
// Controller
@GetMapping("/users")
public List
search(@RequestParam String name) {
return userService.search(name);
}
// Service
public List search(String name) {
String filtered = nameFilter(name); // looks like sanitization, but isn't
return userRepo.findByName(filtered);
}
// Repository
public List findByName(String name) {
return jdbc.query(
"SELECT * FROM users WHERE name = '" + name + "'", // 🚨 SQL Injection
mapper
);
}
```
该值来自 `@RequestParam`,穿过 service 和 repository 层,未经任何过滤就到达了 SQL 查询。像 `name = ' OR '1'='1` 这样简单的 payload 就会暴露整个表。
**SonarQube 无法检测到此路径。** 它只会标记 sink 和 source 在同一方法中的情况。真正的漏洞存在于跨多个层的流转中——而这正是本项目所致力于解决的问题。
## 什么是污点分析
污点分析使用三个概念来追踪不受信任数据在系统中的流转:
```
[SOURCE] ──► data flow ──► [SANITIZER?] ──► [SINK]
│
if absent → alert
```
- **Source** —— 外部数据进入的地方:`@RequestParam`、`@RequestBody`、`@KafkaListener`
- **Sanitizer** —— 负责净化数据的机制:`HtmlUtils.htmlEscape()`、参数化查询、`@Valid`
- **Sink** —— 危险数据被消费的地方:`JdbcTemplate.execute()`、`Runtime.exec()`、`response.write()`
如果数据从 source 流向 sink **且未经过 sanitizer** → 潜在漏洞。
该分析是**过程间**的:它跨方法、类和抽象层追踪数据——而不仅仅是在单个函数内。
## 定位:SonarQube 的补充
本项目**不**取代 SonarQube。它们服务于不同的目的:
| 工具 | 目的 | 过程间污点分析 |
|---|---|---|
| SonarQube | 通用质量 + bug + 简单漏洞 | ❌ |
| Semgrep OSS | 静态代码模式 | ❌ |
| Semgrep Pro | 过程间污点分析 | ✅ — 但是**收费** |
| Checkmarx / Veracode | 完整的企业级 SAST | ✅ — 但是**昂贵** |
| **Spring Taint Analyzer** | 针对 Spring Boot 的过程间污点分析 | ✅ — **免费** |
实际应用中的不同之处——依赖于真正的过程间污点分析的 Spring 特定功能:
| 功能 | Spring Taint | SonarQube (免费) | Semgrep OSS |
|---|:---:|:---:|:---:|
| 过程间污点分析(跨方法/层) | ✅ | ❌ | ❌ |
| `@KafkaListener` / `@FeignClient` 作为 source | ✅ | ❌ | ❌ |
| `MultipartFile` / `@MatrixVariable` 作为 source | ✅ | ❌ | ❌ |
| 条件 sanitizer | ✅ | ❌ | ❌ |
| 跨请求存储型注入 | ✅ | ❌ | ❌ |
| WebFlux / Reactor (`Mono` / `Flux`) | ✅ | ❌ | ❌ |
| JPQL / 模板 / JNDI / XXE 注入 | ✅ | ❌ | ❌ |
| Spring Security & `application.yml` 配置错误 | ✅ | ❌ | ❌ |
| 差点缺失 sanitizer 检测(错误/不足的过滤) | ✅ | ❌ | ❌ |
| 自动修复 —— 应用修复(参数化查询 / 输出转义) | ✅ | ❌ | ❌ |
| 每个发现项的置信度评分 | ✅ | ❌ | ❌ |
| 用于拉取请求的 Diff 模式 + 基线 | ✅ | ❌ | 部分 |
| SARIF 2.1 输出 | ✅ | ✅ | ✅ |
| 免费 / 开源 | ✅ | ✅ | ✅ |
在 CI pipeline 中的预期用途:
```
- sonarqube scan # general quality, code smells, coverage
- spring-taint scan # deep data-flow vulnerabilities
```
**一句话价值主张:** 你已经在使用 SonarQube——而本项目能检测到它所看不见的内容。
## 架构
基于 **[Tai-e](https://github.com/pascal-lab/Tai-e)**(南京大学,ISSTA 2023)构建,这是一个现代的 Java 静态分析框架。Tai-e 解决了其中的难点——调用图构建、上下文敏感的指针分析以及过程间 **IFDS** 污点传播。我们的工作则是建立在其之上的 Spring 层。
```
Spring Boot project
│ compile (Maven / Gradle)
▼
Bytecode (.class / JAR) ← analysis runs here, not on source
│
▼
Tai-e: Call Graph + Pointer Analysis
│
▼
IFDS Taint Propagation
│
▼
Spring Source/Sink Config ← our differentiator
(@RequestParam, @KafkaListener,
JdbcTemplate, Runtime.exec…)
│
▼
SARIF 2.1 report
(terminal / GitHub / GitLab / VS Code)
```
直接在 bytecode(而非源代码)上运行,能提供精确的继承/泛型解析,无需源码即可分析第三方依赖,并独立于任何 IDE 或构建系统。
## 项目结构
```
spring-taint/
├── config/
│ └── spring-taint.yml # default Spring sources/sinks/sanitizers (Tai-e format)
├── docs/
│ └── design/ # technical scope & design notes
├── spring-taint-engine/ # analyzer: CLI, config loader, Tai-e adapter, SARIF reporter
└── spring-taint-benchmark/ # intentionally vulnerable Spring Boot cases + ground truth
```
## 基准测试
就像 FlowDroid 的 DroidBench 一样,本仓库内置了一个基准测试,包含故意设计为有漏洞(和安全)的 Spring Boot 案例。每一个对外宣称的检测功能在发布前都会针对它进行验证。
该基准测试包含 **40 个案例(37 个有漏洞,3 个安全)**,覆盖 SQL 和 JPQL 注入
(直接的、穿透 service 层的、四层调用、通过 Kafka 和 RabbitMQ、响应式 R2DBC)、反射型、
条件 sanitizer 和 **跨请求存储型** XSS、SSRF、SpEL、JNDI、XXE、
模板注入 (SSTI)、日志注入、路径遍历、命令注入和
开放重定向——其 source 来自 Spring (`@RequestParam`、`@PathVariable`、
`@RequestBody`、`@RequestHeader`、`@MatrixVariable`、`MultipartFile`)、
`@KafkaListener`、`@RabbitListener`、JAX-RS (`@QueryParam`)、`@Repository` 读取、
**`@FeignClient` 结果、`@Scheduled` 任务和 `@Transactional` 先写后读**,
此外还有通过 `Optional` / `CompletableFuture` 包装器流转的污点。基准真值见
[`expected.yml`](spring-taint-benchmark/expected.yml)。
当前引擎结果:**仅靠污点引擎就检测出了 37 个漏洞案例中的 36 个,
** 在 3 个安全案例上 **0 误报**;差点缺失层 (`--src`) 捕获了剩余的上下文错误的流 (37) 并解释了其余部分。完整表格:[基准测试 README](spring-taint-benchmark/README.md)。
每条规则参考:[docs/rules.md](docs/rules.md)。
正面案例衡量**召回率**;安全案例衡量**精确率**。
除了合成基准测试外,分析器还在真实的 OSS 应用上运行,覆盖
Spring Boot 3 (`jakarta`) 和 Spring Boot 2 (`javax`):
- **spring-petclinic**(干净,Boot 4.0)—— 正确接入(9 个入口点,12 个
source)并报告了 **0 误报**,外加一个项目自身标记为生产环境不安全的合法配置发现。
- **spring-petclinic-rest**(干净,更大规模 —— 约 126 个类,真实的 `JdbcTemplate` 用法)
—— 在大规模下 **0 误报**(31 个入口点,46 个 source;分析耗时约 0.2 秒)。
- **sql-injection-web**(有漏洞)—— 以 99% 的置信度发现了**跨层** SQL 注入
(controller → repository,两个文件)并生成了修复程序。
- **Contrast vulnerable-spring-boot-application**(有漏洞,Boot 2 / `javax`,通过 `@RequestParam Map` 传值)
—— 以 99% 的置信度发现了跨层 SQL 注入。
详见 [docs/validation.md](docs/validation.md)。
## 其检测类别对应的真实 CVE
基准测试证明了在合成案例上的召回率;这些是野外**相同漏洞类别的公开 CVE**。
分析器对应用程序 bytecode 进行推理,并报告每个流转的过程间 source-to-sink 形式——因此,当易受攻击的调用位于被分析的代码中,而不仅仅是在第三方库内部时,它就能捕获这些模式。
| 类别 | 检测器 | 具代表性的公开 CVE | 数据流 |
|---|---|---|---|
| SQL 注入 (CWE-89) | `sql-injection` | [CVE-2020-5427 / CVE-2020-5428](https://spring.io/security/cve-2020-5428/) — Spring Cloud Data Flow / Task | 请求可控的 `sort` 列被拼接到 task-execution 查询中 |
| SQL 注入 (CWE-89) | `sql-injection` | [CVE-2016-6652](https://spring.io/security/cve-2016-6652/) — Spring Data JPA | 来自请求的 `Sort` 值到达生成的 SQL(盲注 SQLi) |
| SQL 注入 (CWE-89) | `sql-injection` | [CVE-2024-54762](https://nvd.nist.gov/vuln/detail/CVE-2024-54762) — RuoYi (Spring Boot admin) | 已认证的请求参数未经过滤到达查询 |
| SpEL 注入 (CWE-917) | `spel-injection` | [CVE-2018-1273](https://nvd.nist.gov/vuln/detail/CVE-2018-1273) — Spring Data Commons | 构造的请求 payload 属性路径被作为 SpEL 表达式执行 |
每一个都是请求值跨方法流入中间没有 sanitizer 的 sink——这正是此工具追踪的形态。
(对于易受攻击的调用位于框架而非应用程序中的 CVE,只有当从被分析的代码到达该调用时,分析器才会报告它。)
## 各阶段范围
- **阶段 1 —— Spring MVC (MVP):** SQL 注入、XSS、路径遍历、命令注入、SSRF、SpEL 注入、开放重定向。Source:`@RequestParam`、`@PathVariable`、`@RequestBody`、`@RequestHeader`、`@CookieValue`、`@ModelAttribute`、servlet API。退出标准:检测到每个基准案例,零漏报且精确率 > 80%。
- **阶段 2 —— 现有 OSS 工具留下的空白(已完成):** `@KafkaListener` 和 `@RabbitListener` 作为 source,条件 sanitizer,自定义方法 sanitizer,跨请求存储型注入,WebFlux / 异步(`Mono`/`Flux` 作为透明的污点包装器)。
- **阶段 3 —— 多框架与健壮性(已完成):** JAX-RS / Quarkus 和 Micronaut source;JNDI / XXE / 模板 / JPQL / 日志注入 sink;`@FeignClient`、`@Scheduled` 和 `@Transactional` source;配置和错误配置审计。
- **阶段 4 —— 路线图:** gRPC source、IntelliJ 插件,以及将镜像发布到 GHCR。
完整的技术范围位于 [`docs/design/spring-taint-scope.md`](docs/design/spring-taint-scope.md)。
## 用法
命令(可运行的 `java -jar …/spring-taint-all.jar` 形式见
[构建](#building);`scan` 需要通过 `--libs` 传入目标的依赖 classpath):
```
# 基础扫描
spring-taint scan target/classes --libs ""
# 自定义配置(合并到内置规则上)
spring-taint scan target/classes --libs "…" --config spring-taint.yml
# SARIF 输出(GitHub Advanced Security、GitLab SAST、VS Code)
spring-taint scan target/classes --libs "…" --output results.sarif
# 按严重性过滤,显示完整 trace
spring-taint scan target/classes --libs "…" --severity critical,high --verbose
# 仅显示与 base ref 相比改动过的文件的 findings(快速 PR 扫描)
spring-taint scan target/classes --libs "…" --diff origin/main
# Near-miss 提示 + 建议的 parameterized-query 修复(需要 sources)
spring-taint scan target/classes --libs "…" --src src/main/java --suggest-fixes
# 将高可信度的修复应用到 source
spring-taint scan target/classes --libs "…" --src src/main/java --fix
# 在遗留 codebase 上采用:记录今天的 findings,然后仅在出现 NEW findings 时失败
spring-taint scan target/classes --libs "…" --baseline spring-taint-baseline.txt
```
示例输出(每个污点发现项都带有置信度评分):
```
[CRITICAL] sql-injection (confidence: 95%)
Source: UserController.java:28 - search() - tainted parameter
Flow: UserController.search() -> UserService.search() -> UserRepository.query()
Sink: UserRepository.java:27 - sink: query()
Sanitizer: none detected
```
## 可扩展性
团队可以按 Tai-e 的 YAML 格式添加自己的规则:
```
sources:
- { kind: call, method: "", index: result }
- { kind: param, method: "", index: 0 }
sinks:
- { method: "", index: 0 }
sanitizers:
- { method: "", index: 0 }
```
## 构建
需要 JDK 17+ 和 Maven。
```
mvn -q clean package # build engine + benchmark
mvn -q -pl spring-taint-benchmark package # compile the benchmark cases only
```
### 运行扫描
构建独立的 jar 包并扫描已编译的类。通过 `--libs` 传入目标的
依赖 classpath,以便像 `JdbcTemplate` 这样的框架类型能够解析
(内置了污点配置,因此 `--config` 是可选的):
```
java -jar spring-taint-engine/target/spring-taint-all.jar \
scan target/classes --libs "$(... your dependency classpath ...)" \
--output results.sarif
```
自定义的 `--config` 会**合并**到内置规则上(使用 `--no-default-config`
来替换它们)。
### 密钥、配置与错误配置扫描
三种基于模式的扫描(任意 JDK,无需污点引擎)补充了污点分析:
```
# bytecode 中的硬编码 secrets — 以 secret 命名的常量、已知 key 格式
# (AWS、GitHub, …)以及 @Value 默认值
java -jar …/spring-taint-all.jar secrets target/classes
# application*.yml / .properties 中的不安全设置 — 硬编码 secrets、
# 已禁用的 TLS、已排除的 Security auto-config、Actuator "*"、H2 console
java -jar …/spring-taint-all.jar config src/main/resources
# bytecode 中不安全的 Spring 代码 — csrf()/frameOptions().disable()、
# @CrossOrigin("*")、不安全的 cookies、被记录的敏感数据
java -jar …/spring-taint-all.jar misconfig target/classes
```
## GitHub Action
在 CI 中运行分析器并将发现项上传到 GitHub 代码扫描。该 Action
作为 Docker 容器在 JDK 17 上运行;为其提供已编译的类和
依赖 classpath:
```
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { distribution: temurin, java-version: '17' }
- run: mvn -B -ntp package -DskipTests
- id: cp
run: echo "value=$(mvn -q dependency:build-classpath -Dmdep.outputFilterFile=/dev/stdout)" >> "$GITHUB_OUTPUT"
- name: Spring Taint Analysis
uses: GabrielBBaldez/spring-taint@main
with:
path: target/classes
libs: ${{ steps.cp.outputs.value }}
output: results.sarif
severity: critical,high
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: results.sarif
```
所有输入项详见 [`action.yml`](action.yml)。本仓库还会在每次推送时扫描自身的
基准测试 —— 详见 [`.github/workflows/ci.yml`](.github/workflows/ci.yml)。
### 拉取请求审查
[`examples/pr-security.yml`](examples/pr-security.yml) 是一个为你自己的项目准备的直接复制粘贴 workflow:
在每次 PR 时,它只扫描更改的代码 (`--diff`),上传
SARIF,以便发现**内联在 PR 中显示**(GitHub 代码扫描),并将
**建议的修复**(参数化查询 / 输出转义)作为 PR 评论发布。将它与
`--baseline` 配对,以仅对新引入的问题进行拦截。
## Dashboard
一个 Web 控制台(React + Vite + TypeScript)将 SARIF 输出可视化:严重程度
明细、按规则分类的发现项,以及每个发现项的完整 **source → sink 污点流**。
拖放一个 `.sarif` 文件即可加载你自己的报告。详见
[`dashboard/`](dashboard/)。
```
cd dashboard && npm install && npm run dev # → http://localhost:4321
```
## 状态
- [x] 范围与定位
- [x] 引擎选择
- [x] 与竞品的空白映射
- [x] 项目脚手架(Maven 多模块、CLI 骨架、配置加载器、SARIF 模型)
- [x] 初始基准测试:SQL 注入(直接 / 穿透 service 层 / 通过 Kafka / 安全)、反射型 XSS、路径遍历、命令注入
- [x] 引擎:在基准测试上端到端打通 Tai-e IFDS
- [x] Spring source 层:注解 → Tai-e param-source 生成
- [x] 带有 SARIF 输出的功能性 CLI
- [x] 当前基准测试上的精确率/召回率 —— **检测出 30/30 个漏洞案例,0 误报**,覆盖 SQL / JPQL 注入(直接 / 穿透 service 层 / 四层 / 通过 Kafka / 响应式 R2DBC)、反射型 / 条件型 / **跨请求存储型** XSS、SSRF、SpEL、JNDI、XXE、模板注入 (SSTI)、日志注入、路径遍历、命令注入、开放重定向;多框架 source(Spring MVC/WebFlux、Kafka、JAX-RS/Quarkus、Micronaut、`@Repository` 读取、`@FeignClient`、`@Scheduled`)
- [x] 阶段 2 差异化:`@KafkaListener` source、条件 sanitizer、存储型 / 二阶注入
- [x] GitHub Action (Docker) + 独立 jar 包 + CI workflow
- [x] 用于 SARIF 报告的 Web Dashboard (React + Vite)
- [x] 硬编码密钥扫描器(`secrets` 命令);可合并的 `--config`
- [x] 健壮性检查:JNDI / XXE / 日志 / 模板 / JPQL sink,文件上传和 `@MatrixVariable` source,`Optional` / `CompletableFuture` 污点转移,框架内部 sink 过滤
- [x] 配置与错误配置审计:`config`(不安全的 `application.yml`/`.properties`)和 `misconfig`(CSRF/点击劫持被禁用、CORS `*`、不安全的 cookie、记录敏感数据)
- [x] 采用度:每个发现项的置信度评分(控制台 + SARIF),用于快速拉取请求扫描的 `scan --diff [`,内联 `// spring-taint: suppress` 注释(`--src` / `suppressions`),以及用于捕获拼写错误的自定义规则的 `validate-config`
- [x] 高级 source:`@FeignClient` 结果(跨服务),`@Scheduled` 任务作为入口点,以及 `@Transactional` 先写后读存储型注入
- [x] 差点缺失 sanitizer(`--src`):标记不足(去除引号)、黑名单、丢弃结果以及上下文错误的过滤 —— 即“我确信这是安全的”这类 bug
- [x] 自动修复(`--suggest-fixes` / `--fix`):将拼接的 SQL 查询重写为参数化查询;已完成端到端验证(应用修复后,基准测试的 SQL 发现项从 15 降至 1,且修补后的代码可以编译)
- [x] 扫描器的单元测试套件(18 个测试,带有回归覆盖);bytecode 扫描器(`secrets`/`misconfig`)读取任意 JDK 的类文件 (ASM 9.7)
- [x] 自动修复涵盖 XSS(包装在 `HtmlUtils.htmlEscape` 中)以及 SQL;采用基线模式(`--baseline`)以便在旧代码库中采用,并仅对新的发现项拦截 CI
## 已知限制
静态分析具有固有的局限性。对于本项目:
- **Java 反射**(`Class.forName()`、`Method.invoke()`)可能会打断流
- **Spring 动态代理**(AOP / CGLib)引入的间接性可能会破坏调用图
- **Entity / DTO 字段追踪** —— source 仅限 `String`(如果 `@Repository`/`@FeignClient` 返回的 DTO,其 getter 随后被读取,则不会被继续追踪),这是一种重精确率轻召回率的选择
- **复杂的 Lambda / 方法引用** —— 通过 Tai-e 实现部分覆盖
- **污点分析运行在 JDK 17 上** —— Tai-e 0.5.1 无法读取 JDK 21 的 bytecode
每个版本都明确记录了其局限性,以及执行这些限制的测试用例。
## 致谢
基于 [Tai-e](https://github.com/pascal-lab/Tai-e)(南京大学)构建,它
提供了调用图构建、指针分析和 IFDS 污点传播。
Tai-e 基于 LGPL-3.0 授权;本项目将其作为库依赖。
## 许可证
[MIT](LICENSE) © Gabriel Baldez。]标签:CISA项目, JS文件枚举, SAST, Spring Boot, 云安全监控, 后台面板检测, 域名枚举, 盲注攻击, 请求拦截, 静态分析