前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
返回 AI 情报前线
All News · 全部资讯9910
  • PentAGI 用多智能体执行渗透测试
  • MCP 排错日志里的凭据泄露隐患
  • Jev新版加入图像输入与26.2万上下文
  • 微软用知识编译降低智能体延迟与耗量
  • Grok语音模型升级,接口与价格保持不变
  • 把 ClickUp 项目记录变成 Agent 可查询记忆
  • Matrix-Game 4.0 开源单卡实时生成游戏
  • Agent 接入外部服务前先检查失败恢复能力
  • 多模型网关踩坑:推理预算吃光了回答
  • Step 5 Preview 开放权重,可部署百万上下文
  • AI 工具写操作防重:执行前先抢占
  • 给 n8n 的模型输出加一道契约检查
  • 自托管 AI 工具漏洞公告可能漏过扫描
  • 本地运行 Gemma 4:推测解码与草稿模型
  • AI 代码上线前先排查这些安全漏洞
  • Copilot更新HydraFusion并新增Max
  • MCP工具数量实测:准确率之外更要看成本
  • Apigee 支持在自有集群运行 MCP 工具
  • 编程 Agent 源码研究:架构比功能清单耐用
  • 把 GPU 内核调优变成夜间并行搜索
  • AI 生成的 Supabase 权限代码可能允许提权
  • Agent 成本控制要先预留并发调用预算
  • 全 AI 编码项目如何靠质量关卡持续交付
  • AI反编译加速老游戏移植,也带来风险
  • 用 AgentCore 构建可适应新格式的文档处理
  • 用 Issue 标签驱动 AI 分析与提 PR
  • AI 辅助六天串起 Chrome 漏洞利用链
  • Claude Code 改造电商站的操作与复盘
  • Agent 平台缺少身份配置却开放管理权限
  • 英特尔将至强推理调优封装进容器
  • 用 AgentCore 将企业文档生成演示文稿
  • 用 Bedrock Agent 编排云安全修复
  • Orion-1 面向网络防御,试补通用模型限制
  • 用 JumpStart 完成模型部署与性能测试
  • GPT-4 系列部分 API 据报将停用
  • 工厂智能排产应先建约束模型
  • 法律 RAG 的核心是有据回答与可靠拒答
  • Agent 如何调用工具:从函数接口到 MCP
  • 给 AI 功能算清每月 API 账单
  • 用可验证进度训练长任务 Agent
  • Agent 数据架构怎么选:五种方案对照
  • 终端编程助手实测:正确率之外的差异
  • curl 安全更新揭示 AI 漏洞研究成效
  • 视频编辑智能体如何准确操作时间线
  • Fish Audio 新语音模型开放权重,部署门槛仍高
  • n8n 节点改名后,副作用策略为何失效
  • Gemini 4 Argon 迁移需查参数兼容与计费
  • 智能体审批如何防止越权与重复执行
  • XBOW 披露 AI 辅助 Chrome 漏洞研究
  • Liquid AI 推出面向本地智能体的 MoE
  • 让编程 Agent 从源码开始排查故障
  • 已加载 51 / 9910
9.0
重磅
AI SCORE
编程提效2026-10-15 01:41

AI 生成的 Supabase 权限代码可能允许提权

dev.to · AI#AI 编程#权限安全#Supabase
Editor brief · 编辑速览

文章指出 AI 编辑器可能把管理员角色存入用户可修改的 user_metadata,并让 RLS 策略和页面权限信任该字段,导致普通用户自行提权。建议改用仅服务端可写的 app_metadata,或禁止客户端写入的角色表。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

AI 编辑器总是把 Supabase 用户的角色放进 user_metadata,再根据这个字段决定用户能否通过管理员 RLS 策略,以及访问管理员页面。

user_metadata 可以由用户自行修改。在浏览器控制台调用一次 supabase.auth.updateUser({ data: { role: 'admin' } }),任何人都能把自己变成管理员。

