缓存层不执行sql解析,真正风险在于业务代码将未校验的缓存值拼入sql。需对缓存数据做参数化查询、白名单校验和命名空间隔离。

缓存层是否真的执行了 SQL 解析?
绝大多数缓存系统(如 Redis、Memcached)只存储序列化后的结果,不解析也不执行 SQL。所谓“二次注入”在缓存读取阶段根本不会发生——因为缓存里存的是 ["id": 123, "name": "admin"] 这类数据,不是 SQL 字符串。真正危险的,是把缓存值直接拼进新 SQL 中再执行。
哪些场景会触发缓存后拼接 SQL 的风险?
典型错误模式:从缓存取出用户昵称,未经处理就用于 WHERE name = '${cachedName}' 这类字符串拼接。此时若缓存曾被恶意污染(比如上游写入时未过滤),就会导致注入。
- 缓存内容来自不可信输入源(如 API 响应、日志提取、未校验的 DB 查询结果)
- 业务代码用
eval()、exec()或模板引擎动态构造 SQL(Node.js 的pg.query(sql, params)被绕过,改用pg.query(sql + ' AND ...')) - ORM 查询链中混用原始 SQL 片段,且该片段引用了缓存字段(如
User.where("status = ? AND name LIKE ?", status, cache[:name]))
缓存读取后必须做的三件事
缓存本身不解决安全问题,验证责任落在读取方。关键不是“防缓存注入”,而是“防缓存内容被误当可信输入”。
- 所有从缓存读出的字符串,若要进 SQL,必须走参数化查询:
db.query('SELECT * FROM users WHERE name = ?', [cachedName]),绝不用字符串拼接 - 对缓存字段做白名单校验:比如昵称只允许
/^[a-zA-Z0-9_\u4e00-\u9fa5]{1,20}$/,匹配失败则拒绝使用该缓存项或 fallback 到 DB 查询 - 设置缓存 key 的命名空间隔离:避免不同来源的数据共用同一 key(如
user:123:profile和user:123:input_raw分开),防止上游污染扩散
Redis 缓存污染的真实案例与修复
某系统将用户提交的搜索关键词缓存为 search:abc'; DROP TABLE users; --,后续代码读取后拼成 "SELECT * FROM logs WHERE keyword = '" + cached + "'",触发注入。问题不在 Redis,而在写入和读取两端都缺失校验。
修复方式:
- 写入前清洗:
cleanKeyword = keyword.replace(/[^a-zA-Z0-9\u4e00-\u9fa5\s]/g, '') - 读取后强类型断言:
if (typeof cachedKeyword !== 'string' || !/^[a-zA-Z0-9\u4e00-\u9fa5]{1,50}$/.test(cachedKeyword)) throw new Error('Invalid cache value') - 永远用
client.get(key, (err, val) => { if (val) useParametrizedQuery(val) }),而非client.get(key, (err, val) => { if (val) rawQuery(val) })
缓存不是信任边界,它只是性能优化层。任何从缓存流出的数据,在进入执行上下文前,都得按和用户输入同等标准校验。










