在软件安全领域,Java代码审计是发现和预防漏洞的关键环节。本文将系统性地介绍一套从目标选择、工具使用到实战验证的全链路Java代码审计工作流,旨在为安全从业者提供一套可落地的实践指南。
1. 攻防与审计知识
进行有效的代码审计,首先需要建立清晰的攻防知识模型,明确审计对象的核心构成与风险点。
1.1 Source与鉴权
- Source:指代码中接收外部数据传入的入口点,如HTTP请求参数、文件上传、数据库查询结果等。识别所有Source是审计的起点。
- 鉴权:需要区分前台接口(如用户登录、公开查询)和后台接口(如数据增删改查、系统管理)。审计时需重点关注鉴权逻辑是否健全,是否存在绕过可能。
- 鉴权配置方式:鉴权逻辑可能存在于Spring Security、Shiro等安全框架的配置文件中,也可能以
Filter、Interceptor或注解(如@PreAuthorize)的形式嵌入在Java代码里。
- 绕过鉴权:大部分鉴权绕过漏洞源于业务逻辑缺陷,而非配置错误,因此需要深入理解业务代码流。
1.2 快速找到前台接口
在大型项目中,快速定位未授权或低权限的前台接口是提高审计效率的关键。可以通过以下方式:
- 代码搜索:在IDE中全局搜索关键词如
login、public、@GetMapping、@PostMapping等。

- 配置文件分析:检查
web.xml、spring-security.xml或Shiro配置,其中通常会定义匿名访问(anon)的路径。
// 示例:Shiro配置中的匿名访问路径
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
- 扫描接口与默认路径:项目可能暴露Swagger文档(
/v3/api-docs)、Actuator监控端点(/actuator/health)、Druid监控(/druid/)等,这些可能是未受保护的入口。
public static final String[] DEFAULT_INCLUDE_PATHS = new String[]{
"/v2/api-docs", "/swagger-resources/**", "/swagger-ui.html",
"/actuator/**", "/druid/**"
};

1.3 常见Java特有漏洞
Java生态中存在一些特有的高危漏洞类型,审计时应重点关注:
- 反序列化漏洞:涉及原生Java序列化、FastJson、Jackson、SnakeYaml、XStream等库。
- 表达式注入:包括SpEL (Spring)、OGNL (Struts2)、MVEL、JEXL、EL (JSP)等。
- JNDI注入与LDAP攻击:通常与反序列化、RMI等服务结合导致RCE。
- SSTI (服务器端模板注入):如Thymeleaf、FreeMarker、Velocity等模板引擎的滥用。
- JDBC Attack:通过控制JDBC连接URL,利用特定数据库特性(如H2、MySQL)执行代码。
- 类加载与反射滥用:通过
Class.forName().newInstance()或Method.invoke()执行任意代码。
- 实战扩展场景:还需考虑不出网利用、内存马注入、高版本JDK(如JDK17)下的利用限制以及安全产品(WAF、RASP)的绕过手法。

2. 目标选择
选择合适的目标是成功审计的第一步,需从难度和价值两个维度综合评估。
2.1 评估维度
- 闭源 vs 开源:闭源项目因难以直接获取代码,审计难度相对较大。
- 小众 vs 知名:小众项目研究的人少,可能隐藏未曝光的漏洞,但相关情报也少;知名项目关注度高,漏洞价值大,但已被充分挖掘。
- 情报影响:拥有历史漏洞情报(如CVE、分析文章)的项目,审计路径更清晰。
- 公网暴露面:目标在公网暴露的资产越多,潜在影响范围越大,漏洞价值越高。
- 行业差异:金融、证券、政府等行业对安全极为敏感,在其系统中发现的漏洞通常价值更高。