把角色存进 app_metadata(仅服务端可写),或者存进没有客户端写入策略的角色表,然后检查这些位置中的角色。

让 Cursor 或 Claude Code 给 Supabase 应用添加一个管理员控制台,你往往会得到一套看起来很规整的实现:注册时设置 role 字段,用 RLS 策略允许管理员读取所有订单,再通过 middleware 检查,把非管理员从 /admin 重定向出去。第一次运行,一切正常。

接着,用一个普通的非管理员账号打开浏览器控制台,运行一行代码。你就成了管理员。

不需要利用其他漏洞,也没有密钥泄露。只是角色被存到了 Supabase 明确允许用户自行编辑的位置。

问题出在:代码从 user_metadata 读取授权依据,而这个字段由已登录用户控制。下面是 AI 编辑器常见的写法,同一个问题同时出现在三个地方。

注册时,把角色存到用户数据中:

// signup.js
await supabase.auth.signUp({
  email,
  password,
  options: { data: { full_name: name, role: 'user' } },
});

RLS 策略信任这个字段:

-- CWE-269: privilege decided by a value the user can set
create policy "Admins can read all orders"
on public.orders
for select
to authenticated
using ( (select auth.jwt()) -> 'user_metadata' ->> 'role' = 'admin' );

应用也信任它:

// middleware.js
const { data: { user } } = await supabase.auth.getUser();
if (user?.user_metadata?.role !== 'admin') {
  return NextResponse.redirect(new URL('/', request.url));
}

整个攻击过程就是下面这两行。用任意已登录账号,在 devtools 中输入即可:

await supabase.auth.updateUser({ data: { role: 'admin' } });
await supabase.auth.refreshSession();

新的 token 现在携带了 role: admin。RLS 策略检查通过,middleware 检查通过,表中的所有订单都会返回。攻击者甚至不需要调用更新方法,直接把 role: 'admin' 传给 signUp,注册完成时就已经是管理员了。

Supabase 自己的文档对此说得很直接。RLS 指南指出,raw_user_meta_data「可以由已认证用户更新」,依赖 user_metadata claim 的策略「可能给你的应用带来安全问题」。Supabase 的数据库 linter 还为此提供了专门的检查项:0015 rls_references_user_metadata,级别为 ERROR。其理由是:用户 metadata「本来就是供用户自行修改的」,可以被改成「任意值(没有任何校验)」。

同一个漏洞,只是多绕了一步

注册时把角色复制到 profiles 表,并不能解决问题。下面这个 trigger 是 AI 常见的「改进」:

insert into public.profiles (id, full_name, role)
values (
  new.id,
  new.raw_user_meta_data ->> 'full_name',
  coalesce(new.raw_user_meta_data ->> 'role', 'user')
);

角色依然来自用户注册时提交的数据。如果 profiles 表还有常见的「用户可以更新自己的资料」策略,用户就能对自己的那一行执行 update profiles set role = 'admin',因为 RLS 检查的是你操作哪一行,而不是哪些列。

为什么这个问题总是出现

AI 编辑器把角色放进 user_metadata,是因为它是浏览器客户端唯一能写入的用户字段。代码使用公开的 anon key 运行,因此能用的工具就是 updateUser 和 signUp({ options: { data } })。写入 app_metadata 需要 admin API 和 service_role key,而这个密钥必须放在服务端,AI 可能还没有搭好这一部分。

教程也在引导开发者往这个方向走。注册示例把 full_name 和 avatar_url 存进 options.data,role 字段看起来不过是又一个用户资料属性。两个 metadata 对象又并排出现在 auth.jwt() 中,因此,读取 user_metadata 的策略看起来和读取 app_metadata 的策略一样合理。没有报错,编辑器也没有警告。管理员页面就这么正常工作了——谁想当管理员,谁都能用。

把角色存到只有服务端能写入的位置,并在那里检查角色。有两个不错的选择。

方案 1:app_metadata。用户无法更新它。在服务端路由或 Edge Function 中设置它,使用从非公开环境变量读取的 service_role key:

