直接用eval会出问题,因用户输入若被拼接进脚本体(如f-string),可触发lua注入,导致读取、删改任意key甚至执行flushdb;安全做法是仅用evalsha+预加载,脚本内容须硬编码且参数通过keys/argv传入。

为什么直接用 eval 会出问题?
Redis 的 eval 命令允许传入任意 Lua 脚本,但 redis-py 默认不校验脚本内容,一旦拼接了用户输入(比如用 f-string 或 % 格式化),就可能触发 Lua 注入——不是 SQL 注入那种,但效果类似:攻击者能读取、删改其他 key,甚至调用 redis.call('flushdb') 清空整个库。
常见错误写法:r.eval("return redis.call('get', '" + user_key + "')", 0)。只要 user_key 是可控的,就危险。
真正安全的做法是:只用 evalsha + 预加载(script_load),且脚本内容完全硬编码或从可信配置源读取,绝不拼接。
如何用 register_script 预加载并复用脚本?
register_script 是 redis-py 提供的安全封装,它自动完成 script_load → 缓存 SHA1 → 后续调用 evalsha 的流程,避免手动管理 SHA 值和重载逻辑。
- 脚本必须是纯字符串字面量,不能含变量插值
- 注册后返回一个可调用对象,参数通过
keys和args显式传入,Lua 中用KEYS[1]、ARGV[1]访问 - 首次执行会自动
script_load,后续都走evalsha,性能更好也更安全
示例:
lua_get_with_ttl = r.register_script("""
local val = redis.call('get', KEYS[1])
if val then
return {val, redis.call('ttl', KEYS[1])}
else
return {nil, -2}
end
""")
<p>result = lua_get_with_ttl(keys=['mykey'], args=[])</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill8054" title="python-parallelization"><img
src="https://img.php.cn/upload/skill/000/000/081/179181013081932.jpg" alt="python-parallelization" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill8054" title="python-parallelization" class="overflowclass">python-parallelization</a>
<p class="overflowclass">将顺序 Python 代码转换为并行/并发实现,用于在需要并行化 Python 代码、提升性能时使用。</p>
</div>
<a rel="nofollow" href="/xiazai/skill8054" title="python-parallelization" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><h1>result 是 ['value', 3600] 或 [None, -2]</h1><p></p>
遇到 NOSCRIPT No matching script 怎么办?
这个错误说明 Redis 里没有缓存对应 SHA 的脚本,通常有三个原因:
- 脚本内容有细微差异(比如多了一个空格、换行符不同),SHA 就变了;检查是否用了
textwrap.dedent或意外修改了缩进 - 连接的是不同 Redis 实例(比如本地开发 vs 测试环境),脚本没在目标实例上加载过;
register_script只对当前连接生效,集群环境下需确保所有节点都加载过(可用redis-py-cluster的load_scripts) - Redis 重启后清空了 script cache;生产环境建议启动时预热脚本(调用一次
register_script即可)
临时绕过方式(仅调试):lua_get_with_ttl.sha 拿到 SHA,再手动 r.evalsha(sha, 1, 'mykey') 看是否报错,能快速定位是缓存缺失还是语法错误。
哪些 Lua 特性在 redis-py 中要特别注意?
Redis 内置 Lua 解释器是精简版(Lua 5.1),不支持 os.*、io.*、require,也不允许 math.random(除非显式启用)。更重要的是返回值限制:
- 只能返回 number / string / boolean / table(且 table 不能嵌套函数或 userdata)
- 如果 Lua 返回
nil,redis-py 会转成 Python 的None;返回{1,2,3}会变成[1,2,3];但{a=1,b=2}(带键名)会被丢弃键名,只留值顺序,容易误判 - 大脚本(> 1MB)可能触发 Redis 的
lua-time-limit超时,默认 5 秒;超时后脚本被 kill,连接进入 busy 状态,需调用r.script_kill()恢复
所以复杂逻辑尽量拆成多个小脚本,用 pipeline 批量提交,别在一个脚本里循环查几百个 key。
最常被忽略的一点:脚本安全性不只取决于怎么写,更取决于谁控制 keys 和 args 参数。哪怕脚本本身干净,如果 keys 列表里混入了用户可控的 key 名,依然可能越权访问——务必在业务层做 key 命名空间校验(比如限定前缀为 user:)。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










