AI 辅助代码安全审计:从 SAST 到 LLM 增强审查

代码安全审计(Security Audit)的目标,是在漏洞被攻击者利用之前,把注入、越权、敏感信息泄露这类缺陷从代码里挖出来。传统做法依赖 SAST(静态应用安全测试)与 DAST(动态应用安全测试)工具做规模化扫描,而 LLM 的加入带来了一层「能读懂业务语义、能跨文件推理」的增强审查能力。本文先建立漏洞认知框架,再讲清工具之间如何互补,最后给出可直接复用的提示词与一段含漏洞代码的实战修复示例。

一、先建立漏洞认知框架:OWASP Top 10 与 CWE Top 25

在让工具干活之前,得先知道「要找什么」。两个权威清单是最好的地图。

OWASP Top 10:2025

OWASP Top 10 是面向开发者的 Web 应用安全风险共识列表。截至 2025 版本,十大类为:

编号类别与本文相关的重点
A01Broken Access Control(失效的访问控制)越权、IDOR 的核心来源
A02Security Misconfiguration(安全配置错误)默认口令、暴露的调试接口
A03Software Supply Chain Failures(软件供应链失效)依赖投毒、构建完整性
A04Cryptographic Failures(加密机制失效)明文存储、弱算法
A05Injection(注入)SQL 注入、命令注入
A06Insecure Design(不安全设计)缺少威胁建模
A07Authentication Failures(认证失效)弱凭证、会话固定
A08Software or Data Integrity Failures(软件与数据完整性失效)不安全的反序列化
A09Security Logging and Alerting Failures(安全日志与告警失效)无法追溯攻击
A10Mishandling of Exceptional Conditions(异常处理失当)错误信息泄露内部细节

CWE Top 25:2025

CWE(常见弱点枚举)从「具体代码缺陷」层面给漏洞编号,比 OWASP 更细,也更适合作为审计时的排查清单。2025 榜单中与本文三类重点问题高度相关的有:

  • CWE-79:跨站脚本(XSS),榜单第 1 位。
  • CWE-89:SQL 注入,第 2 位。
  • CWE-78:OS 命令注入,第 9 位。
  • CWE-94:代码注入,第 10 位。
  • CWE-862:缺少授权(Missing Authorization),第 4 位,越权的典型。
  • CWE-639:通过用户可控键绕过授权(Authorization Bypass),即常见 IDOR。
  • CWE-200:向未授权方暴露敏感信息,第 20 位。
  • CWE-502:反序列化不受信任数据,第 15 位。

审计时建议把 OWASP 当「风险大类」做面,把 CWE 当「具体弱点」做点,两者结合才不会漏项。

二、传统 SAST/DAST 与 LLM 增强审计如何互补

三者不是替代关系,而是分层的组合拳。

维度SAST(静态)DAST(动态)LLM 增强审查
工作时机编码/提交阶段运行阶段编码/审查/复盘阶段
擅长模式化、确定性的缺陷(危险函数、硬编码密钥)运行态真实利用(鉴权绕过、配置错误)语义理解、跨文件逻辑、业务越权
短板误报多、看不懂业务意图覆盖依赖流量、难定位代码行可能幻觉、结果非确定性、需喂上下文
定位第一道确定性滤网真实利用验证推理层与降噪层

互补的典型用法有三类:

  1. SAST 出清单,LLM 做降噪。SAST 一次报几百条,人看不过来。把告警连同代码片段交给 LLM,让它按「是否真实可达、危害等级、修复成本」排序,先把明显误报滤掉。
  2. LLM 补 SAST 看不到的盲区。注入、硬编码密钥 SAST 很强,但「这个接口是否校验了当前用户是否有权查看这条记录」这种业务逻辑,SAST 基本无能为力,LLM 结合上下文能判断。
  3. LLM 当 DAST 的辅助解释器。DAST 发现一个越权响应,LLM 可反向定位到对应的鉴权缺失代码,并给出最小修复。

