漏洞概述
node这个dos洞是dns模块解析ttl时限定了数组大小导致的。当node的dns模块发出dns请求后,恶意dns server返回ttl数量大于256就会造成数组越界。之前误以为很多ttl的意思是多次cname,跟代码后发现只要返回一条包含很多ip的a记录,即可触发漏洞。
环境搭建
之前一直没怎么自己搭dns-rebinding的服务器,这里自己搭建,记录一下过程。
Step1: python dns server
这里用kunkun的,python2跑一下就行,注意vps的话可能需要先systemctl stop systemd-resolved,防止53端口占用
Step2: 设置域名解析
这里先配置cloudflare,生效快而且定义很简单。
添加两条记录就行。A记录指向跑python dns server的vps,NS记录指向A记录。
然后配置域名的dns server地址为图中cloudflare提供的两个dns server地址。
之前是用的国内的腾讯阿里云啥的,改了dns server后,生效都非常慢,于是这里去namesilo买了。设置都不用改,只需要改一下dns server就行。
随手测一下dns-rebinding,没问题。这里环境就先算是能用了。
漏洞分析
漏洞commit,可以看到对naddrs值大于naddrttls的值是进行了限制。
1 | https://github.com/nodejs/node/commit/a81aa37944a6b3efad49c15bbb62cbd1522631f4 |
根据描述也能大概看出,漏洞原因是返回的ttl数量大于nodejs中设置的ttl数组的容量,导致数组越界。
node中处理dns相关的逻辑使用的是c-ares库,我们跟一下c代码看看问题出在哪里。
往上跟,addrttls和naddrttls是调用时传入的,找一下调用:
在node对c-ares的调用中,传入了node里自定义的addrttls和naddrttls,而addrttls的是个256长度的数组。。。naddrttls自然时256了,当naddrs大于256时,赋值给naddrttls,就造成了数组越界。往下一看就有根据naddrttls长度去访问。。
addrttls存的是cname和a记录的ttl,因此这里只要让返回的ttls数量足够多就行。而DNS记录是只能返回一个ttl的,这怎么能让他ttl超过256个呢?我们去看一下c-ares的ares_parse_a_reply实现。
首先naddrs的值,可以看到由next链表长度决定。ai是一个ares_addrinfo结构体,由ares__parse_into_addrinfo2将输入buf解析得到。
进入ares__parse_into_addrinfo2,可以看到一个for循环根据ancount大小,将dns返回的ares_addrinfo_node插到ares_addrinfo.node链表尾。
那ancount的大小就是关键了。根据dns header结构,这个ancount是header中answer count的值,也就是相应的记录数量。这里我们改一下dns server让它返回256+的answer就行。
这里还挺有意思的,之前没看源码,以为要cname256次,就搭了俩dns server,互相cname,结果发现域名成环就不能继续cname了。于是又在域名前面random了一下,才可以cname256次,结果没卵用。。
现在可以response超过256的answer了,按道理在node里http.get一下应该就完了,结果。。咋断都断不下,死活不触发。调了一下node的源码,发现ares_init完了以后,就再也没进过ares。。。人都傻了
后来再仔细看代码,发现node在注册回调时,只有QueryWrap相关的操作才会触发漏洞函数。再看看node,在dns.js中,只有resolve系列的函数才绑定了query系列,因此,这个漏洞只有dns.resolve4(host)这种类似的形式才能触发。。。着实鸡肋
看完怎么触发,后边就简单了,随便写个demo:
1 | var express = require('express'); |
跑一下,然后搭建一个dns server返回超过256个ip就行。但是这里我遇到了一个问题,我用之前的dns server改大返回的answer数量,node却爆了connect error,这让我很懵逼。。
查了一下资料,根据rfc(回头一定好好啃rfc…),当udp包大于512字节时,会转为tcp方式去连接。而我们的dns server就监听了udp的53,也没有tcp的处理逻辑,自然就connect error了。
DNS协议从UDP切换到TCP的过程如下:
1 | 1、客户端向服务器发起UDP DNS请求; |
因此我在cloudfare里加了700多个a记录。。。这里就是dig的流量
这样就成功断下来啦!但不知道为什么这里AddrTTLToArray进去之前,addrttls的长度时256,进去后变长了,导致我加了很多a记录才成功
最后放个成功的图:
总结
这个洞本身有没有用呢?
毫无疑问,没有。
但我还是想去调试一下他,哪怕花了接近两天。一个原因是我对dns相关的知识并不是很熟想去看看,另一个原因很想学一下这种协议造成的dos是什么思路挖到的。这看一下,虽然就是个数组越界,但还是要知识面足够广,协议理解够深刻,才能处理各个bug,看到别人看不到的洞。