Java 案例中防守漏洞的识别与定位方法
"防守漏洞"在 Java 安全审计中通常指代码防护缺失或失效导致的可利用风险点,下面结合真实案例,系统讲解识别与定位方法。

漏洞识别的四大维度
输入验证缺失(最常见)
案例:SQL 注入
// 漏洞代码
public User getUser(String username) {
String sql = "SELECT * FROM users WHERE name = '" + username + "'";
return jdbcTemplate.queryForObject(sql, User.class);
}
识别要点:
- 字符串拼接 SQL / HQL / JPQL
- 未使用
PreparedStatement或 MyBatis - 动态表名/排序字段未做白名单
定位方法:全局搜索 + "select、Statement.execute、createQuery(、 在 mapper XML 中的使用。
认证与授权缺陷
案例:越权访问(IDOR)
// 漏洞代码:只校验登录,未校验资源归属
@GetMapping("/order/{id}")
public Order getOrder(@PathVariable Long id, HttpSession session) {
// 缺少:校验 order.userId == session.userId
return orderService.findById(id);
}
识别要点:
- 敏感接口调用
@PreAuthorize/checkPermission是否缺失 - 资源类接口是否做了对象级权限校验(不只是角色)
- 越权检测:换用其他用户 token 访问他人资源
定位方法:Spring Security 配置审查 + 逐接口核对 @PreAuthorize 注解覆盖度。
反序列化与表达式注入
案例:Fastjson 反序列化
// 漏洞代码
String json = request.getParameter("data");
Object obj = JSON.parse(json); // 未指定 safeMode / AutoType 黑名单
识别要点:
ObjectInputStream.readObject()未做类白名单- Fastjson
parse/parseObject未开启 safeMode - SpEL/OGNL 中
parseExpression(userInput) - Jackson 开启
enableDefaultTyping()
定位方法:搜索 readObject、JSON.parse、SpelExpressionParser、enableDefaultTyping。
敏感信息与加密误用
案例:硬编码密钥 + 弱加密
// 漏洞代码
private static final String KEY = "1234567890abcdef"; // 硬编码
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); // 弱算法
识别要点:
- 硬编码 secret/key/password
- ECB 模式、DES/MD5/SHA1
Random用于安全场景(应用SecureRandom)- 日志打印敏感字段(身份证、密码、token)
定位方法:静态扫描 getInstance("DES、new Random(、log.info.*password。
实战定位流程(五步法)
① 资产梳理 → ② 入口枚举 → ③ 数据流追踪 → ④ 防护点核查 → ⑤ 动态验证
① 资产梳理
- 识别 Controller 层入口(
@RequestMapping、@GetMapping…) - 识别第三方组件版本(
pom.xml/build.gradle),对照 CVE(如 Log4Shell、Fastjson)
② 入口枚举
列出所有 外部可控输入点:
- HTTP 参数、Header、Cookie、Body
- 文件上传、URL 重定向参数
- 反序列化入口、RPC 接口
③ 数据流追踪(关键)
从 Source(入口) 追踪到 Sink(危险操作):
| Source | Sink |
|---|---|
request.getParameter |
Statement.execute |
@RequestParam |
Runtime.exec |
| 上传文件名 | new File(path) |
| JSON body | readObject |
工具辅助:
- IDEA 内置 Data Flow Analysis(右键 → Analyze Data Flow to Here)
- FindBugs/SpotBugs 插件、Semgrep、CodeQL
- Fortify / SonarQube 做 taint analysis
④ 防护点核查
对每个 Sink 确认是否有防护:
- SQL → 参数化?
- 命令执行 → 白名单?
- 文件路径 → 规范化校验(
getCanonicalPath().startsWith(base))? - 输出 → 编码/转义?
⑤ 动态验证
- 构造 payload 实际请求
- 关注堆栈、错误回显判断后端行为
- 使用 Burp/Postman 测试越权、注入
典型漏洞速查表
| 漏洞类型 | 危险代码特征 | 定位关键词 |
|---|---|---|
| SQL 注入 | 字符串拼接 SQL | + "WHERE、execute(、 |
| XSS | 直接输出用户输入 | response.getWriter().print、视图未转义 |
| 命令注入 | 运行时执行 | Runtime.exec、ProcessBuilder |
| 路径遍历 | 拼接文件路径 | new File(base + name)、 |
| SSRF | 请求用户提供的 URL | new URL(userInput)、RestTemplate |
| XXE | XML 解析未禁外部实体 | DocumentBuilderFactory、SAXParser |
| 反序列化 | 不可信数据反序列化 | readObject、JSON.parse |
| 越权 | 缺对象级校验 | 无 @PreAuthorize、无归属判断 |
| 硬编码 | 密钥写在代码里 | private static final String KEY |
| 弱随机 | 用 Random 生成 token | new Random( |
工具组合建议
- 静态扫描:SonarQube(基线)+ Semgrep(自定义规则)+ CodeQL(深度数据流)
- 依赖检测:
mvn dependency-check/ OWASP Dependency-Check - 运行时:IAST(如 Contrast)、RASP 观察异常调用链
- 人工复核:重点审查 Sink 点上下文,排除误报(如已做白名单)
一个完整案例演示
@PostMapping("/download")
public void download(@RequestParam String filename, HttpServletResponse resp) {
// Source: filename (外部可控)
File file = new File("/data/files/" + filename); // Sink: 路径拼接
// 缺失防护:未校验 ../
Files.copy(file.toPath(), resp.getOutputStream());
}
定位步骤:
- 入口:
@RequestParam filename - 数据流:filename →
new File(...)→Files.copy - 防护缺失:无
getCanonicalPath校验、无白名单 - 验证:
filename=../../etc/passwd读取系统文件 - 修复:
Path base = Paths.get("/data/files").toRealPath(); Path target = base.resolve(filename).normalize(); if (!target.startsWith(base)) throw new SecurityException("非法路径");
识别 = 找 Source 和 Sink;定位 = 追踪数据流 + 核查防护点;确认 = 动态验证。 核心思路是「每个外部输入到敏感操作的通路上,必须存在有效防护」,任何断链处就是潜在漏洞点。