原则:让 SAST/DAST 做「确定性的广撒网」,让 LLM 做「语义化的精加工」。最终是否修复、如何修复,仍由人拍板。

三、提示策略:让 LLM 发现注入、越权、敏感信息泄露

LLM 审计的效果高度依赖提示词的结构。笼统地说「帮我看看有没有安全问题」会换来一堆泛泛而谈。有效的提示策略包含五点:

  1. 定角色与基准:明确「你是资深应用安全工程师,熟悉 OWASP Top 10:2025 与 CWE Top 25」,让模型套用专业框架。
  2. 明确排查清单:把要找的问题枚举出来(注入 / 越权 / 敏感信息泄露),而不是开放式的「安全问题」。
  3. 要求带证据:强制输出「CWE 编号 + 行号 + 风险等级 + 成因」,没有定位的结论一律视为无效。
  4. 要求修复而非改代码:让模型只出「修复建议」,不要让它直接重写整段,避免引入新变量或悄悄改变行为。
  5. 分批聚焦:一次只盯一类问题(例如先注入、再越权),比一次性全查更准,也便于人工逐条核对。

可复用的审计提示词模板:

你是资深应用安全工程师,熟悉 OWASP Top 10:2025 与 CWE Top 25。
请审计下面这段代码,本次只聚焦三类问题:
1. 注入:用户输入是否未经转义就进入 SQL、命令、HTML 或反序列化路径。
2. 越权:是否校验了当前调用者对该资源拥有访问权限(Broken Access Control / IDOR)。
3. 敏感信息泄露:是否向调用方、日志或报错中暴露密钥、token、SSN 等数据。
逐条输出:对应 CWE 编号、文件:行号、风险等级(高/中/低)、成因、修复建议。
不要修改代码,只输出结构化审计结论。

针对「敏感信息泄露」可单独加一句:「检查异常分支是否把堆栈、SQL、内部路径回传给客户端,这类信息会协助攻击者构造利用」。

四、实战示例:含漏洞代码 + 审计 Prompt + 修复

下面以一段 Flask 接口为例,它同时踩中注入、越权、敏感信息泄露三个坑。

有漏洞的代码

from flask import Flask, request, jsonify
import sqlite3

DB_PATH = "app.db"
SECRET_KEY = "sk_live_1234567890abcdef"   # 硬编码密钥,应移入环境变量(CWE-798)

app = Flask(__name__)

@app.route("/api/user")
def api_user():
    uid = request.args.get("uid", "")       # 用户输入,未校验、未鉴权
    # 字符串拼接构造 SQL,存在 SQL 注入(CWE-89)
    sql = "SELECT id, name, email, ssn FROM users WHERE id = " + uid
    conn = sqlite3.connect(DB_PATH)
    row = conn.execute(sql).fetchone()
    if not row:
        return jsonify({"error": "not found"}), 404
    # 直接回传 ssn 等敏感字段,且未校验当前用户是否有权查看(越权 + 信息泄露 CWE-200/862)
    return jsonify({
        "id": row[0],
        "name": row[1],
        "email": row[2],
        "ssn": row[3],
    })

把上面这段代码连同第三节的提示词交给 LLM,它会给出类似结论:

  • CWE-89(高):第 13 行字符串拼接 uid 进 SQL,攻击者可构造 0 OR 1=1 拖库。
  • CWE-862(高):接口未校验调用者身份,任意 uid 都能查,构成越权。
  • CWE-200(中):响应回传 ssn,违反最小暴露原则。
  • CWE-798(中):SECRET_KEY 硬编码,应进环境变量或密钥管理。

修复后的代码

from flask import Flask, request, jsonify, g
import sqlite3
import os

DB_PATH = os.environ.get("DB_PATH", "app.db")   # 配置从环境读取,不再硬编码密钥

app = Flask(__name__)

def current_user_id():
    # 从已校验的会话中取当前用户标识(实际项目由鉴权中间件写入 g)
    return g.get("user_id")

