postgresql驱动默认禁用堆叠查询,防护关键在于驱动层拒绝多语句执行及代码中严格使用参数化查询;需验证各语言驱动行为、禁用字符串拼接、限制数据库权限并避免日志泄露原始sql。

PostgreSQL驱动默认不支持堆叠查询,但某些配置会绕过限制
PostgreSQL 协议本身允许一次请求发送多条语句(用分号分隔),但主流客户端驱动(如 psycopg2、pg for Node.js、PDO_PGSQL)在默认配置下**拒绝执行多语句**。这意味着即使攻击者输入 admin'; DROP TABLE users; --,只要用的是标准参数化方式,后半条 DROP TABLE 根本不会被发给服务器——不是数据库拦的,是驱动层先卡死了。
容易踩的坑在于:部分驱动提供显式开关来启用多语句,一旦开启,就等于主动打开堆叠注入大门。
-
psycopg2中没有等效于 MySQL 的multi_statements=True选项,它天然禁止多语句;但若开发者手动拆解字符串、用cursor.execute()多次调用拼接后的 SQL,就等于自己绕过防护 -
node-postgres(pg)默认也禁用多语句;但若使用client.query("SELECT ...; DROP ...")直接传入含分号的字符串,且未设statement_timeout或连接级限制,可能触发异常行为(尤其在旧版本或自定义协议封装场景) -
PDO_PGSQL在 PHP 中,PDO::ATTR_EMULATE_PREPARES设为true时,预处理会被客户端模拟,此时若输入含分号,有可能被错误解析——应始终设为false
如何确认你的 PostgreSQL 驱动没开多语句通道
关键不是“怎么开”,而是“怎么确保它关着”。不同语言的验证方式不同,但逻辑一致:查文档 + 查运行时行为 + 看错误反馈。
- Python +
psycopg2:尝试执行含分号的原始查询,比如cur.execute("SELECT 1; SELECT 2"),会直接抛出psycopg2.ProgrammingError: multiple statements found—— 这是正常且期望的行为 - Node.js +
pg:同样执行client.query("SELECT 1; SELECT 2"),新版(v8+)默认报错Multiple statements detected;若没报错,说明底层用了非标准 client 或启用了allowExitOnMultipleStatements类似危险选项(该选项不存在于官方 pg,但某些 fork 或封装库可能加了) - PHP +
PDO:执行$pdo->query("SELECT 1; SELECT 2")应失败并提示ERROR: syntax error at or near ";";如果成功返回两行结果,说明驱动被魔改过,或用了非 PostgreSQL 原生协议适配器
比驱动设置更关键的防线:永远不用字符串拼接构造查询
驱动层禁用多语句只是兜底手段,真正起决定性作用的是代码写法。哪怕驱动允许多语句,只要不用拼接,攻击者就无法把分号塞进合法 SQL 结构里。
- 所有用户输入必须走参数占位符:
SELECT * FROM users WHERE name = %s(psycopg2)、SELECT * FROM users WHERE name = $1(pg/node) - 绝对避免用
f"SELECT * FROM users WHERE name = '{user_input}'"或"SELECT * FROM users WHERE name = '" + user_input + "'" - 动态表名/字段名等无法参数化的部分,必须用白名单校验:
if table_name not in ['users', 'orders']:而不是正则替换或简单截断 - PostgreSQL 的
quote_ident()和quote_literal()函数只应在极少数元编程场景下谨慎使用,不能替代参数化
生产环境还需配合数据库权限收紧
驱动层堵住多语句入口后,仍要防止攻击者利用已有的高权限账户执行破坏性操作。PostgreSQL 的角色权限模型非常精细,别让应用账号拥有它不需要的能力。
- 应用连接不要用
postgres超级用户,也不要给app_userCREATEDB或CREATEROLE权限 - 只读接口对应的账号,应仅授予
SELECT权限,且明确限定到具体表:GRANT SELECT ON TABLE users TO app_reader - 避免使用
GRANT ALL PRIVILEGES ON DATABASE mydb TO app_user—— 这类宽泛授权会让任何成功的注入都变得极其危险 - 注意:PostgreSQL 不支持像 MySQL 那样通过
sql_mode=STRICT_TRANS_TABLES禁用多语句,它的限制完全依赖客户端驱动和权限设计
最常被忽略的一点:很多团队花时间调 WAF 规则去拦截分号,却没检查自己的 ORM 是否在日志或调试模式下悄悄打印了原始 SQL 字符串——这些明文日志一旦泄露,等于把构造好的堆叠语句直接送给攻击者参考。










