分享一篇 hackerone 2019 年的报告,介绍了通过 DNS 重新绑定(DNS rebinding)绕过 ip 验证,从而执行 SSRF 攻击获取敏感信息。
报告链接:HackerOne report 541169: GitLab::UrlBlocker validation bypass leading to full Server Side Request Forgery。
Greg 在一个视频中也解释了这个漏洞。
如果你想简单直观地理解 DNS rebinding 绕过 SSRF,可以看 Lostsec 的这个视频:SSRF Bypass by DNS Rebinding | Bug bounty poc
1 漏洞描述
The
GitLab::UrlBlockerIP address validation methods suffer from a Time of Check to Time of Use (ToCToU) vulnerability。The vulnerability occurs due to multiple DNS resolution requests performed before and after the checks.
简单讲,用户输入一个 url,GitLab::UrlBlocker 会先对该 url 进行 DNS 解析,并验证 ip 有效性,防止其解析到诸如 127.0.0.1, ::1, 169.254.0.0/16 等的 ip。之后,通过 HTTP 请求该 url 时,又会进行一次 DNS 解析。第一次解析到有效 ip 后会默认后面解析的 ip 均有效,使用 DNS rebinding 将 127.0.0.1 绑定到该 url 上,再结合使用 TOCTOU(经典的 Race Condition)即可绕过 ip 有效性验证。
2 影响版本
gitlab 11.9.8 ee PS: hackerone report 中的版本
3 复现流程
证明:配置一个域名 gitladextssrf.webhooks.pw,该域名可能会被解析到 127.0.0.1(DNS 重新绑定后的恶意域名)与 198.211.125.160(gitlab 服务原域名)。如果访问域名时,域名被解析到 127.0.0.1 则说明攻击成功。
- Create a new repository
- Add a commit to the repository
- Create a new Web Hook integration with the URL
http://gitlabextssrf.webhooks.pw:9999. - Log into the gitlab server and start a TCP listener on port 9999/tcp (e.g.
nc -vvn -l -p 9999) - 发送一系列并行报文请求 webhook。
DNS rebinding 一般要跟 TOCTOU 逻辑漏洞组合使用,毕竟要跟解析到的正常 IP 竞争。TOCTOU 算是比较传统的条件竞争(race condition)漏洞,而条件竞争的关键就是使各个请求间的延迟尽可能短,尽可能实现“同时”。所以这里要并行发送报文请求 webhook。
具体用到的命令如下:
1 | $ ./wfuzz -X POST \ |
上面的命令貌似是通过 wfuzz 使用多线程或并发的方式向服务端发送 1000 个请求报文,无法保证各请求之间的延迟。目前主流的并行发送请求的方式有两种:last-byte request 和 single-packet attack,我们可以使用 BurpSuite 中的 Turbo Intruder 以这两种方式发送并行请求,后面我写一篇关于 Race Condition 的文章细说。
域名能解析到 127.0.0.1 意味着也可以解析到任意 IP,包括内网 IP、AWS 云服务 IP(一般是 169.254.169.254),继而允许攻击者读取敏感信息、攻击内网主机等。
4 行为分析
4.1 当前行为
The
GitLab::UrlBlockervalidation code resolves the IP addresses of a URL domain, validates them against a series of block lists, and if valid returns to theGitLab::HTTPmodule which re-resolves the URL domain in order to perform the HTTP request
4.2 期望行为
The validated resolved addresses should be returned by
GitLab::UrlBlockerand used byGitLab::HTTPto make the TCP connection to the destination host.
5 代码分析
先吐个槽
妈的,这代码怎么找啊,目录结构都是一团浆糊更别说在里面找相关代码了,麻了…..
5.1 漏洞相关代码
代码链接:lib/gitlab/url_blocker.rb · v11.9.8-ee · GitLab.org / GitLab · GitLab
url_blocker.rb 中的存在逻辑漏洞的代码块如下。功能:判断是否可以 DNS 解析,以验证 url 合理性,返回一个 bool 变量。同时,也就意味着当请求该 url 时仍会进行一次 DNS 解析以获取 ip 地址。总共进行了两次 DNS 解析,存在 TOCTOU 逻辑漏洞。
1 | begin |
url_blocker.rb 的全注释代码见文末。
5.2 漏洞修复代码
再来看看该漏洞是如何修复的。
漏洞修复代码链接:Protect Gitlab HTTP against DNS rebinding attack
主要的变动就是有效性验证成功的返回值包括了 uri 解析后的 ip [uri, hostname] ,而不仅仅返回布尔值。
1 | # Returns the given URI with IP address as hostname and the original hostname respectively |
6 一些问题
Wikipedia上为什么说 DNS rebinding 解析到攻击者 DNS server 时需要 TTL 尽可能小甚至为0?
In order to perform this attack a DNS server must be configured to resolve a domain to alternating addresses with a low (or zero) Time To Live.
什么是 TTL?
[!quote] Time to live (TTL) or hop limit is a mechanism which limits the lifespan or lifetime of data in a computer or network。
不同场景也有不同的意义。
- 数据包路由:TTL 表示数据包的生存时间或跳数。每经过一个路由器,TTL 计数均减一,防止数据包无限循环吃掉网络资源。
- DNS 服务器缓存的 DNS 记录:DNS 缓存服务器在连接到权威性 DNS 服务器并获取记录的新副本之前可以为 DNS 记录提供服务的时间。
简而言之,当 TTL 用在缓存(DNS, CDN)上时一般就表示缓存信息有效时间。
什么是 DNS?
可以使用 chrome://net-internals/#dns 查看 chrome 浏览器缓存的 DNS 记录。
- A 记录:“A” 代表地址,即给定域名的 IP 地址。其中
@表示该记录为根域的记录。
| example.com | record type: | value: | TTL |
|---|---|---|---|
| @ | A | 192.0.2.1 | 14400 |
NS 记录:NS 代表“域名服务器”,域名服务器记录指示哪个 DNS 服务器对该域具有权威性(即,哪个服务器包含实际 DNS 记录)。基本上,NS 记录告诉互联网可从哪里找到域的 IP 地址。简单讲,NS 记录表示该域名的权威域名服务器。
| example.com | record type: | value: | TTL |
|---|---|---|---|
| @ | NS | ns1.exampleserver.com | 21600 |
CNAME 记录:canonical name,规范名,表示一个域名可映射为另一个规范域名(canonical name)。当该域名是另一域名的别名或解析到同一 ip 地址时,cname 可代替 a 记录。
| blog.example.com | record type: | value: | TTL |
|---|---|---|---|
| @ | CNAME | is an alias of example.com | 32600 |
回到那个问题,DNS rebinding 为什么要求 TTL 尽可能小?
TTL 越小,DNS 解析或查询就会越频繁,进而允许攻击者更频繁地修改域名解析到的 IP 地址。另一方面,更小的 TTL 也允许忽略掉缓存中的 DNS 记录
其实 DNS rebinding 的关键在于 how to redirect DNS resolution to an attack server?
- 设置一个 DNS 服务器并把它配置成某个域名的 authoritative DNS server 权威 DNS 服务器。
- short TTL。更正常 DNS 服务器竞争
- DNS 投毒。把 DNS 缓存中的记录直接改到 attack server
参考链接:
必须通过域名来验证 SSL 或 TLS 证书嘛,IP 是否可以?
可以。SSL 证书绑定在一个 “common name”上,可以是域名,可以是带通配符的名称 *.example.com 也可以是 IP,但很少给 IP 分配证书。
一个 IP 上可能寄宿着很多不同的站,分别有不同的域名,SSL 跟域名绑定可以,确保每个网站有其对应的 SSL 证书。
在一个 IP 上跑网站,可以避免 DNS lookup 产生的时间开销,虽然效果微乎其微。DNS 记录都有缓存,每过 TTL 的时间才发一次请求。我们假设这个 TTL 是 10 min,也就是说过 10 min 才会有个毫秒级的 DNS lookup 请求,时间约等于没有。
1.1.1.1 有 SSL 证书,但好像解析到了 cloudflare-dns.com .
1 | root@OuluVM072311847:~# openssl s_client -connect 1.1.1.1:443 -servername 1.1.1.1 | openssl x509 -noout -ext subjectAltName |
使用 IP 的好处:
- 网站经常会使用 cookie 进行认证或追踪用户行为,对于每个发送到域名的请求,这些 cookie 也会随之发送,但请求大多静态内容时却不需要 cookie。如果 cookie 很长,时间开销也不容小觑。一种做法是使用其他域名(separate domain)存放这些静态内容,或者直接将其存放到某个 IP。
- 域名要花钱
- 避免了 DNS lookup
使用 IP 的缺点:
- IP vs domain 相当于 硬编码 vs 变量。更换提供商之后,所有的 IP 都得修改,还要重新配置 SSL。
- 取决于 SSL 证书办法机构。有些机构不会给私有 IP 颁发 SSL 证书。The Certificate Authority/Browser Forum doesn’t like seeing private IPs in certs but has nothing against public IPs. Let’s Encrypt 不会给 IP 地址颁发证书
参考链接:
- StackOverflow: Is it possible to have SSL certificate for IP address, not domain name?
- StackOverflow: Are SSL certificates bound to the servers IP address?
openssl 貌似是使用 ip 查找证书?如何处理 CDN?
webhook?
Webhook 是一种基于 HTTP 的回调函数,可在两个 API 之间实现事件驱动的轻量级通信,也有人称之为 “反向 API”。
当客户端 API 从服务器 API 请求数据时,客户端需要以固定间隔向服务端发送 HTTP 请求(轮询),若服务端数据发生了变化,服务器 API 会发送相关的数据(也称为 payload)。
这种方式需要客户端不断发送请求以追踪服务端数据变化。在使用 Webhook 时,客户端向服务器 API 提供唯一的 URL,并指定要了解的事件。设置 Webhook 后,客户端不再需要轮询服务器。发生特定的事件时,服务器会自动将相关的有效负载发送到客户端的 Webhook URL。
参考链接:
localhost, loopback, local network 和 link local 之间的区别?
- localhost: 本地计算机自己,一般映射到 127.0.0.1 (IPv4) 或 ::1 (IPv6)
- loopback: 回环(地址),用来与自己通信,从源发出的信息又回到源。一般用来 debug、测试或与内部服务器通信。
- ipv4 127 打头的都是回环地址,127.0.0.1 - 127.255.255.255 即 127.0.0.0/8
- ipv6 ::1/128
- local network:本地网络,本地子网,包含在同一网段内的所有设备的集合。
- 家庭网络 192.168.1.0/24
- 办公网络 10.0.0.0/8,172.16.0.0/12
- ipv6: fd00::
- link local: “Link-local” refers to a special range of addresses used for network configuration on a single network link (like a segment of a larger network). These addresses allow devices to communicate with each other when there’s no central DHCP server. Link-local addresses are often used for auto-configuration in the absence of DHCP.
- ipv4: 169.254.0.0 - 169.254.255.255
- ipv6: fe80::/10 以 fe80 打头的
参考链接:回环地址的一点儿破事
0.0.0.0 是什么地址?
0.0.0.0 指不可路由的元地址(a non-routable meta-address),一般用来表示一个无效、未知或不可用的目标,可以理解成the “no particular address” placeholder. So,0.0.0.0 的意思就是没有地址 emmm。IPv6 可以写成 :: 或 ::/0
0.0.0.0 在不同的场景下有不同的意义:
- In the context of servers, 0.0.0.0 means “all IPv4 addresses on the local machine”.
listen 0.0.0.0会监听本机上的所有IPV4地址。 - In the context of routing, 0.0.0.0 usually means the default route. 默认路由。
用法:
- 在通过 DHCP 分配到 IP 地址之前,主机可以使用
0.0.0.0表示自己 - A way to explicitly specify that the target is unavailable.
- A way to specify “any IPv4 address at all”. It is used in this way when configuring servers (i.e. when binding listening sockets). This is known to TCP programmers as
INADDR_ANY
参考链接:
附
url_blocker.rb 的全注释代码:
1 | # frozen_string_literal: true |

