PostgreSQL安全PL/pgSQL函数防注入编写指南
在PL pgSQL中编写动态SQL时,仅依赖quote_ident()无法防注入,需先通过正则白名单校验标识符;参数必须用USING绑定;禁止在函数内拼接COPY命令路径,应硬编码;避免依赖format()占位符,需逐条分支进行安全检查。
在PL/pgSQL里写动态SQL,很多开发者最先想到的防护就是 quote_ident()。但如果你觉得只要套上这个函数就万事大吉,那可真是踩进了一个大坑。
先说结论:动态拼接表名或字段名时,quote_ident() 只能算是最低限度的防御,绝对不够用。它只负责把你传进来的字符串用双引号包起来,再把里面的反斜杠转义一下。但问题是,它从来不问“你这字符串到底是不是一个合法的标识符”。来看一个典型例子:你传进来一个 'users; DROP TABLE accounts; --',quote_ident() 会把它转换成 "users; DROP TABLE accounts; --"。你看,双引号是包上了,可这句话放到 EXECUTE 'SELECT * FROM ' || quote_ident(user_input) 里,它仍然是语法上合法的标识符——数据库不会报错,也不会拦截后面的恶意操作。等于说,你给了攻击者一本正经构造恶意字符串的通行证。
那真正安全的做法是什么?很简单:先白名单校验,再调 quote_ident()。举个例子,你可以只允许输入由字母、数字、下划线组成,长度不超过64个字符,并且不能以数字开头:
IF user_input !~ '^[a-zA-Z_][a-zA-Z0-9_]{0,63}$' THEN
RAISE EXCEPTION 'invalid identifier: %', user_input;
END IF;
- 正则白名单必须放在
EXECUTE之前执行,不能指望quote_ident()来兜底。 - 处理表名或字段名时,千万不要用
quote_literal()——它给你加的是单引号,一执行就语法报错。 - 也别直接把用户输入扔进
format()的%I占位符里,除非你已经做了白名单过滤。
动态查询中传参必须用 USING,不能拼进 SQL 字符串
这条坑埋得特别深,也特别容易被忽略。你可能会觉得自己已经用 quote_ident() 安全地处理了表名,WHERE 条件里的值随便拼一下也没事——但就是这“随便拼一下”,可能直接把注入入口暴露给攻击者。
-- 危险!拼接值 = 注入入口 EXECUTE 'SELECT * FROM ' || quote_ident(tbl) || ' WHERE id = ' || user_id;
业内公认的正确写法,是把查询值作为参数传给 EXECUTE ... USING:
EXECUTE 'SELECT * FROM ' || quote_ident(tbl) || ' WHERE id = $1' USING user_id;
USING后面的变量,数据库引擎会当作纯粹的数据绑定,完全脱离 SQL 解析上下文——这等于从根上断了注入的路。- 支持多个参数,比如
USING val1, val2, val3,对应 SQL 里的$1,$2,$3。 - 有一点必须牢记:
USING里不能传表名、字段名、ORDER BY 子句——这些还是得走白名单 +quote_ident()的组合拳。
COPY 命令在函数里绝对禁止拼接路径
这大概是最容易被忽视的高危操作了。在 PL/pgSQL 函数里写 EXECUTE 'COPY users FROM ''' || filename || '''',等于直接给攻击者开了一扇任意文件读取的大门。比如攻击者可以调用 load_csv('/etc/passwd'),直接把系统密码文件泄露出去。
生产环境下,唯一靠谱的方案是:彻底放弃在服务端用 COPY,改为由应用层流式导入(比如 Python 的 cursor.copy_expert())。如果实在绕不开,必须满足以下三个条件:
- 路径必须硬编码,比如
'/var/lib/postgresql/import/users.csv',绝对不要允许任何变量参与路径拼接。 - 数据库角色必须撤销
pg_read_server_files和pg_write_server_files权限。 - 函数本身用
SECURITY DEFINER并限制为只读角色,只对特定目录有访问权限。
为什么不用 format() 的 %L 或 %I 就容易出事
format() 用起来确实方便,但它的占位符行为完全依赖你传入的参数类型和上下文。比如 %L 对空字符串、NULL、特殊字符的处理,远不如 quote_literal() 稳定;%I 虽然等价于 quote_ident(),但它同样不会主动拒绝非法输入——它只是尽力转义,而不是校验。
更麻烦的是,在嵌套调用或复杂表达式里,format() 很容易漏掉某个占位符,导致未格式化的原始输入直接混进最终 SQL。所以建议这样处理:
- 优先拆解逻辑:白名单校验 →
quote_ident()/quote_literal()→EXECUTE ... USING,步步到位。 - 避免在同一个
format()调用里同时处理标识符和值。 - 测试时一定要覆盖边界输入:空字符串、单引号、分号、反斜杠、Unicode 控制字符——一个都不能少。
说到底,写对一行 EXECUTE 并不难,难的是确保函数里的每一条分支路径——IF 分支、异常处理块、循环体里的每一次拼接——都经过相同的安全检查。这才是真正的安全底线。

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
Redis是什么:核心特性、架构与应用场景解析
Redis是一款基于内存的键值型NoSQL数据库,以超高读写速度和丰富的数据结构著称。本文系统梳理Redis的核心特性、架构组成、性能优势及典型应用场景,并通过与Memcached、MySQL、MongoDB的对比,帮助开发者快速判断Redis是否适合当前业务需求。
Windows 安装 MongoDB 完整图文教程
本文详细介绍在 Windows 系统上安装 MongoDB 的完整流程。从官网下载 MSI 安装包开始,逐步演示自定义安装路径、配置 Windows 服务、跳过 MongoDB Compass 等关键选项,并提供通过系统服务列表验证安装是否成功的方法,帮助开发者快速搭建本地 MongoDB 环境。
Linux 安装 MongoDB 完整指南:依赖配置、环境变量与服务启动
本文详解在 Linux 系统下安装 MongoDB 的完整流程,涵盖依赖包安装、二进制包下载解压、环境变量配置、数据与日志目录创建及服务启动验证。通过标准化命令与路径说明,帮助开发者快速完成部署并确认服务状态。
MacOS安装MongoDB完整教程
本文介绍在MacOS系统下安装MongoDB的完整流程,涵盖下载、解压、目录配置、环境变量设置及服务启动。通过明确的命令与参数说明,帮助开发者快速完成环境搭建并验证安装结果。
Ubuntu系统安装与配置Redis完整指南
本文详解在Ubuntu系统中安装Redis的两种主流方式:apt在线安装与源码编译安装。涵盖版本选择逻辑、服务启停与状态检查、连接验证方法,以及在线练习工具与桌面GUI客户端的对比与使用建议,帮助开发者快速搭建并验证Redis运行环境。
- 热门数据榜
相关攻略
2026-09-01 06:20
2026-09-01 06:20
2026-09-01 06:20
2026-09-01 06:19
2026-09-01 06:19
2026-09-01 06:19
2026-09-01 06:18
2026-09-01 06:18
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程