// server only. Never import this file in client code.
const supabaseAdmin = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_SERVICE_ROLE_KEY
);

await supabaseAdmin.auth.admin.updateUserById(userId, {
  app_metadata: { role: 'admin' },
});

然后,让策略读取 app_metadata:

create policy "Admins can read all orders"
on public.orders
for select
to authenticated
using ( (select auth.jwt()) -> 'app_metadata' ->> 'role' = 'admin' );

Supabase 文档提到了一个需要权衡的地方:对 app_metadata 的修改,「在用户的 JWT 刷新之前,不会反映到 auth.jwt() 中」。也就是说,撤销管理员权限要等到其 token 刷新后才会生效,而不是立即生效。

方案 2:建立一张客户端无法写入的角色表。这个方案的变更立即生效,而且角色不用放进 token:

create table public.user_roles (
  user_id uuid primary key references auth.users on delete cascade,
  role text not null check (role in ('user', 'admin'))
);
alter table public.user_roles enable row level security;

create policy "Users can read their own role"
on public.user_roles
for select
to authenticated
using ( (select auth.uid()) = user_id );
-- No insert, update or delete policies: only service_role or SQL can change roles.

create function public.is_admin()
returns boolean
language sql
stable
security definer
set search_path = ''
as $$
  select exists (
    select 1 from public.user_roles
    where user_id = (select auth.uid()) and role = 'admin'
  );
$$;

create policy "Admins can read all orders"
on public.orders
for select
to authenticated
using ( (select public.is_admin()) );

如果你在 profiles 中保留 role 列,就把它排除在用户可更新的列之外:

revoke update on public.profiles from authenticated;
grant update (full_name, avatar_url) on public.profiles to authenticated;

从注册时的 options.data 中移除 role,也要从所有将 metadata 复制到角色列的 trigger 中移除它。在 middleware 中,从 app_metadata 读取角色,或者查询数据库,绝不要从 user_metadata 读取。

验证时,用 options: { data: { role: 'admin' } } 注册一个新账号,再用这个账号的 session 查询一张仅管理员可访问的表。返回结果应该是零行。然后,用 grep 搜索这一模式:

grep -rn "user_metadata" src/ app/ supabase/migrations/

凡是用搜索结果中的字段来决定是否允许访问的地方,都是漏洞。在 Supabase 控制台中,Security Advisor 会对你的所有策略运行 0015 检查。

问:Supabase 用户能编辑自己的 user_metadata 吗?答:能。任何已登录用户都可以通过 supabase.auth.updateUser({ data: { ... } }) 修改它,也可以在注册时通过 options.data 设置它。Supabase 文档明确指出,不应使用它做安全决策。

问:Supabase 中的 user_metadata 和 app_metadata 有什么区别?答:user_metadata 是用户可写的资料数据。app_metadata 无法由用户更新,需要使用 service_role key 通过 admin API 设置,因此适合存储角色、套餐和团队成员关系。

问:如果页面也检查 user_metadata,那么在 Next.js middleware 中检查它安全吗?答:不安全。两处检查读取的都是同一个由用户控制的值,因此攻击者能同时通过这两处检查。把角色移到 app_metadata 或受保护的表中,并在服务端和 RLS 中检查它。

我一直在用 SafeWeave 检查这类问题。它的仓库检查包含一条检测从用户 metadata 读取角色的规则(supabase-role-from-user-metadata),Supabase 集成还会每天获取 Supabase 自己的 security advisors 检查结果,这样新出现的策略问题就会自动浮现,不用我惦记着去查看。这些检查包含在 Cloud 套餐中。即使没有扫描工具,上面的 grep 搜索和新账号测试也能发现大多数此类问题。关键是,要在别人打开控制台之前发现它。

如果要采取进一步措施,可以考虑屏蔽此人和/或举报其滥用行为。

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
把 GPU 内核调优变成夜间并行搜索
下一篇
Agent 成本控制要先预留并发调用预算