SQL注入防护关键在于凭证动态注入、权限最小化、连接池隔离三者并行;K8s中禁用SQL代理WAF,因其无法识别参数化后的真实SQL且破坏合法语法,应通过Secret挂载环境变量、新旧凭据并存滚动更新、严格限制数据库用户权限来实现。

SQL注入防护不能只靠应用层参数化查询
在Kubernetes中部署安全代理防SQL注入,本质是补全应用层防御的盲区:即使PreparedStatement写得再规范,一旦攻击者通过RCE、日志注入或反序列化漏洞拿下应用Pod,就能直接用curl连数据库IP+端口执行任意SQL——因为连接池里还存着有效的DB_PASSWORD,且该凭证大概率是硬编码在ConfigMap或镜像里的。
所以真正起作用的不是某个“SQL防火墙代理”,而是三件事必须同时做到:
- 数据库连接凭证必须从运行时环境注入(
process.env.DB_PASSWORD或DB_USER环境变量),绝不能出现在application.yml或config.json里 - 凭证轮换必须支持新旧并存,滚动重启时旧连接靠
maxLifetime自然超时断开,而非立刻失效 - 数据库用户权限必须最小化,比如只给
orders表的SELECT, INSERT,不给DROP TABLE或information_schema读取权
为什么不要在K8s里部署SQL注入WAF代理
试图在Pod前加一层“SQL解析代理”(如基于ModSecurity规则的Sidecar)基本无效,原因很实在:
- 它只能看到应用发出去的原始SQL字符串,但无法区分
WHERE id = ?和WHERE id = 1 OR 1=1——因为参数化查询后,JDBC驱动早已把问号替换成真实值再发往数据库 - 若代理强制重写SQL(如过滤
UNION SELECT),会破坏ORM框架的分页、CTE或JSON函数等合法语法 - Kubernetes网络模型下,Pod间通信默认走ClusterIP,代理无法拦截Service内部直连;而用NetworkPolicy又没法做SQL语义分析
结论:与其花力气部署一个既挡不住真实攻击、又拖慢QPS的代理,不如把精力放在凭证动态获取 + 权限最小化 + 连接池隔离这三件确定有效的事上。
凭证必须从K8s Secret挂载,且禁止用.env文件
常见错误是把DB_PASSWORD写进.env文件然后volumeMount进容器——这会让轮换变成手动改文件、删Pod、等重建,完全违背“定期轮换”的初衷。
正确做法是用Kubernetes原生Secret,并确保:
- Secret名称与应用代码解耦:应用只读
DB_PASSWORD环境变量,Secret名可以是prod-db-creds-v202609,代码里不出现这个字符串 - 挂载方式优先选
envFrom而非volumeMount,避免敏感信息意外泄露到/proc/$PID/environ - Secret内容必须用
kubectl create secret generic mssql --from-literal=MSSQL_SA_PASSWORD="..."生成,别用--from-file读明文文件
轮换时必须容忍新旧凭据并存窗口期
执行ALTER USER app_prod_v2 IDENTIFIED BY 'newpass'后,旧密码立刻失效。如果此时应用没重启,连接池里所有存活连接都会在下次复用时抛出Access denied for user。
安全轮换唯一可行路径是:
- 先在数据库创建新用户
app_prod_202609,只赋SELECT, INSERT权限,不给GRANT OPTION - 更新K8s Secret,滚动重启Deployment(
rollingUpdate.maxSurge=1) - 确认新Pod日志里有
Connected as app_prod_202609,且旧用户登录日志停止增长 - 等至少24小时(覆盖所有连接池
maxLifetime),再删掉旧用户
跳过并存期等于主动制造可用性事故——这点最容易被忽略,也最常被线上故障复盘时反复踩中。


















