SSRF简介
SSRF(Sever-Side Request Forgery, 服务器端请求伪造)
造成原因主要是服务器端提供的接口包含了所请求内容的URL参数
并未对这个参数过滤且客户端可控URL参数
造成的危害主要是
- 对服务器内网或本地端口扫描,获取服务信息
- 攻击运行在内网或本地的应用程序
- 利用File协议读取服务器本地文件
SSRF漏洞代码
curl_exec造成的SSRF
<?PHP
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_get_contents造成的SSRF
<?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;
?>
fsockopen造成的SSRF
<?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);
$content = '';
while(!feof($fp)){
$contents .= fgets($fp, 1024);
}
fclose($fp);
return $contents;
}
}
绕过SSRF过滤
利用进制
IP 127.0.0.1
8进制格式:0177.0.0.1
16进制格式:0x7F.0.0.1
10进制整数格式:2130706433
16进制整数格式:0x7F000001
利用@
例如: http://www.baidu.com@127.0.0.1/
一定程度上会被解析成内网地址
DNS Rebinding
1、服务器端获得URL参数,进行第一次DNS解析,获得了一个非内网的IP
2、对于获得的IP进行判断,发现为非黑名单IP,则通过验证
3、服务器端对于URL进行访问,由于DNS服务器设置的TTL为0,所以再次进行DNS解析,这一次DNS服务器返回的是内网地址。
4、由于已经绕过验证,所以服务器端返回访问内网资源的结果。
介绍一个网站http://ceye.io/
它可以进行DNS Rebinding
,也可以记录HTTP Request和DNS Query
可以用来外带无回显命令执行数据
利用短地址
http://dwz.cn/11SMa >>> http://127.0.0.1
利用[::]
http://[::]:80/ >>> http://127.0.0.1
利用句号
127。0。0。1 >>> 127.0.0.1
利用.
127.0.0.1. >>> 127.0.0.1
利用协议
dict:// #泄露安装软件版本信息,还可以查看端口,操作内网redis服务
SFTP://
TFTP://
LDAP://
gopher:// #利用Gopher攻击Redis、攻击Fastcgi、攻击mysql服务
file:// #读取文件
local_file:// #在python的urllib.urlopen()函数中可以读取本地文件
利用302跳转
some trick
filter_var() bypass
有如下代码
<?php
$url = $_GET['url'];
echo "Argument: ".$url."\n";
if(filter_var($url, FILTER_VALIDATE_URL)) {
$r = parse_url($url);
var_dump($r);
if(preg_match('/m0yuqi\.cn$/', $r['host']))
{
exec('curl -v -s "'.$r['host'].'"', $a);
print_r($a);
} else {
echo "Error: Host not allowed";
}
} else {
echo "Error: Invalid URL";
}
?>
filter_var($url, FILTER_VALIDATE_URL)
过滤显然是检查是否为有效urlparse_url($url)
获取url的hostpreg_match
对host进行正则匹配
在url中可以包含一些字符可以绕过上面的过滤,例如,
和;
所以上面的绕过方案为
# 80端口是为了curl的请求
0://evil.cn:80;m0yuqi.cn:80
或
0://evil.cn:80,m0yuqi.cn:80
# 若含有执行函数可以创建一个空变量
0://evil$m0yuqi.cn
更换evil可以请求到指定ip:port
parse_url和libcurl
完整url: scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
这里仅讨论url中不含'?'的情况
php parse_url:
host: 匹配最后一个@后面符合格式的host
libcurl:
host:匹配第一个@后面符合格式的host
如:
http://u:p@a.com:80@b.com/
php解析结果:
schema: http
host: b.com
user: u
pass: p@a.com:80
libcurl解析结果:
schema: http
host: a.com
user: u
pass: p
port: 80
后面的@b.com/会被忽略掉
[ip]是一种host的形式,libcurl在解析时候认为[]包裹的是host
修复方案
- 限制协议为HTTP、HTTPS
- 禁止30x跳转
- 设置URL白名单或者限制内网IP