
上周处理完一起 AI Agent 越权漏洞后我把整套攻击链路、修复方案和复盘结论整理成文档准备沉淀成一篇技术文章。本来以为最难的部分是漏洞复现和越权判断结果在写复盘内容时卡了好几天。AI 生成的初稿看起来结构清晰、逻辑自洽细节里却到处都是问题接口参数被凭空多了一个、报错堆栈对不上、修复代码里还混进了一个编造的安全注解。那一刻我才意识到AI 辅助写作和 AI Agent 越权犯的是同一个错误在没有事实授权的情况下擅自扩展自己的权限边界。这篇文章我会按实际处理的顺序展开。先讲清楚什么叫 AI 越权产卡再从一次典型触发链路出发给出仿真复现过程接着落到服务端如何收敛权限、怎么修复。最后回到复盘文章写作聊聊用 AI 辅助写技术内容时如何防止幻觉污染事实。如果你正在做 Agent 应用或者经常用 AI 辅助写技术文档这篇内容应该能帮你少踩几个坑。1. 先搞清楚“AI越权产卡”到底发生了什么先给结论AI 越权产卡指的是 AI Agent 在意图识别、工具调用、参数补全和执行返回的完整链路中因为缺少权限校验替用户完成了一项他本没有权限执行的操作。这里的“产卡”是业务动作可能是一张卡券、一份单据、一条配置记录也可能是后台管理系统里的一次资源创建。这个问题的严重性和普通越权漏洞不同。传统 Web 接口的越权通常是一次请求、一次执行、一次影响。而 Agent 的越权是“意图级”的用户只需要说一句话Agent 会自动选择工具、补全参数、调用内部服务甚至批量操作。攻击成本被大幅降低影响面却被放大了一个数量级。看一个具体场景。企业内部接入了一个知识库问答 Agent这个 Agent 被允许调用一个“用户卡券创建”工具用于支持客服人员在线为用户开卡。开发人员为了图方便在工具描述里直接写明了参数名和调用方式后端接口却只验证了“请求来自内网”没有校验“请求者是否为卡券归属人”更没区分调用角色是普通用户还是管理员。结果是任何能访问这个对话入口的人都能让 Agent 帮他把卡开到任何用户名下。很多团队会问Agent 不是有提示词约束吗不是应该“拒绝执行危险操作”吗但提示词从来不是安全边界。Agent 本质是一个意图到行动的执行器它不具备人类对“权限归属”的常识判断更不会主动去校验你忘了校验的东西。服务端缺了权限校验Agent 就会忠实执行这就是越权产卡能成立的根本原因。这篇文章适合谁读AI 应用开发者、后端工程师、安全测试人员以及所有负责 Agent 上线的技术负责人。读完你至少能判断自己的 Agent 工具注册是否暴露了越权风险后端服务是否真正起到了兜底作用。2. 越权的典型触发链路Agent 完成一次操作通常经过四步意图识别理解用户自然语言判断用户想做什么。工具选择在注册的工具列表里挑选最合适的工具。参数补全根据用户输入和目标工具的参数 schema生成调用参数。后端执行调用真实接口操作数据库或第三方系统。越权问题发生在第四步但诱因往往在第二步和第三步就已经埋下。如果工具描述里没有声明“仅限管理员”“必须校验资源归属”Agent 就会在自己“理解范围内”大胆调用如果后端接口没有做身份和归属校验那么 Agent 生成什么参数接口就执行什么参数。这里有一个关键容易误判的点很多团队以为“工具是系统内部注册的外部用户碰不到”所以在接口层只加了简单的 token 校验。但实际上Agent 的对话入口往往同时面向客服、运营、财务甚至外部合作方。只要入口可达工具就是可达的工具可达越权请求就只是换个说法的事。我整理了一个常见对比对比项人工操作流程Agent 操作流程触发方式用户手动打开后台页面用户通过自然语言下达指令参数来源用户从表单中填写字段受前端控制Agent 根据工具描述自动生成可能生成任意用户 ID权限校验前端按钮按角色隐藏后端接口做鉴权完全依赖工具描述和后端接口约束操作速度单次操作耗时数分钟秒级执行可实现批量创建风险点越权时需要绕过前端和后端越权时只需让 Agent 按“合法流程”执行从表格可以看得很清楚Agent 只是把“谁来发起请求”这个风险前置到了对话层并没有消除后端应有的鉴权责任。真正的防线必须落在服务端而不是依赖模型“自觉”。3. 仿真复现一段越权请求是怎么产生的下面用一个最小示例复现整个链路。先说明以下代码仅用于技术演示请在本地测试环境或授权渗透测试环境中操作严禁在未授权情况下对生产系统执行。3.1 存在越权的后端接口假设后端是一个 Spring Boot 项目提供了一个卡券创建接口。注意看代码里的注释接口接收了请求头里的用户 ID但并没有校验请求体里的 cardOwnerId 是否与当前用户匹配。// 文件路径src/main/java/com/example/demo/controller/CardController.java RestController RequestMapping(/api/card) public class CardController { PostMapping(/create) public ResultString createCard(RequestBody CreateCardRequest request, RequestHeader(X-User-Id) String userId) { // 错误点只把 userId 记录到日志没有校验 cardOwnerId 是否属于 userId log.info(userId{} create card for cardOwnerId{}, userId, request.getCardOwnerId()); cardService.createCard(request.getCardOwnerId(), request.getQuota(), request.getRemark()); return Result.success(OK); } }问题不在 Controller 写得多粗糙而在于它假设了“能调用这个接口的人都是内部可信用户”。Agent 工具注册后这个接口就从“内部接口”变成了“Agent 能力”而 Agent 的入口又暴露给了更广的用户群体信任边界就被打破了。3.2 Agent 工具描述接下来Agent 工程团队在模型配置里注册了这个工具{ name: create_card, description: 为用户创建卡券参数cardOwnerId(卡主ID), quota(额度), remark(备注), parameters: { type: object, properties: { cardOwnerId: { type: string, description: 卡主用户ID }, quota: { type: integer, description: 开卡额度 }, remark: { type: string, description: 备注说明 } }, required: [cardOwnerId, quota] } }这份工具描述里没有任何权限说明。模型并不知道这个工具要区分管理员和普通用户也不清楚“只能给本人开卡”这样的业务规则。AI 应用开发者只告诉它能做什么没告诉它不能做什么它自然会在所有条件匹配时调用。3.3 模拟攻击请求现在用一个普通用户身份模拟对话调用。真实场景里用户不会直接发 HTTP 请求而是会对 Agent 说“帮我把这张卡开到管理员账号下”。Agent 会自动完成后续所有步骤。这里为了演示后端缺少校验直接模拟 Agent 最终生成的请求import requests url http://localhost:8080/api/card/create payload { cardOwnerId: admin_user_001, quota: 10000, remark: test from normal user } headers { X-User-Id: normal_user_002 } resp requests.post(url, jsonpayload, headersheaders) print(resp.status_code) print(resp.text)3.4 运行与预期输出在本地启动服务后执行脚本预期结果是200 {code:0,message:OK,data:OK}返回 200 已经说明问题一个普通用户成功通过 Agent 能力为管理员账号创建了高额度卡券。这个结果代表的是业务越权而不是 SQL 注入、命令执行这类“系统被攻破”的问题。它更隐蔽因为所有操作都走了合法接口日志里也都能查到但权限边界已经全面失守。4. 修复方案把权限边界下沉到服务端越权修复的关键就一句话不要把安全寄托在 Agent 的提示词上所有 Agent 调用最终都必须落到服务端校验。下面给出几个可落地的加固方向。4.1 在 Service 层增加归属校验最直接的修复是在服务端判断资源归属。涉及用户自身数据的操作必须校验请求体里的目标用户和当前登录用户是否一致。// 文件路径src/main/java/com/example/demo/service/CardService.java public void createCard(String operatorUserId, String cardOwnerId, int quota, String remark) { // 普通用户只能操作自己的卡券管理员除外 if (!operatorUserId.equals(cardOwnerId) !adminService.isAdmin(operatorUserId)) { throw new PermissionDeniedException(无权为其他用户创建卡券); } // 继续创建卡券的业务逻辑 // ... }注意这里把“当前登录用户”和“要操作的目标用户”分离成了两个概念。Controller 层的X-User-Id只能作为操作者标识不能作为业务数据来源。归属校验必须在 Service 层强制处理而不是在 Controller 层做一次前端式判断。4.2 工具注册时声明权限范围Agent 应用侧也需要同步收敛。为每个工具增加角色、写操作、二次确认等元数据应用层在调用前先做一次“预检”。agent: tools: card-create: enabled: true role: ADMIN_ONLY write-operation: true require-confirm: true allowed-scopes: - admin - customer-service-supervisor这份配置表达的含义是card-create工具只允许管理员角色调用属于写操作并且必须经过用户二次确认。Agent 应用可以在发起调用前过滤不可用的工具也可以在后端接口校验失败后给出友好错误提示。4.3 写操作多一层人工确认对于“产卡”这类有实际业务后果的写操作建议在 Agent 执行前插入确认步骤。实现方式不复杂Agent 先把解析好的参数返回给用户显示“即将创建以下卡券确认请回复 yes”而不是直接调工具。这一条看着简单实际能拦截大量误操作。很多越权事件里Agent 往往不是“故意害你”而是把用户随口说出的目标 ID 当成了合法参数。多一次确认就是在意图和行动之间插入一道人工校验点。4.4 审计日志必须记录操作者和资源归属修复之后日志同样重要。记录 Agent 调用时至少要包含以下字段字段含义agent_session_idAgent 会话 IDoperator_user_id实际登录用户tool_name被调用的工具名称request_params完整请求参数resource_owner_id被操作资源的归属者decision放行 / 拒绝 / 待确认server_time操作时间没有审计日志越权事故发生后只能通过业务数据反查排查成本极高。有了完整日志至少能在事后快速定位是哪一次对话、哪一次工具调用、哪一个用户发起了越权操作。5. 复盘文章输出AI 写作也在“越权”漏洞修复完我进入复盘阶段把整套材料交给 AI 辅助写作结果遇到了第二个问题。现象是这样的我给了 AI 一堆零散的复盘材料包括接口路径、错误点、修复代码片段让它“扩写成一篇结构完整的技术文章”。AI 输出的初稿非常漂亮章节齐全、语言顺畅但经不起事实核对。它自己补了一个根本不存在的 CVE 编号把沙箱环境的报错时间写成生产事故时间甚至在讲修复方案时加了一段“引入 Spring Security 后自动解决归属校验”的结论这既不是我的原意也让文章偏离了真实事件。这件事让我意识到AI 辅助写文章和 Agent 越权背后是同一个漏洞模式模型在它没有被事实授权的情况下擅自扩写、断言和总结。我把“整理材料”授权给了它它却把“补充事实”这件事也一并越权完成了。AI 不会承认自己不知道它只会用流利的文字掩盖不确定。这也是为什么“AI 写文章骗不了人了”逐渐成为社区共识能骗人的不是 AI 的结论而是 AI 用看似专业的语言伪造了没有依据的细节。技术文章的读者不会容忍这类错误。一个错版本文档、一条不存在的 API 名、一段无法运行的代码足以抵消整篇内容的价值。所有用 AI 辅助写作的人都应该把“防止 AI 越权补事实”当成第一优先级。6. 用 AI 写技术文章的防幻觉实践经过这次复盘我总结了一套可复用的 AI 辅助写作流程。核心原则是AI 只能负责表达和结构所有事实必须由人来提供和把关。6.1 用“事实锚点”喂给 AI而不是丢一堆资料直接给 AI 一堆聊天记录和零散笔记它很难判断哪些是事实、哪些是推断。正确做法是先整理出一份事实锚点清单再让 AI 基于清单写作。下面是一个可参考的提示词模板请基于以下事实锚点写一篇技术复盘文章不要新增任何事实 1. 漏洞位置POST /api/card/create 2. 触发原因服务端未校验 cardOwnerId 与登录用户身份是否一致 3. 影响范围任意登录用户可通过 Agent 为任意用户创建卡券 4. 修复方案在 Service 层增加归属校验工具注册增加管理员角色限制 5. 修复后验证普通用户构造越权请求返回 403 写作要求 - 只能使用以上事实不能补充 CVE 编号、版本号、性能数据 - 如果事实不足以支撑某个观点直接跳过 - 代码示例用注释标出“需要读者自行验证”有了这样的约束AI 的自由发挥空间会被大幅压缩。6.2 建立“真相校对表”逐项验证AI 初稿完成后不要直接发布。建议建一张校对表把文章中的每个事实性描述和原始材料逐项对照。事实项文章中的描述来源证据是否通过漏洞接口POST /api/card/create复盘文档第 3 节是漏洞原因未校验 cardOwnerId修复前代码是CVE 编号文章写 CNVD-2024-XXXXX原始材料中不存在否修复代码引入 Spring Security实际采用 Service 层校验否预期输出返回 200本地运行日志是校对表能强迫你逐条确认而不是被 AI 文章“看起来很对”的整体印象带跑。只要有一条标红就说明文章里还存在需要人工重写的地方。6.3 代码示例必须由人跑通一遍AI 生成代码有个特点语法通常是对的逻辑经常是似而非。让 AI 写“越权修复代码”它可能生成一个只校验了用户名是否非空的假校验看起来合理实际毫无防护。任何 AI 生成的代码都必须粘贴到本地工程里运行一次覆现场景验证它真的能拦住危险请求。这里真正容易踩坑的地方是AI 可能在代码里引入一个你项目里根本不存在的依赖或注解。建议跑通后再检查一遍依赖树避免把不存在的 API 写进文章误导读者。6.4 交给 AI 表达但把事实边界放在人手里我更推荐的分工方式是人负责整理事实、设计文章结构、提供核心代码AI 负责段落衔接、语言润色、生成对比表格初稿。事实范围一旦划定AI 就是高效的表达工具事实范围不划定AI 就是幻觉机器。7. 常见问题与排查思路写技术复盘时AI 辅助写作常见的坑集中在几个方向问题现象可能原因排查方式解决方案AI 生成了不存在的 CVE 编号训练数据里的相似漏洞被模型错误关联搜索该编号的权威来源确认删除无法验证的编号只写“该类漏洞”AI 补了一个项目中没有的依赖模型根据常见框架模式臆测检查 pom.xml 或 build.gradle 里的依赖树删除该依赖改用工程中真实存在的组件报错堆栈与描述场景不符模型混淆了不同案例的错误信息比对本地运行日志只用本地实际复现产生的报错内容修复代码看着正确但运行时异常模型生成的是伪代码缺少完整上下文本地跑通最小示例用真实项目代码替代 AI 生成代码文章结论比原始材料更绝对模型倾向于生成确定性表述核对结论是否被事实支撑增加限定词明确标注推测内容所有问题都可以归结为同一个排查原则一旦发现异常先问“这个信息在原始材料里有吗”。原始材料没有就让 AI 删除或重写而不是替它脑补一个合理解释。8. 最佳实践AI 应用安全与 AI 辅助写作的双车道把这次经历拆成两个方向分别给出工程建议。在 AI 应用安全侧有几条原则值得直接写进团队规范里最小权限原则Agent 能调用的工具集合必须按用户角色动态下发而不是所有角色共享一份全量工具。后端兜底原则任何 Agent 调用最终都必须经过服务端权限校验提示词约束只能作为体验优化不能作为安全控制。写操作双确认原则涉及创建、修改、删除资源的操作Agent 必须回显参数并请求确认。可观测性原则记录会话 ID、操作者、工具名、请求参数让每次 Agent 行为都能追溯。灰度上线原则新工具接入 Agent 前先在小范围用户群测试权限边界再逐步扩大权限范围。在 AI 辅助写作侧可以这样约束流程先整理事实锚点再让 AI 写作。所有代码必须本地跑通所有错误信息必须源自实际操作。用真相校对表逐项验证事实性描述。把不能确定的内容直接留空或标注“待补充”不要用 AI 的推测填空。最后人工通读一遍重点检查“是不是每一步都有材料支撑”。这两个方向看似无关核心却是一样的你有没有守住边界。Agent 越权是模型在工具调用上越过了权限边界AI 写作幻觉是模型在事实生成上越过了信息边界。边界一旦模糊模型就会用流畅的输出掩盖不确定最终产生看似合理但实际错误的结果。9. 总结与后续学习方向这次从越权漏洞到复盘文章输出的整个过程核心可以浓缩成两个判断第一Agent 应用的安全不能依赖模型自觉。工具注册、角色配置、后端校验、审计日志每一项都要做到服务端可验证。提示词是体验层不是安全层。第二AI 辅助写技术文章时AI 的角色是表达放大器不是事实创造器。事实只能来源于你的真实材料、本地运行结果和可验证的权威来源。如果你想继续深入建议优先学习几个方向Spring Security 这类框架如何做细粒度权限控制OAuth2 或 RBAC 在现代应用里如何融入 Agent 工具调用以及 RAG检索增强生成场景下如何对检索结果做权限过滤。这些方向背后的共同问题都是在回答同一个问题AI 的能力边界应该由谁来定义以及如何被强制约束。