2.2 选择策略
- 学习目的:应从易到难,选择有公开漏洞和标准分析答案的经典项目(如WebGoat、Damn Vulnerable Java Application)。
- 证明能力/研究:可选择知名度高的开源项目或框架,发现其漏洞能有效证明技术水平。
- 漏洞悬赏/渗透测试:应选择性价比高的目标,即评估漏洞易于发现且奖金/价值可观。
2.3 前提:资产测绘
在针对特定项目进行实战审计前,往往需要通过资产测绘寻找真实环境进行验证。
- 编写指纹:提取目标项目的特征,如特定HTTP响应头、JavaScript文件、HTML正文内容、favicon的哈希值(
icon_hash)等。
- 公网测绘:利用FOFA、Shodan、360 Quake等网络空间测绘引擎,使用编写好的指纹进行搜索。
- 裂变搜索:根据初步找到的站点,分析其更多独特特征(如特定API路径、Cookie名称),形成新指纹进行二次搜索,以发现更多资产。
- 筛选可用站点:收集状态码为200的可访问URL,数量上建议至少准备5个(用于漏洞悬赏)或3个(用于一般验证)不同站点,以确保漏洞的普遍性。

3. 静态分析工具选择
人工审计效率有限,需要借助静态分析工具进行辅助,自动化地发现潜在漏洞路径。
3.1 工具示例:TABBY
TABBY是一款基于代码属性图(CPG)的开源静态分析工具,特别擅长分析Java反序列化等复杂漏洞链。它可以将代码解析为图结构,并通过自定义的Cypher查询来搜索从Source到Sink的数据流路径。

3.2 商业化/集成化工具
市面上也存在一些集成了多语言支持、规则库和交互式界面的静态分析工具或SAST平台。这类工具通常具备以下功能:
- 多语言支持:覆盖Java、C/C++、Python、Go、JavaScript等。
- 丰富的规则库:内置针对各种漏洞(如反序列化、SQL注入、命令注入)的检测规则。
- 交互式分析:允许用户选择规则、查看扫描进度和结果。

3.3 静态规则编写
无论使用何种工具,核心都是编写规则来匹配“从污染源(Source)到危险函数(Sink)”的路径。规则需要描述数据流经过的可能节点和边。
// 规则思路伪代码:检测JDBC-Attack漏洞
1. 定义Source: 用户可控的输入点 (如HttpServletRequest.getParameter)。
2. 定义Sink: 危险的数据库连接方法 (如DriverManager.getConnection(url))。
3. 检查是否存在从Source到Sink的、未被净化的数据流路径。

4. LLM提效与工作流标准化
静态工具会产生大量告警,其中包含许多不可达的路径(误报)。引入大语言模型(LLM)进行辅助判断,可以极大提升人工审核效率。
4.1 LLM验证路径可达性
直接将工具输出的原始数据流结果扔给LLM,通常会导致其产生“幻觉”,判断不准确。
- 错误示例:简单Prompt + 复杂数据流 -> 错误判断。

正确的做法是构建清晰的指令,并提供关键的代码片段作为上下文,必要时采用Few-Shot示例引导。
- 正确方法:
- 清晰的Instruction:明确要求LLM基于代码逻辑判断路径是否真实可达。
- 提供关键代码片段:给出Source点、Sink点以及中间关键跳转的代码。
- 多因素判断:要求LLM从执行环境、数据流控制、权限校验等多个维度评估。
{
“execution_environment”: {
“name”: “执行环境”,
“sub_factors”: [
{ “factor”: “execution_permissions”, “description”: “执行权限”, “impact”: “高” },
{ “factor”: “sandbox_environment”, “description”: “沙箱环境”, “impact”: “中” }
]
},
“data_flow_control”: {
“name”: “数据流控制”,
“sub_factors”: [
{ “factor”: “parameter_encoding”, “description”: “参数编码方式”, “impact”: “中” },
{ “factor”: “intermediate_validation”, “description”: “中间验证”, “impact”: “高” }
]
}
}

