AI Agent 权限滥用风险警示:2 万账户被劫持案例
攻击者通过礼貌地向 Meta AI 请求,绕过权限检查劫持两万个 Instagram 账户。警示程序员在设计 agent 权限模型时的关键漏洞。
攻击者通过礼貌地向 Meta AI 请求,绕过权限检查劫持两万个 Instagram 账户。警示程序员在设计 agent 权限模型时的关键漏洞。
六月初,攻击者控制了超过两万个 Instagram 账户,包括一个已停用的奥巴马时代白宫账户,却没有编写任何漏洞代码,也没有猜测单个密码。他们与 Meta 的 AI 支持助手开启聊天,要求它将一个他们控制的邮箱地址附加到他们不拥有的账户,并申请向该邮箱发送密码重置链接。Meta 后来确认了日志所显示的:这个助手的行为完全符合设计,而系统的另一部分本应验证该邮箱是否属于这个账户,但那个检查从未运行。
说这是一个 AI 错误,掩盖了真正发生的事情。这个助手执行的是一系列对与其交谈的人来说都是有效的、被允许的操作。本能阻止这次攻击的是一个人:一个看到陌生人重新路由名人恢复邮箱的支持工作者,感觉到什么不对劲,并拒绝了请求。
许多真实世界的授权从未被写成软件代码。相反,它存在于站在请求和系统之间的那个人的自由裁量权中,而其背后的一切都是基于这种裁量权始终存在的假设而构建的。把一个 agent 放在那个位置,自由裁量权就消失了,但下游的什么和谁都注意不到。Agent 并没有绕过你的安全模型,它只是暴露了其中作为人员的那一部分。
安全领域有一个精确的术语来描述 Meta 遭遇的问题:困惑代理(confused deputy)。一个拥有真实特权的进程被一个特权较低的方说服去代表它使用那些特权。就像夜班保安会为任何打来电话说"老板派我来的"的人打开保险库:他拥有钥匙,对方有个好故事。经典的 1988 案例是一个可以写入受保护的账单文件的编译器。一个无法直接写入那里的用户要求编译器代其完成,编译器照做了,因为它拥有这个权限,而且从未问过它是在服务谁的请求。
LLM agent 在构造上就是这样一个东西。它的界面是自然语言,自然语言不包含任何关于谁被授权做什么的信息,而这个模型的整个工作就是把听起来可信的句子转换成工具调用。直接的 API 请求至少会将调用者的身份携带过来。但一个句子不会,所以除非在调用执行前重新附加身份,否则 agent 就是以自身的权限行动,而请求者的权限永远进不了画面。
Agent 也无法可靠地区分指令和数据。上下文窗口中的一切都可被读作潜在指令:用户的消息、检索到的文档、agent 被要求总结的邮件正文。一个因为令人信服的聊天而重置密码的支持机器人,也会同样轻易地跟随隐藏在它被交去处理的文件内部的命令。击败 Meta 地理位置检查的 VPN 技巧是这个问题的粗糙版本。更复杂的版本——其中恶意指令通过 agent 摄取的内容偷渡进来——已经被记录为 agent 攻击的主导类别。
Instagram 机器人可以重置密码,这是一次严重的漏洞,但范围有限。现在推出的 agent 没有这样的限制。在 Meta 禁用这个支持工具的同一周,它推出了 Business Agent,它可以预订约见、甄别线索、达成销售、处理支付,并连接 Shopify 和 Zendesk 等系统代表公司行动。把相同的困惑代理逻辑跑过支付 API 和 CRM,故障就不再是被盗账户。而是退款发给了错误的一方、订单被重新路由、价格被覆盖、客户记录被篡改——每一个都是 agent 被授权代表任何提问者执行的合法操作。
市场正在超越安全模型。Gartner 预测,到 2026 年底,40% 的企业应用将包含任务特定的 AI agent,相比年初的不足 5%。大多数这些部署都会继承 Meta 的相同假设:无论什么位于特权行动的远端,都具有判断力。
在同样工作流后面放一个更强大的模型,也只会以更好的语法交付相同的账户,这正是为什么授权不能存在于模型中——模型是攻击者控制的部分。允许一个行动的决定必须在其外部做出,由一个策略层在任何事运行前检查谁真正在会话后面。Meta 的助手在重新绑定他们的恢复邮箱之前,从未确认与其交谈的人是否拥有这个账户。
在短短几行代码中,部署的代码看起来大概像这样。Agent 可以调用这个函数,而能调用它就是全部授权:
def add_recovery_email(account, new_email):
account.recovery_email = new_email # nothing here ties to the caller
send_reset_link(new_email)
修复不是一个更聪明的模型或更好的 prompt。而是缺失的 principal 检查,由聊天无法影响的东西决定:
pythondef add_recovery_email(account, new_email, principal):
if not principal.owns(account): # who is actually asking, verified
raise Unauthorized("session not authenticated as the account owner")
account.recovery_email = new_email
send_reset_link(new_email)
攻击者控制了对话,但 principal 来自经过认证的会话,而不是聊天,所以没有任何令人信服的消息序列能满足那一行。
Agent 应该持有限定范围、短期有效的权限,而不是持久访问。为总结客户未处理工单而生成的令牌,对于退款他们上一笔订单应该没用。这是最小权限原则,但它必须在每个行动和每个资源上执行,而不是在会话打开时授予一次就此信任,因为 agent 会被骗取伸手去拿它的凭证允许的一切。
任何不可逆的东西都需要一道 agent 无法通过的关卡。一个模型可以通过生成正确的词来满足的确认不是一个控制,只是装饰。支付、删除、权限变更和账户恢复应该位于人工批准或硬策略规则后面,按它们能造成的伤害程度分类,而不是像日常查询一样在同一路径上通过。
Agent 采取的每个行动都应该携带其来源信息,即 principal、会话和产生它的 prompt,这样你就能审计发生了什么,在某些东西被利用时撤销它。考虑到 Instagram 攻击运行了大约六周,一个受控事件和两万个被盗账户之间的距离通常就在于是否有人能接近实时地看到一个特权行动对完全无关的账户一次次触发。
这些都不是理由让 agent 远离真实系统。它们值得构建,Meta 出错的地方是一个普通的工程疏漏,不是什么我们必须害怕的 AI 属性。这里的每个修复都是团队已经知道如何做的事:限定凭证范围、验证 principal、门控你无法撤销的行动、保存运行内容的记录。
一个习惯把它们联系在一起。在把 agent 连接到任何重要的东西之前,问问那个循环中的人过去检查什么。那种判断是真实的工作,现在它必须作为代码存在,因为 agent 不会为你即兴表演。做到这一点,顺从就停止成为责任。一个恰好做它被允许做的事的 agent,正是你想要的,只要你已经完成了决定它被允许做什么的工作。