应禁用SHOW DATABASES权限并配合视图重写或RLS策略;Redis需禁用KEYS改用SCAN+ACL前缀控制;SDK须配置跳过库查询异常。
MySQL 多实例中如何避免 information_schema 暴露敏感库名
默认情况下,所有 mysql 实例都会把本地数据库名(包括业务库、中间件库、测试库)统一列在 information_schema.schemata 里,只要用户有 select 权限就能查到。这不是“显示不显示”的前端问题,而是权限与视图机制的底层行为。
真正可控的点只有两个:权限隔离 + 视图重写。不能靠应用层过滤,因为 DBA、运维、甚至某些监控脚本直连时都会绕过。
- 给非 DBA 用户分配账号时,**禁止授予
SHOW DATABASES权限**——这是最直接的开关,缺省即关闭; - 若必须保留该权限(如某些 ORM 初始化逻辑依赖),则需配合
sql_mode=STRICT_TRANS_TABLES+ 自定义视图替代原information_schema.schemata查询路径; - 注意:MySQL 8.0+ 的
admin_port或performance_schema不会自动同步此限制,仍需单独处理。
PostgreSQL 中用 pg_catalog 实现分级可见性
PostgreSQL 没有全局“隐藏数据库”开关,它的库级隔离靠的是连接粒度——每个连接只属于一个数据库。但 pg_database 表本身对所有用户可读,所以关键不是“藏”,而是“让不该看的人查不到有效数据”。
典型做法是:用 row level security (RLS) 策略锁住 pg_database,再结合角色继承链做动态过滤。
- 创建只读角色
app_reader,不赋予CREATEDB或CONNECT到系统库的权限; - 在
pg_database上启用 RLS,并添加策略:USING (datname IN ('prod', 'staging')); - 注意:RLS 对超级用户无效,所以 DBA 账号必须严格管控,且不能用
SET ROLE临时切换成普通角色来绕过; - 某些客户端(如
psql \l)会绕过 RLS 直接调用内部函数,此时需配合pg_hba.conf的hostnossl或clientcert=verify-full做连接层兜底。
Redis 多租户场景下如何防止 KEYS * 泄露库结构
Redis 本身没有“数据库分级”概念,SELECT 0 到 SELECT 15 是平级编号,KEYS * 默认扫当前 db,但很多运维误以为“不同 db 就天然隔离”。实际风险在于:一旦拿到任意 db 的访问权,就可能通过 CONFIG GET databases 推出总数量,再轮询所有 db 扫 key。
- 禁用
KEYS命令(在redis.conf中加rename-command KEYS ""),改用SCAN+ 游标,且要求客户端必须带MATCH前缀约束; - 为每个租户分配独立 Redis 实例或使用 Redis Modules(如 RedisJSON + ACL),而不是靠 db 编号区分;
- ACL 规则示例:
ACL SETUSER tenant_a on >pwd ~tenant_a:* allcommands—— 注意~后面的 pattern 必须显式限定 key 前缀,否则~*等同于开放全部; - Redis 7.0+ 支持
ACL LOG,建议开启并对接 SIEM,因为 ACL 误配比命令禁用更容易被忽略。
跨语言 SDK 连接池中过滤掉系统库名的通用技巧
很多语言的数据库驱动(如 Python 的 mysql-connector-python、Go 的 database/sql)在初始化时会自动执行 SHOW DATABASES 或等效查询来构建元数据缓存。如果后端已做权限控制,但 SDK 仍报错或返回空列表,大概率是驱动没适配新权限模型。
- Python:
mysql.connector.connect(..., get_warnings=False, raise_on_warnings=False)可避免因SHOW DATABASES权限不足导致 connect() 报Access denied; - Go:
sql.Open("mysql", "user:pass@tcp(127.0.0.1:3306)/?multiStatements=true")中去掉末尾的/dbname,否则驱动会尝试USE dbname并触发权限检查; - Java JDBC:
useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC这类参数不影响权限,但zeroDateTimeBehavior=CONVERT_TO_NULL若配置不当,可能让驱动在初始化阶段因类型转换失败而静默跳过库列表获取; - 所有 SDK 都应设置
maxIdleTime和connectionTimeout,否则连接池复用旧连接时,可能缓存了上一个用户看到的库列表。
系统数据库不是靠“前端不展示”来隐藏的,而是靠连接建立那一刻的权限边界、协议层响应截断、以及 SDK 对异常的容忍策略共同决定的。最容易被漏掉的是连接池生命周期和驱动初始化顺序——它们往往在业务代码之前就完成了元数据拉取。

