4.2 工作流标准化与持续改进
一个高效的审计工作流应该是标准化、可复现的,并且需要持续优化。
- 构建测试样本库:收集历史审计过的项目代码、漏洞靶场中的复杂案例,作为测试样本。
- 验证工具链有效性:
- 用样本测试静态工具,检查是否漏报(False Negative)。
- 用样本测试LLM判断逻辑,检查是否误杀(False Positive)了真实可达的路径。
- 形成闭环:根据验证结果,不断调整静态分析规则和LLM的Prompt策略,提升整个工作流的准确率。
5. 实战分享一:某低代码开发平台
目标:某闭源低代码开发平台。
挑战:代码无法在本地直接运行。
5.1 资产测绘
由于代码跑不起来,转而通过资产测绘寻找线上真实环境。
- 提取项目
static目录下静态文件的icon_hash作为指纹。
- 使用测绘平台搜索,并用Yakit等工具发送探测请求验证。
- 利用返回包中的独特特征(如特定Cookie、响应头)进行裂变搜索,扩大资产范围。
- 筛选出响应状态为200的可用站点,收集其URL。

5.2 代码分析与漏洞挖掘
- 项目分析:确认其为经典的Spring多应用架构,核心代码打包在lib下的jar包中。配置文件包含数据库连接信息等。
- 定位入口:根据配置文件及代码,在静态分析工具中自定义规则,描述前台配置校验接口为Source点。

- 静态扫描:运行工具扫描,发现一条从“配置校验接口”到
DriverManager.getConnection()的疑似漏洞路径,其中JDBC连接URL参数可控。

- 依赖分析:检查项目依赖,发现包含H2数据库驱动。H2数据库连接URL支持执行JavaScript代码,这为攻击提供了可能。
- 构造利用链:结合安全攻防知识,构造不出网加载恶意字节码的H2 JDBC攻击Payload。
- 真实环境验证:将构造好的Payload在测绘找到的真实环境中进行测试,成功触发漏洞,验证了漏洞的有效性。

6. 实战分享二:某证券投研平台
目标:某证券行业投研平台。
情报:事先知晓该项目历史版本存在反序列化漏洞。
6.1 初步分析与困境
- 架构分析:项目为传统Servlet架构,路由清晰定义在
web.xml中。
- 静态扫描遇阻:使用常规的Servlet接口识别规则进行扫描,未发现预期的反序列化漏洞路径。

- 问题定位:
- Sink点确认:项目中确实存在
ObjectInputStream.readObject()调用点,且能被工具识别。
- Source点遗漏:工具可能未能识别全部的接口入口。经检查,发现项目除了Servlet,还使用了Axis框架发布RPC服务,这部分接口未被标准规则覆盖。

6.2 调整策略与验证
- 补充规则:为静态分析工具添加识别Axis框架服务接口(
<service>标签)作为Source点的规则。
- 重新扫描:成功找到从Axis接口到
readObject()的完整数据流路径。
- 环境适配:该平台环境多为老旧的JDK 6/7,需要选用兼容的利用链(如CommonsCollections系列)。

- 多环境测试:在多个目标站点上进行测试,成功验证漏洞。
7. 验证注意事项总结
无论漏洞看起来多么可靠,最终都必须经过严格验证。
- 多环境测试:实际环境可能存在代码版本、配置差异,多测试几个站点可以提高成功率,并确认漏洞的普遍性。
- 分析失败原因:如果利用失败,需考虑:代码版本不同、安全配置(如WAF、RASP)拦截、环境缺失依赖、或最初的漏洞判断本身就是错误的。
- 本地调试:如果条件允许,最好在本地搭建环境进行调试,跟踪代码执行流,确认每一步是否按预期进行。

结语
一套成熟的Java代码审计工作流,需要将攻防知识、目标筛选、自动化工具(静态分析)、智能辅助(LLM)以及严谨的实战验证有机结合。它不是一个完全自动化的“漏洞扫描”,而是一个“人机协同”的深度分析过程。通过标准化和优化这个流程,可以持续提升审计的效率与质量。欢迎大家在云栈社区的安全板块交流更多关于代码审计与渗透测试的心得与工具。