使用pgbouncer输入错误用户名触发“FATAL: bouncer config error”的疑问
PgBouncer SCRAM-SHA-256认证配置问题
问题背景
我正在尝试用SCRAM-SHA-256配置PgBouncer的简单认证,相关配置及测试情况如下:
PgBouncer配置文件(pgbouncer.ini)
[databases] * = host=localhost port=5432 auth_user=pgbouncer [pgbouncer] listen_port = 6432 listen_addr = * auth_type = scram-sha-256 auth_file = /etc/pgbouncer/users.txt auth_query = SELECT uname, phash from pgbouncer.user_lookup($1) admin_users = pgbouncer stats_users = pgbouncer default_pool_size = 20 max_client_conn = 1000 pool_mode = transaction logfile = /var/log/pgbouncer/pgbouncer.log
用户密码文件(users.txt)
"pgbouncer" "pgbouncer"
数据库查询函数(user_lookup)
CREATE OR REPLACE FUNCTION pgbouncer.user_lookup(in i_username text, out uname text, out phash text) RETURNS record AS $$ BEGIN SELECT usename, passwd FROM pg_catalog.pg_shadow WHERE usename = i_username INTO uname, phash; RETURN; END; $$ LANGUAGE plpgsql SECURITY DEFINER; REVOKE ALL ON FUNCTION pgbouncer.user_lookup(text) FROM public, pgbouncer; GRANT EXECUTE ON FUNCTION pgbouncer.user_lookup(text) TO pgbouncer;
测试现象
- 使用
postgres用户通过PgBouncer登录正常 - 使用不存在的用户名
wrong_user登录时触发错误:
[root@repmgr1 pgbouncer]# psql -U wrong_user -p 6432 -h localhost -d postgres psql: error: connection to server at "localhost" (::1), port 6432 failed: FATAL: bouncer config error
PgBouncer日志内容:
2024-07-17 22:50:39.483 MSK [22172] LOG S-0x55c67b0dc930: postgres/pgbouncer@127.0.0.1:5432 closing because: auth_query response contained null user name (age=34s) 2024-07-17 22:50:39.483 MSK [22172] LOG C-0x55c67b0d3f40: postgres/(nouser)@[::1]:57790 closing because: bouncer config error (age=0s) 2024-07-17 22:50:39.483 MSK [22172] WARNING C-0x55c67b0d3f40: postgres/(nouser)@[::1]:57790 pooler error: bouncer config error
疑问
- 这种输入错误用户名就返回明确错误的现象是PgBouncer的正常行为吗?
- 是否需要优化
user_lookup()函数,让它在查询不到用户时返回非空值? - 当前这种行为会不会导致暴力枚举有效用户名的风险?
解答
1. 该现象是PgBouncer的正常行为吗?
是正常行为。当auth_query返回的结果中uname或phash为NULL时,PgBouncer会判定为认证失败,并抛出bouncer config error这类错误。这是因为PgBouncer无法从查询结果中获取到用于SCRAM认证所需的有效用户名和哈希值,进而终止连接。
2. 是否需要优化user_lookup()函数?
非常有必要优化,主要有两个原因:
- 避免暴露用户存在性:当前行为会让攻击者通过错误信息差异(合法用户会走正常认证流程,非法用户直接返回配置错误)来枚举有效用户名,存在安全风险。
- 统一认证失败提示:优化后可以让非法用户的错误提示和密码错误的提示一致,混淆攻击者的判断。
优化后的user_lookup()函数可以在查询不到用户时,返回一个无效的用户名和哈希值(比如固定的占位符),这样PgBouncer会继续执行SCRAM认证流程,但最终会因为哈希不匹配而返回“密码错误”的通用提示:
CREATE OR REPLACE FUNCTION pgbouncer.user_lookup(in i_username text, out uname text, out phash text) RETURNS record AS $$ BEGIN SELECT usename, passwd FROM pg_catalog.pg_shadow WHERE usename = i_username INTO uname, phash; -- 如果查询不到用户,返回无效的占位符(需确保该哈希不是任何真实用户的密码哈希) IF uname IS NULL THEN uname = 'invalid_user'; phash = 'SCRAM-SHA-256$4096:abc123$xyz789=:xyz789='; END IF; RETURN; END; $$ LANGUAGE plpgsql SECURITY DEFINER; REVOKE ALL ON FUNCTION pgbouncer.user_lookup(text) FROM public, pgbouncer; GRANT EXECUTE ON FUNCTION pgbouncer.user_lookup(text) TO pgbouncer;
注意:这里的占位符哈希可以随便生成一个无效的SCRAM哈希(比如用一个测试密码生成后复制过来),只要确保它不会匹配任何真实用户的密码即可。
3. 当前是否存在暴力枚举风险?
是的。当前的错误提示差异非常明显:
- 合法用户名:即使密码错误,PgBouncer会返回
FATAL: password authentication failed这类提示,且耗时会包含SCRAM认证的交互过程。 - 非法用户名:直接返回
FATAL: bouncer config error,耗时极短。
攻击者可以利用这种差异,快速枚举系统中存在的有效用户名,进而针对这些用户名进行暴力破解密码的攻击。优化函数后,所有认证失败的提示都会统一为密码错误,且耗时一致,能有效降低这种风险。
内容的提问来源于stack exchange,提问作者elfcheg
相关产品推荐
相关产品推荐

