缓存穿透与sql注入可协同攻击,空值缓存无法防御sql注入,因其不校验key合法性;必须在请求入口层对参数做正则校验、类型转换和范围检查,从源头拦截非法输入。

缓存穿透和SQL注入不是两个孤立问题,而是一对协同攻击组合:恶意构造的无效key往往伴随非法字符,不拦截就可能进数据库触发注入。
为什么空值缓存本身挡不住SQL注入
很多人以为加了 redis.setex(key, 60, "") 就万事大吉,但这是错觉。空值缓存只解决“查不到数据要不要缓存”的问题,它完全不校验 key 本身是否合法。比如传入 user:1'; DROP TABLE users; -- 这种字符串,Redis照单全收,空值缓存也照设不误——可一旦后续某个分支绕过缓存(比如管理后台直连DB),或空值过期后重查,这个恶意 payload 就直接进了 SQL 查询语句。
常见疏漏点:
- 只在缓存层做空值写入,没在参数进入 DAO 前做白名单/格式校验
- 把
userId当作纯数字处理,但实际接收的是字符串,未做Integer.parseInt()或正则过滤 - 用 MyBatis 的
${}拼接 ID,而非#{}预编译
必须在缓存入口处做参数合法性校验
真正有效的联防,是把校验卡在最外层:请求刚进来、还没碰 Redis 时就拒绝非法输入。这不是“多此一举”,而是让穿透请求根本没机会生成 key。
实操建议:
- 对所有用于拼接 Redis key 的字段(如
userId、orderId),强制走正则校验:^[a-zA-Z0-9_\-]{1,32}$—— 拒绝任何含空格、引号、分号、括号的输入 - 数字型 ID 必须先转整型再校验范围:
Long.parseLong(id) > 0 && id ,防止负数或超长数绕过 - 若使用 Spring Boot,推荐用
@Pattern+@Min注解统一约束 DTO,避免每个 service 方法重复写 if - 校验失败直接返回 HTTP 400,不记录日志(防探测)、不进 Redis、不查 DB
布隆过滤器不能替代参数校验
布隆过滤器(RBloomFilter.contains(key))确实能高效拦截大量不存在的 key,但它本质是概率型结构:存在误判率(哪怕 0.01%),且无法识别字符串内容是否含恶意字符。一个被布隆过滤器“放行”的 user:123;--,仍会落到 DAO 层执行 SELECT * FROM users WHERE id = '123;--'。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
所以正确顺序只能是:
- 第一步:参数格式校验(阻断非法字符)
- 第二步:布隆过滤器判断 key 是否可能有效(减少无效 DB 查询)
- 第三步:查 Redis → 查 DB → 写空值(仅对已通过前两步的请求)
三者缺一不可,且顺序不可颠倒。
警惕 ORM 框架里的“自动逃逸”幻觉
有人觉得用了 JPA/Hibernate/MyBatis 就天然防注入,这是危险认知。这些框架只对 #{}、:param 这类占位符语法做预编译逃逸,但如果你写了 @Query("SELECT * FROM user WHERE id = '" + userId + "'") 或 MyBatis 的 ${id},那框架完全不干预,恶意字符串原样进 SQL。
关键检查点:
- 全局搜索项目中所有
${}、+字符串拼接、String.format构造 SQL 的地方 - DAO 方法签名里,ID 类型尽量用
Long而非String,从类型层面堵住字符串注入入口 - 测试阶段用
' OR '1'='1这类 payload 手动发请求,验证是否真被拦截(而不是返回空或 500)
最易被忽略的一点:缓存穿透防御代码里写的 redis.setex(key, 30, ""),那个 key 变量如果未经清洗就直接拼进 Redis 命令,本身也可能触发 Redis 协议注入(虽然罕见,但在某些客户端实现下存在风险)。所以 key 的合法性校验,必须发生在任何缓存操作之前,且独立于缓存逻辑本身。










