云服务器价格_云数据库_云主机【优惠】最新活动-搜集站云资讯

云存储_阿里云管理平台_三重好礼

小七 141 0

DNS解析器,去见fuzzer

在CloudFlare,我们是miegitdns集线器去DNS库,我们会尽可能的为它的发展做出贡献。因此,当Dmitry Vyukov发布go fuzz并开始发现go标准库中的数十个bug时,我们的任务是明确的。热绒毛模糊化是一种测试软件的技术,通过不断地输入自动变化的输入。对于C/C++,米莎·ZaleWSKY的AFL模糊工具非常成功地使用了仪器源覆盖来判断哪些突变将程序推到了新的路径上,最终击中了许多未被测试的分支。gofuzz将同样的技术应用到go程序中,通过重写源代码来检测它(就像godebug那样)。afl-fuzz和go-fuzz的一个有趣的区别是前者通常对未修改程序的文件输入进行操作,而后者则要求您编写一个go函数并将输入传递给该函数。前者通常为每个输入分叉一个新的进程,后者不经常重新启动就一直调用函数。这种差异并没有强有力的技术原因(事实上,afl最近获得了类似go fuzz行为的能力),但这可能是由于它们所处的生态系统不同:go程序经常公开有良好文档记录、行为良好的api,使测试人员能够编写一个好的包装器,不会在调用过程中污染状态。此外,Go程序通常更容易深入研究,也更容易预测,这显然要归功于GC和内存管理,但也要归功于社区对意想不到的全局状态和副作用的排斥。另一方面,许多遗留的C代码基非常难处理,因此简单稳定的文件输入接口值得在性能上进行折衷。回到我们的DNS库。我们的内部DNS服务器RRDNS使用github.com/miekgs/dns对于所有的解析需求,它已经被证明能够胜任这项任务。然而,它在边缘案例中有点脆弱,并且有过对错误数据包恐慌的记录。谢天谢地,这是Go,而不是BIND C,我们可以承受recover()恐慌,而不必担心最终会出现疯狂的内存状态。这就是我们要做的func ParseDNSPacketSafely(buf[]字节,消息*旧的。Msg)(错误错误){延迟函数(){惊慌失措:=恢复()如果惊慌失措!等于零{错误=错误。新的("ParseError")}}()错误=消息解包(buf)返回}我们看到了一个使库更健壮的机会,所以我们编写了这个初始的简单模糊函数:func Fuzz(rawMsg[]字节)int{消息:=&dns消息{}如果unpackErr:=消息解包(rawMsg);解包错误!等于零{返回0}如果Uu,封隔器=消息包();包装工!等于零{println("无法打包邮件")喷出。倾倒(消息)打包机(紧急)}返回1}为了创建一个初始输入的语料库,我们采用了压力和回归测试套件并使用github.com/miekg/pcap每包写一个文件。主包装进口("加密/随机""编码/hex""日志""操作系统""斯特康夫""github.com/miekg/pcap")func fatalIfErr(错误){如果出错!等于零{日志。致命(错误)}}函数main(){句柄,错误:=pcap.OpenOffline(操作系统参数[1] )法塔利费尔(err)b:=make([]字节,4)_,错误=兰德。读(二)法塔利费尔(err)前缀:=十六进制编码字符串(二)i:=0对于pkt:=把手。下一个();pkt!=无;包装=把手。下一个() {密码解码()f、 错误:=操作系统创建("p_"+前缀+""u"+strconv.Itoa公司(i) )法塔利费尔(err)_,err=f.写入(包装有效载荷)法塔利费尔(err)fatalIfErr(f.Close())我++}}由JD Hancock提供的CC BY 2.0图像然后我们用gofuzz编译了Fuzz函数,并在实验室服务器上启动了fuzzer。go fuzz做的第一件事是通过丢弃触发相同代码路径的包来最小化语料库,然后它开始变异输入并在循环中将它们传递给fuzz()。不失败(返回1)和扩展代码覆盖率的突变被保留并迭代。当程序死机时,会保存一个小报告(输入和输出),然后重新启动程序。如果你想了解更多关于go fuzz的信息,请看作者的GopherCon谈话或阅读自述。崩盘开始浮出水面,大多是"指数出界"。当程序经常崩溃时,gofuzz变得非常缓慢和无效,所以当cpu烧坏时,我开始修复bug。在某些情况下,我决定更改一些解析器模式,例如重新排序并使用len()而不是保留偏移量。然而,这些可能会破坏更改—我还远远不够完美,所以我调整了Fuzz函数来监视新旧解析器之间的差异,如果新解析器开始拒绝好的数据包或更改其行为,则会崩溃:func Fuzz(rawMsg[]字节)int{变量(消息,msgOld=&dns消息{}, &旧的。Msg{}buf,bufOld=make([]字节,100000),make([]字节,100000)res,resOld[]字节unpackErr,unpackerold错误打包机,打包机错误)解包错误=消息解包(rawMsg)unpackerold=ParseDNSPacketSafely(rawMsg,msgOld)如果解包错误!=nil和unpackerold!等于零{返回0}如果解包错误!=无解包错误()=="dns:无序NSEC块"{//97b0a31-重写NSEC位图[un]打包以说明出现故障返回0}如果解包错误!=无解包错误()=="dns:错误的rdlength"{//3157620-unpackStructValue:删除rdlen,改为reslice msg返回0}如果解包错误!=无解包错误()=="dns:错误地址系列"{//f37c7ea-在解包时拒绝错误的EDNS0_子网系列(不仅在包上)返回0}如果解包错误!=无解包错误()=="dns:错误的网络掩码"{//6d5de0a-EDNS0_子网:重构网络掩码处理返回0}如果解包错误!=nil和unpackerold==nil{println("新代码无法解包有效数据包")恐慌(unpackErr)}res,封隔器=消息包缓冲(buf)如果包装工!等于零{println("无法打包邮件")喷出。倾倒(消息)恐慌(打包机)}如果unpackerold==nil{转售,packErrOld=微软Gold.PackBuffer(布福德)如果packErrOld==nil&&!字节。相等(再出售){println("有效数据包的新代码更改行为:")println()印刷品(十六进制转储(资源)印刷品(十六进制转储(转售)操作系统退出(一)}}返回1}我对健壮性的提高很满意,但是由于我们在RRDNS中使用了ParseDNSPacketSafely包装器,所以我没想到会发现安全漏洞。我错了!DNS名称由标签组成,通常用点隔开。为了节省空间,标签可以被指向其他名称的指针代替,这样如果我们知道我们编码了example.com网站在偏移量15处,网站可包装为+PTR(15)。我们在处理指向空名称的指针时发现了一个错误:当遇到名称结尾(0x00)时,如果没有读取任何标签,则会以特殊情况返回"."(空名称)。问题是,这个特殊情况不知道指针,它会指示解析器从指向空名称的末尾而不是原始名称的末尾继续读取。例如,如果解析器在偏移量60处遇到指向偏移量15的指针,并且msg[15]==0x00,则解析将从偏移量16而不是61继续,从而导致无限循环。这是一个潜在的拒绝服务漏洞。A) 解析到位置60,在那里可以找到DNS名称| ... |15 | 16 | 17 |。。。|58 | 59 | 60 | 61|| ... |0x00 | | |。。。|| |->15 ||------------------------------------------------->B) 沿着指针指向位置15| ... |15 | 16 | 17 |。。。|58 | 59 | 60 | 61|| ... |0x00 | | |。。。|| |->15 ||^ |------------------------------------------C) 返回空名称".",特殊情况下触发D) 错误地从位置16恢复而不是61| ... |15 | 16 | 17 |。。。|58 | 59 | 60 | 61|| ... |0x00 | | |。。。|| |->15 ||-------------------------------->E) 冲洗并重复当我们修补服务器时,我们私下把补丁发给了库维护人员,一旦修复完成,我们就打开了一个公关。(在我们发布RRDNS更新时,Miek独立发现并修复了两个bug。)不仅仅是撞车和吊死由于其灵活的fuzzingapi,gofuzz不仅可以很好地搜索崩溃的输入,而且可以用于探索边缘情况很麻烦的所有场景。有用的应用程序包括通过向Fuzz()函数添加崩溃断言来检查输出验证,到比较解包包包链的两端,甚至比较同一功能的两个不同版本或实现的行为。例如,在准备启动DNSSEC引擎时,我遇到了一个奇怪的错误,它只会发生在生产或压力测试中:NSEC记录本来只在其类型位图中设置了几个位,但有时看起来像这样德莱格.菲利波.io. 在NSEC 3600\000中。德莱格.菲利波.io. NS WKS HINFO TXT AAAA LOC SRV证书SSHFP RRSIG NSEC TLSA HIP 60型61 SPF我们的"打包并发送"代码池[]字节缓冲区可以减少GC和分配的混乱,因此缓冲区传递到dns.msg.PackBuffer(buf[]byte)以前的用法可能是"脏"的。变量bufpool=同步池{New:func()接口{}{返回make([]字节,0,2048)},}[...]数据:=布夫普尔。快().([]字节)推迟布夫普尔。放(数据)如果是数据,则err=r。响应.PackBuffer(数据);错误!等于零{但是,buf不是一个由零组成的数组,有些人没有处理它github.com/miekgs/dns封隔器,包括NSEC-rdata-one,将只显示或显示钻头,而不清除本应缺失的钻头。案例`dns:"nsec"`:最后一个窗口:=uint16(0)长度:=uint16(0)对于j:=0;j