只读副本本身不防注入,但能大幅限制注入成功后的实际破坏范围——前提是应用连接正确、权限收得够紧、副本状态稳定。sql注入的本质是让攻击者借应用账号执行任意sql;若该账号连只读副本且仅具特定表字段的select权限,则insert/update/delete/drop等写操作全部失败,而union select、sleep()等读取探测行为仍可执行;必须配合三项硬性配置:显式连接只读副本地址、副本账号单独创建且仅授最小select权限、mysql启用super_read_only或postgresql禁用会话级写覆盖;真正关键防线仍是权限隔离,只读副本仅提供物理层写保护。

只读副本本身不防注入,但能大幅限制注入成功后的实际破坏范围——前提是应用连接正确、权限收得够紧、副本状态稳定。
为什么只读副本对注入攻击有“止损”效果
SQL注入的本质是让攻击者借应用账号执行任意SQL。如果这个账号连的是只读副本,且该副本上账号只有SELECT权限(甚至仅限特定表和字段),那么以下操作全部失败:
-
INSERT/UPDATE/DELETE:直接报错ERROR: cannot execute INSERT in a read-only transaction(PostgreSQL)或The MySQL server is running with the --read-only option -
DROP TABLE、CREATE FUNCTION等 DDL:权限拒绝,非只读设置本身拦截 -
SELECT ... INTO OUTFILE、LOAD_FILE():需FILE权限,只读副本账号通常不授
但注意:UNION SELECT、AND SLEEP(5)、SELECT version() 这类读取/探测行为依然可执行——只读副本防的是写,不是读。
必须配合的三项硬性配置
只读副本不是开个read_only=ON就万事大吉。漏掉任一环节,止损效果归零:
- 应用连接串必须显式指向只读副本地址(如
db-replica.example.com:3307),不能靠负载均衡随机分发;否则部分请求仍打到主库 - 副本上的数据库账号必须单独创建,且只授
SELECT权限——不能复用主库账号,也不能用GRANT SELECT ON *.*这种宽泛授权 - MySQL 需确认
super_read_only=ON已启用(防止 super 用户误写);PostgreSQL 需设default_transaction_read_only = on并禁用用户会话级覆盖
容易被忽略的“只读失效”场景
很多团队上线后才发现只读副本没起作用,问题常出在这些地方:
- ORM 框架(如 Django 的
using='replica')底层未真正切换连接,抓包发现 SQL 仍发往主库 IP - 应用用了连接池,某个连接在副本上开了事务但没提交/回滚,后续请求复用该连接时继承了只读状态,导致写接口静默失败
- MySQL 从库延迟高,攻击者用时间盲注反复探测,恰好在延迟窗口内查到刚同步过来的敏感数据(如新注册用户邮箱)
- 副本开启了
log_bin且未禁用binlog_format=STATEMENT,某些含函数的SELECT可能意外触发写日志行为(虽不改数据,但增加审计噪音)
比只读副本更关键的防线其实是权限隔离
就算没有副本,只要应用账号在主库上也只拥有SELECT (id, name) ON myapp.users这种列级权限,90% 的注入攻击连完整用户表都扫不出来。只读副本的价值,在于给权限配置多加一层物理隔离——它不解决“能不能读”,而是确保“绝对不能写”。真正容易被忽略的,是把权限控制、连接路由、监控告警这三件事当成一个整体来设计,而不是各自配完就认为安全闭环了。