@app.route("/api/user")
def api_user():
    uid = request.args.get("uid", "")
    if not uid.isdigit():                         # 先做输入类型校验
        return jsonify({"error": "invalid id"}), 400
    conn = sqlite3.connect(DB_PATH)
    try:
        # 使用参数化查询,杜绝 SQL 注入(CWE-89)
        row = conn.execute(
            "SELECT id, name, email FROM users WHERE id = ?",
            (int(uid),),
        ).fetchone()
    finally:
        conn.close()
    if not row:
        return jsonify({"error": "not found"}), 404
    # 越权校验:只允许查看属于自己的记录(CWE-862)
    if int(uid) != current_user_id():
        return jsonify({"error": "forbidden"}), 403
    # 不再返回 ssn 等敏感字段(CWE-200)
    return jsonify({"id": row[0], "name": row[1], "email": row[2]})

修复要点:参数化查询消除注入;current_user_id 比对消除越权;移除 ssn 字段收敛信息暴露;密钥改走环境变量。

五、工具链概览(仅能力描述)

工具能力定位
CodeQLGitHub 推出的语义化代码分析引擎,核心理念是「把代码当数据来查询」。它用专门的 QL 查询语言编写规则,可跨函数、跨文件追踪数据流,官方覆盖了大量 CWE;深度集成 GitHub code scanning,能在 PR 上自动出安全告警,并支持自定义查询套件。
Semgrep轻量、快速的 SAST 工具,规则可用类代码语法编写,覆盖 C/Go/Java/Python/JS/TS 等数十种语言,并提供 SAST、SCA(依赖可达性分析)与 Secrets(630 多种凭据识别)三类扫描。适合在本地与 CI 里做高频、低延迟的检查。
CodeRabbit面向 PR 的 AI 代码审查助手,会在每次提交时给出带上下文的审查评论,其中含安全维度的提示,适合作为人工审查前的第一道语义滤网。
GitHub Copilot Security依托 GitHub 安全能力的 AI 助手,可在 code scanning 告警旁提供解释与AI 自动修复建议(Autofix),帮助开发者在熟悉的流程里更快地理解并消解漏洞。

这些工具与 LLM 通用审计是互补的:前者提供确定性、可重复的基线扫描,后者提供灵活的业务语义推理。生产环境里通常把它们串成「提交即扫、AI 降噪、人工决断」的流水线。

小结

  1. 用 OWASP Top 10:2025 划「风险大类」,用 CWE Top 25:2025 定「具体弱点编号」,审计才不会漏项。
  2. SAST/DAST 做确定性的广撒网,LLM 做语义化的精加工与降噪,二者互补而非替代。
  3. 提示词要定角色、枚举排查项、强制带 CWE 编号与行号、只给修复建议不改代码、按问题分批聚焦。
  4. 实战中注入靠参数化查询消除,越权靠调用者身份比对消除,敏感信息泄露靠收敛返回字段消除。
  5. CodeQL、Semgrep、CodeRabbit、GitHub Copilot Security 提供确定性基线,通用 LLM 提供灵活推理,组合成流水线最稳。
  6. 无论工具多强,修复优先级与最终决策始终留在人手里,AI 是放大器不是裁判。

参考与延伸阅读

  1. OWASP Top 10:2025(风险大类清单与说明):https://owasp.org/Top10/
  2. Semgrep 官方文档(语言支持、SAST/SCA/Secrets 能力):https://semgrep.dev/docs/
  3. CodeQL 官方文档(QL 查询语言、CWE 覆盖、GitHub code scanning 集成):https://codeql.github.com/docs/
  4. CWE Top 25:2025(具体弱点编号与排名,含 CWE-79/89/862/200 等):https://cwe.mitre.org/top25/archive/2025/2025_cwe_top25.html
  5. GitHub code scanning 与 CodeQL 集成指南(把语义化扫描接进 PR 流程):https://docs.github.com/en/code-security/code-scanning
本文累计阅读