1. 一次偶然的审计任务
上周接到一个内部系统的安全审计需求,代码量不大,但历史包袱很重。翻了几页源码,我很快在登录接口里发现一处可疑的字符串拼接,直觉告诉我这里可能藏着 SQL 注入,但一时又说不清完整的攻击路径。
按照以往的习惯,我会先手动梳理参数流向,再写正则或翻日志验证。但这次我决定换一种方式:把这段代码直接丢给大模型,让它以安全审计员的视角帮我分析。结果出乎意料地顺利,模型不仅定位到了注入点,还顺带指出了我差点忽略的第二处风险。
这篇文章就记录一下这次完整的实践过程,包括我如何构造提示词、如何验证模型结论,以及 AI 辅助审计的边界在哪里。
2. 为什么选择 AI 辅助审计
传统代码审计依赖审计者的经验积累,面对陌生业务模块时,往往需要先花大量时间理解上下文。而大模型在代码理解上有两个天然优势:一是能快速建立跨文件的调用关系,二是对常见漏洞模式有较强的模式识别能力。
以 SQL 注入为例,人工审计的典型路径是:找到数据库操作点,回溯参数来源,判断是否存在过滤或转义。这个过程在代码量小的时候还好,一旦涉及多级调用、动态拼接和框架封装,效率就会明显下降。
AI 辅助的价值不在于替代审计者,而在于把「找可疑点」这一步前置。模型可以快速扫描整段代码,标出所有可能的注入入口,再由人工逐一验证。这样既保留了人的判断力,又大幅缩短了前期排查时间。
3. 实战:一段疑似存在注入的代码
为了便于说明,我把当时审计的代码简化成下面这个示例。它模拟了一个典型的用户登录查询,参数直接拼进 SQL 语句:
public User login(String username, String password) {
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
return new User(rs.getString("username"), rs.getString("password"));
}
return null;
}
这段代码的问题非常典型:username 和 password 都来自前端请求,未经任何参数化处理就直接拼入 SQL。攻击者只要在用户名里输入一段精心构造的字符串,就能改变查询语义。
我把这段代码连同调用上下文一起发给大模型,并明确要求它扮演安全审计角色,输出漏洞点、攻击载荷和修复建议。
4. 提示词设计:让模型进入审计状态
提示词的质量直接决定模型输出的可用性。我采用的模板大致如下:
你是一名资深应用安全审计员。请审查下面这段 Java 代码,重点检查是否存在 SQL 注入风险。请按以下格式输出:
1. 漏洞位置与原因
2. 可能的攻击载荷示例
3. 修复建议(给出具体代码)
4. 是否存在其他安全隐患
这里有几个关键设计点:第一,明确角色设定,让模型切换到安全审计视角;第二,限定输出结构,避免模型泛泛而谈;第三,要求给出攻击载荷,方便后续验证。
模型返回的结果里,除了我预期的用户名注入点,还额外指出 password 参数同样存在风险,并且提醒我 Statement 对象本身不支持参数化,建议改用 PreparedStatement。这个补充提醒确实有价值,因为我在最初的人工排查中只关注了 username 一个入口。
5. 模型给出的攻击载荷与验证
模型给出的典型攻击载荷如下:
username = admin' —
password = anything
这条载荷的注入原理可以拆成两步来看。第一步是单引号闭合:原始 SQL 中 username 的值被一对单引号包裹,即 'admin'。攻击者在用户名里输入 admin',末尾的单引号会提前闭合掉 SQL 中 username 前面的那个左引号,从而让后面的内容脱离字符串字面量,进入 SQL 语法层面。第二步是注释符吞掉密码条件:紧随其后的 — 是 SQL 的行注释符,它会把该行剩余的所有内容全部注释掉,其中就包括原本用于校验密码的 AND password = '…' 条件。这样一来,密码字段无论输入什么都会被忽略,查询条件只剩下了 username = 'admin'。
把载荷代入原始拼接语句后,最终被拼成的完整 SQL 如下:
SELECT * FROM users WHERE username = 'admin' –' AND password = 'anything'
可以看到,– 之后的部分(包括闭合引号和密码条件)全部变成了注释,数据库实际执行的查询等价于 SELECT * FROM users WHERE username = 'admin'。只要 admin 账号存在,这条语句就会返回该用户记录,登录逻辑随即判定校验通过,攻击者便绕过了密码校验直接登录 admin 账号。为了验证结论,我在本地搭建了同样的数据库结构,实际执行了这条注入语句,确认可以绕过登录逻辑。
验证环节非常重要。AI 的结论再漂亮,也必须经过真实环境检验才能采信。我建议所有由模型标记的漏洞点,都要至少做一次手工复现,避免误报或漏报。
6. 修复方案:参数化查询
针对上述漏洞,最直接的修复方式是改用 PreparedStatement 参数化查询:
public User login(String username, String password) {
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
return new User(rs.getString("username"), rs.getString("password"));
}
return null;
}
参数化查询会把用户输入当作纯数据处理,而不是可执行的 SQL 片段,从根上杜绝了注入可能。模型在输出修复建议时也给出了同样的方案,并且补充说明:即使做了过滤和转义,也不应替代参数化,因为黑名单过滤总有绕过空间。
7. AI 审计的边界与人工复核
这次实践让我对 AI 辅助审计有了更清醒的认识。模型在识别常见漏洞模式上确实高效,但它并非万能。比如涉及复杂业务逻辑的越权问题、需要结合具体框架版本判断的已知漏洞,模型的表现就不够稳定。
另外,模型偶尔会产生误报,把已经做了安全处理的代码误判为存在风险。因此我总结了一条原则:AI 负责扩大排查面,人工负责收敛结论。所有模型标记的问题,都必须经过代码路径梳理和实际验证后才能进入修复清单。
从时间成本看,这次审计原本预计需要半天,实际在 AI 辅助下两小时左右就完成了主要风险点的定位和验证。效率提升明显,但前提是我对模型输出保持了足够的警惕。
8. 总结与建议
这次 AI 辅助代码审计的体验整体是正向的。大模型帮我快速定位了隐藏的 SQL 注入点,还补充了我遗漏的检查维度。如果你也想尝试类似的工作流,我有几点建议:
- 提示词要结构化:明确角色、输出格式和关注点,模型才能给出可用的审计结果。
- 务必人工验证:模型结论只能作为线索,不能直接当作最终漏洞清单。
- 关注模型遗漏项:业务逻辑漏洞和框架版本漏洞,AI 的识别能力仍然有限。
- 修复以参数化优先:过滤和转义只是辅助,参数化查询才是根治手段。
AI 不会取代安全审计员,但它可以成为审计员手里最趁手的放大镜。把重复的、模式化的排查交给模型,把判断和决策留给自己,这才是人机协作的正确姿势。






