postgresql 的 format 函数通过 %i(标识符)和 %l(字面量)强制类型分离实现防注入,不依赖转义;错误地将用户输入拼入模板字符串仍会中招,且仅适用于 pl/pgsql 中的 execute,其他数据库无等效函数。

format 函数(PostgreSQL)是少数几个本身具备防注入能力的SQL内置函数,但它只在 PostgreSQL 中存在,且必须配合 %I 和 %L 占位符使用 —— 单独调用 format() 不防注入,错误用法照样中招。
format 函数为什么能防注入?
它不是靠“过滤”或“转义”输入,而是靠类型强制分离:
-
%I专用于标识符(表名、列名),会自动加双引号、处理大小写、转义特殊字符(如空格、连字符、保留字) -
%L专用于字面量值(字符串、数字),会自动加单引号、正确转义内部引号和反斜杠 - 任何非字符串/非标识符类型的参数传入都会报错,杜绝“绕过占位符”的可能
常见错误是把用户输入直接拼进 format 的模板字符串里:
-- ❌ 危险:把 user_input 塞进模板里
EXECUTE format('SELECT * FROM ' || user_input || ' WHERE id = %L', id_val);
-- ✅ 安全:所有动态部分都走占位符
EXECUTE format('SELECT * FROM %I WHERE id = %L', user_input, id_val);
其他数据库没有等效的内置防注入函数
MySQL、SQL Server、Oracle 等没有任何内置 SQL 函数能替代参数化查询的作用。
比如:
-
CONCAT()、REPLACE()、QUOTE()都只是字符串操作函数,不能阻止注入 -
QUOTE()看似有用(返回带引号和转义的字符串),但它返回的是文本值,无法安全嵌入到表名/列名位置,也不能防止二次解析漏洞 - 所有试图用 SQL 层函数“修补”拼接漏洞的做法,本质上都是在对抗解析器,而不是放弃拼接逻辑
你看到的 updatexml()、extractvalue() 等,全是攻击者用来触发报错回显的注入函数,和防护完全无关。
容易被忽略的关键点
-
format只在 PostgreSQL 的 PL/pgSQL 或 DO 块中生效,不适用于客户端直连执行的普通 SQL 语句 - 即使用了
format,如果最终执行语句仍通过字符串拼接传给EXECUTE外部变量(比如拼进注释、条件分支里),防护就失效 - 表名、列名这类标识符,永远不能靠应用层“白名单校验”兜底 —— 动态业务场景下白名单会迅速过时,而
%I能安全支持任意合法标识符(包括带双引号的、含 Unicode 的、大小写混合的)
真正防住 SQL 注入的,从来不是某个函数,而是是否把数据与结构彻底分开。这点上,format('%I', ...) 是极少数在 SQL 层就完成该分离的设计。











