Redis缓存本身不能防御SQL注入,但通过减少数据库查询次数、增加前置校验环节,可显著缩小攻击面;关键在于避免用户输入直接拼接SQL,所有查询须强制使用预处理语句。

Redis 缓存本身不能防御 SQL 注入,但合理使用它能显著缩小 SQL 注入可被利用的攻击面——关键在于把“原本要走数据库查询”的非实时请求,提前拦截在缓存层,让恶意输入根本没机会拼接到 SQL 里。
为什么缓存能降低 SQL 注入风险
SQL 注入发生的前提是:用户输入参与了动态 SQL 拼接,并最终交由数据库执行。如果某个查询结果(比如商品详情、用户配置、地区列表)本身不常变,且业务允许一定延迟,那它就该从 Redis 读,而不是每次都查数据库。
此时即使攻击者构造 ' OR 1=1 -- 这类 payload,只要请求命中缓存,就不会触发后端 SQL 构造逻辑;哪怕没命中,只要缓存层做了参数校验或 key 白名单,也能拦下明显异常的 key(如含空格、单引号、分号的 key)。
本质是:减少数据库查询调用次数 + 增加前置过滤环节。
缓存 key 设计必须规避字符串拼接
常见错误是把用户输入直接拼进 key,例如:
std::string key = "user:" + user_id + ":profile"; // user_id 来自 HTTP 参数
如果 user_id 是 123' OR '1'='1,key 变成 user:123' OR '1'='1:profile,虽不会导致 Redis 执行命令注入(Redis 不解析 key 内容),但可能引发后续逻辑混乱,比如误判为合法 key 而跳过校验,间接放行恶意输入到数据库层。
正确做法:
- 对
user_id先做类型强转或正则匹配(如只允许^[0-9]{1,12}$) - 用哈希或编码处理原始输入,再拼 key:
key = "user:" + md5(user_id) + ":profile" - 所有缓存 key 必须来自白名单字段(如仅允许
user_id、sku_code等已知结构化字段)
空值缓存需配合严格 TTL,避免放大穿透风险
缓存穿透场景下,攻击者用大量不存在的 user_id 刷接口,若你对每个都写 SET user:999999:profile "" EX 60,等于帮攻击者把恶意请求固化进缓存,反而浪费内存、掩盖真实问题。
更稳妥的做法:
- 只对明确已知“合法但暂无数据”的 ID 缓存空值(比如注册流程中查邮箱是否已被占用)
- 对高频无效 key(如连续 10 次查
user:-1、user:abc)直接限流或拒绝,不写空缓存 - 空值 TTL 控制在 1–5 分钟内,避免长期占位
- 日志记录空缓存写入行为,监控异常 key 模式(如含 SQL 关键字的 key)
Redis 本身不防 SQL 注入,但能暴露注入漏洞
如果你发现 Redis 中存了大量形如 sql_cache:SELECT%20*%20FROM%20users%20WHERE%20name%3D'admin' 的 key,说明业务层正在用用户输入拼 SQL 再做 key —— 这本身就是高危设计,Redis 只是“照单全收”,反而成了漏洞的证据链一环。
真正要做的不是让 Redis 更“安全”,而是让应用层不再产生这种 key:
- 禁止任何函数名出现在 key 中,如
query_、sql_、exec_ - 缓存内容应为纯数据(JSON 字符串、序列化对象),而非 SQL 语句或其哈希
- 所有数据库查询路径必须强制走预处理语句(
mysql_stmt_prepare/pg_query_params),缓存只是锦上添花,不是兜底方案
最易被忽略的一点:缓存命中的逻辑分支,常常被开发者当成“安全区”而放松输入校验。实际上,只要缓存 key 的生成依赖未校验的输入,整个缓存机制就可能成为 SQL 注入的跳板。











