我用 Vibe Coding 写了个安全扫描器,结果它自己就是个漏洞

我用 Vibe Coding 写了个安全扫描器,结果它自己就是个漏洞

前言

"你只要跟 AI 说清楚需求,它就能帮你写出来。"这句话我信了。

上个月我心血来潮,想用 Vibe Coding 的方式搞一个内部安全扫描器。需求很简单:扫描公司内网的 Web 应用,检测常见的 OWASP Top 10 漏洞。我用 Claude Code 从零开始,只靠自然语言描述需求,一行代码没手写。

三天后,扫描器跑起来了。功能齐全,UI 漂亮,我还在团队群里秀了一把。

一周后,安全团队的一位同事看了一眼源码,对我说了一句话:"你这个扫描器,它自己就是个安全漏洞。"

这篇文章记录了我在 Vibe Coding 过程中踩过的安全坑。不是说 Vibe Coding 不好用,而是你必须知道它容易在哪些地方翻车——尤其是写安全工具的时候。


坑一:用户输入直接拼接——最基础的注入漏洞

我让 AI 写了一个目标输入框,用户输入 URL 后开始扫描。AI 生成的代码长这样:

@app.post("/scan")
async def start_scan(target: str):
    # AI 生成的代码(有漏洞)
    cmd = f"nmap -sV -sC {target}"
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    return {"result": result.stdout}

看出来问题了吗?

shell=True 加上字符串直接拼接,这是经典的命令注入漏洞。如果用户输入的是 example.com; rm -rf /,那 nmap -sV -sC example.com; rm -rf / 就会被执行。

正确的写法

import shlex
import subprocess
import re
from urllib.parse import urlparse

def validate_target(target: str) -> str:
    """严格校验目标地址"""
    # 1. 解析 URL
    parsed = urlparse(target if '://' in target else f'http://{target}')
    hostname = parsed.hostname
    
    if not hostname:
        raise ValueError("无效的目标地址")
    
    # 2. 只允许域名和 IP,禁止特殊字符
    if not re.match(r'^[a-zA-Z0-9.-]+$', hostname):
        raise ValueError("目标地址包含非法字符")
    
    # 3. 禁止扫描内网地址(安全扫描器的基本素养)
    import ipaddress
    try:
        ip = ipaddress.ip_address(hostname)
        if ip.is_private:
            raise ValueError("禁止扫描内网地址")
    except ValueError:
        pass  # 不是 IP 地址,是域名,继续
    
    return hostname

@app.post("/scan")
async def start_scan(target: str):
    clean_target = validate_target(target)
    # 使用列表形式传参,避免 shell 注入
    result = subprocess.run(
        ["nmap", "-sV", "-sC", clean_target],
        capture_output=True, text=True, timeout=300
    )
    return {"result": result.stdout}

这个漏洞,如果你让 AI 帮你 review 自己的代码,它大概率能发现。但如果你不主动问,AI 生成的代码默认就是最"方便"的写法——方便往往意味着不安全。


坑二:硬编码的密钥和凭证

扫描器需要调用几个外部漏洞库的 API 来查询 CVE 信息。AI 生成的代码里,API Key 是这样写的:

# AI 生成的代码(有漏洞)
VULNDB_API_KEY = "vulndb_sk_a1b2c3d4e5f6g7h8i9j0"
SHODAN_API_KEY = "aBcDeFgHiJkLmNoPqRsT"
NVD_API_KEY = "nvdb-12345-67890-abcdef"

async def query_cve(cve_id: str):
    headers = {"Authorization": f"Bearer {VULNDB_API_KEY}"}
    async with httpx.AsyncClient() as client:
        resp = await client.get(f"https://vulndb.example.com/api/cve/{cve_id}", headers=headers)
        return resp.json()

硬编码密钥是老生常谈的问题了,但 Vibe Coding 生成的代码里,这个问题特别普遍。因为 AI 的目标是让代码"能跑起来",硬编码是最直接的方式。

解决方案:用环境变量 + .env 文件,并且在 .gitignore 中排除。

import os
from pathlib import Path
from dotenv import load_dotenv

# 加载 .env 文件
load_dotenv(Path(__file__).parent / '.env')

VULNDB_API_KEY = os.environ.get("VULNDB_API_KEY")
SHODAN_API_KEY = os.environ.get("SHODAN_API_KEY")

if not VULNDB_API_KEY:
    raise RuntimeError("VULNDB_API_KEY 环境变量未设置")
# .env 文件(不提交到 git)
VULNDB_API_KEY=your_key_here
SHODAN_API_KEY=your_key_here

额外提醒:如果你用 Git 管理代码,即使后来把密钥删了,它还是存在于 git 历史里。需要用 git filter-branchBFG Repo Cleaner 清理历史。


坑三:错误处理泄露敏感信息

扫描器的异常处理,AI 生成的是这样的:

@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
    return JSONResponse(
        status_code=500,
        content={
            "error": str(exc),
            "type": type(exc).__name__,
            "traceback": traceback.format_exc()  # AI 很"贴心"地加了完整堆栈
        }
    )

在开发环境这很方便调试,但在生产环境,完整的 traceback 会暴露你的代码结构、文件路径、数据库连接信息甚至密钥。

正确的做法

import logging

logger = logging.getLogger(__name__)

@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
    # 记录完整错误到日志(不暴露给用户)
    logger.error(f"Unhandled exception: {exc}", exc_info=True, extra={
        "request_path": request.url.path,
        "request_method": request.method,
    })
    
    # 返回给用户的只有通用错误信息
    return JSONResponse(
        status_code=500,
        content={
            "error": "服务器内部错误,请稍后重试",
            "request_id": str(uuid.uuid4())  # 用于问题追踪
        }
    )

坑四:扫描结果未脱敏

扫描器会检测目标应用的漏洞,结果页面直接展示了所有发现。问题是,有些漏洞的 payload 本身包含敏感信息——比如 SQL 注入检测时获取到的数据库版本、表名、甚至部分数据。

# AI 生成的代码(有漏洞)—— 直接展示所有结果
@app.get("/scan/{scan_id}/results")
async def get_results(scan_id: str):
    results = await db.get_scan_results(scan_id)
    return {"results": results}  # 原封不动返回所有数据

如果你把扫描结果分享给开发团队或者外部合作方,可能会无意中泄露敏感的漏洞详情。

import re

def sanitize_results(results: list[dict]) -> list[dict]:
    """脱敏扫描结果"""
    sanitized = []
    for item in results:
        clean_item = {
            "vuln_type": item["vuln_type"],
            "severity": item["severity"],
            "url": _mask_url(item["url"]),
            "description": item["description"],
            "recommendation": item.get("recommendation", ""),
            # 不返回原始 payload 和详细证据
            "evidence": "[已脱敏]" if item.get("evidence") else None,
        }
        sanitized.append(clean_item)
    return sanitized

def _mask_url(url: str) -> str:
    """遮蔽 URL 中的敏感参数"""
    # 把查询参数中的值替换成 ***
    return re.sub(r'([?&][^=]+=)[^&]*', r'\1***', url)

坑五:权限控制形同虚设

Vibe Coding 最容易忽略的就是权限控制。AI 生成的代码通常假设"所有用户都是管理员",因为这样逻辑最简单。

# AI 生成的代码(有漏洞)—— 任何人都能执行任何操作
@app.post("/scan")
async def start_scan(target: str):
    # 没有任何权限检查
    ...

@app.delete("/scan/{scan_id}")
async def delete_scan(scan_id: str):
    # 任何人都能删除任何扫描记录
    await db.delete_scan(scan_id)

即使是内部工具,也应该有基本的权限控制。至少要区分"谁能发起扫描"和"谁能查看结果"。

from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer

security = HTTPBearer()

async def require_scanner_role(token = Depends(security)):
    """校验用户是否有扫描权限"""
    user = await verify_token(token.credentials)
    if "scanner" not in user.get("roles", []):
        raise HTTPException(status_code=403, detail="没有扫描权限")
    return user

@app.post("/scan")
async def start_scan(target: str, user = Depends(require_scanner_role)):
    logger.info(f"用户 {user['name']} 发起扫描: {target}")
    ...

反思:Vibe Coding 的安全边界

经过这次踩坑,我总结了几条 Vibe Coding 的安全原则:

1. 不要相信 AI 生成的安全相关代码。AI 的训练目标是"生成正确的代码",而不是"生成安全的代码"。安全是一个额外的约束,AI 默认不会考虑。

2. 每次让 AI 生成代码后,自己 review 一遍。重点关注:用户输入处理、密钥管理、错误处理、权限控制这四个方面。

3. 用安全工具扫描 AI 生成的代码。我后来养成习惯,每次 AI 生成代码后,都用 bandit(Python 安全扫描工具)跑一遍:

bandit -r src/ -ll -ii --skip B101
# -ll: 只显示中高风险
# -ii: 显示具体问题
# --skip B101: 跳过 assert 警告(开发阶段需要)

4. 不要 Vibe Coding 安全核心逻辑。认证、授权、加密、密钥管理这些核心安全逻辑,必须由安全工程师手写并 review。Vibe Coding 适合写业务逻辑、UI、数据处理这些非安全核心的部分。

Vibe Coding 是个好工具,但安全这事,还真不能全靠"Vibe"。


你在 Vibe Coding 过程中踩过哪些安全坑?评论区聊聊,互相避坑。


关注「安全值班室」公众号

每天AI安全早报 + 实战攻防案例 + 网安学习路线连载

关注安全值班室

posted on 2026-05-31 15:05  明.Sir  阅读(30)  评论(0)    收藏  举报

导航