绕过安全设备的0day
2019-08-09 19:00:00 Author: mp.weixin.qq.com(查看原文) 阅读量:63 收藏

介绍

DNS域名系统是互联网关键的基础设施之一,它是一个将域名与IP地址互相映射的全球分布数据库。对于恶意DNS的过滤、检测恶意网站域名、僵尸网络和网络隐秘通道发现是安全防护设备中必不可少的一种手段。

DNS服务器怎么配置,可以学习课程:DNS服务器配置长按下列二维码或点击文末“阅读原文”可操作学习。

长按二维码开始学习

原理

RFC 1035规定了域名每个标签不超过63字节,域名总长不超过255字节。可以含有任意8bit值,通常情况下域名标签由英文字母、数字和连字符构成。RFC 2181进一步明确了,DNS本身不对域名所含字符内容进行限制。一些文献中验证了ISC BIND等常用DNS服务器软件对二进制域名的支持。尽管在RFC1 123 之中对于DNS软件支持无法转换为可打印格式的资源记录,内部存储不能使用文本格式。由于Letter Digit Hyphen规则的域名含有可打印字符,如此产生了两种问题,其一为大多数程序对于域名的处理采用字符串函数,可能会对于某些特定结束字符进行处理(例如C语言中对于\000进行处理),其二DNS服务器对于特殊字符进行处理后依然返回解析结果,某些程序过滤恶意DNS域名并未考虑。

测试方法

在对DNS服务器测试时,我们想被测的服务器发送正常和带有特殊字符的DNS两种请求,如果DNS服务器两种响应存区别则证明其二失败,否则成功。

01序列名称

用PYTHON socketserver和struct开发简单的DNS服务器进行测试,再使用DNSPython模块作为DNS请求的测试。DNS服务器脚本详情请见附录。

测试使用的版本:

Dnspython 1.16.0

Python 2.17.16

首先进行的是正常的测试,使用www.aa.com.www.bb.com能够正常的解析。如图1所示。

图1

然后使用带有特殊字符的www.aa.com\x09.www.bb.com解析结果,如图2所示。

图2

通过返回信息不难得出”\x09”等同于”.”。为了进一步分析,通过数据报文查看传输的请求。正常的DNS请求如图3所示。

图3

带有特殊字符的请求如图4所示。

图4

图5

危害

一.隐藏恶意软件域名:

通过该方法可绕过基于DNS流量检测的流量分析软件、算法和相关安全设备及在线文件分析系统,通过构造加入特殊字符的DNS请求,既保证了域名成功解析,又保护了恶意域名难以被发现。

二.隐藏DNS隐蔽通道

将伪造源地址的方法与本文域名欺骗方法结合,可以起到更好的DNS隧道流量隐蔽效果,通过伪造源地址来分散DNS隧道流量,对内网数据泄密和远程控制隧道加入了新的挑战。

三.绕过DNS过滤

利用这个方法进行DNS过滤设备的穿透具有一定的可行性,目前已发现大量带有域名过滤的安全设备.存在被绕过的风险。

修复

1. 通过DNSPython修复,在dns\resolver.py->Resolver()->query() 第802行,加入过滤异常的特殊字符。

2. 安全设备中扩大过滤DNS请求特殊字符的范围

附录

DNS简单服务器:

import socketserverimport struct# DNS Queryclass SinDNSQuery:    def __init__(self, data):        i = 1        self.name = ''        while True:            d = data[i]            if d == 0:                break;            if d < 32:                self.name = self.name + '.'            else:                self.name = self.name + chr(d)            i = i + 1        self.querybytes = data[0:i + 1]        (self.type, self.classify) = struct.unpack('>HH', data[i + 1:i + 5])        self.len = i + 5    def getbytes(self):        return self.querybytes + struct.pack('>HH', self.type, self.classify)# DNS Answer RRS# this class is also can be use as Authority RRS or Additional RRSclass SinDNSAnswer:    def __init__(self, ip):        self.name = 49164        self.type = 1        self.classify = 1        self.timetolive = 190        self.datalength = 4        self.ip = ip    def getbytes(self):        res = struct.pack('>HHHLH', self.name, self.type, self.classify, self.timetolive, self.datalength)        s = self.ip.split('.')        res = res + struct.pack('BBBB', int(s[0]), int(s[1]), int(s[2]), int(s[3]))        return res# DNS frame# must initialized by a DNS query frameclass SinDNSFrame:    def __init__(self, data):        (self.id, self.flags, self.quests, self.answers, self.author, self.addition) = struct.unpack('>HHHHHH', data[0:12])        self.query = SinDNSQuery(data[12:])    def getname(self):        return self.query.name    def setip(self, ip):        self.answer = SinDNSAnswer(ip)        self.answers = 1        self.flags = 33152    def getbytes(self):        res = struct.pack('>HHHHHH', self.id, self.flags, self.quests, self.answers, self.author, self.addition)        res = res + self.query.getbytes()        if self.answers != 0:            res = res + self.answer.getbytes()        return res# A UDPHandler to handle DNS queryclass SinDNSUDPHandler(socketserver.BaseRequestHandler):    def handle(self):        data = self.request[0].strip()        dns = SinDNSFrame(data)        socket = self.request[1]        namemap = SinDNSServer.namemap        if(dns.query.type==1):            # If this is query a A record, then response it
name = dns.getname(); if namemap.__contains__(name): # If have record, response it dns.setip(namemap[name]) socket.sendto(dns.getbytes(), self.client_address) elif namemap.__contains__('*'): # Response default address dns.setip(namemap['*']) socket.sendto(dns.getbytes(), self.client_address) else: # ignore it socket.sendto(data, self.client_address) else: # If this is not query a A record, ignore it socket.sendto(data, self.client_address)# DNS Server# It only support A record query# user it, U can create a simple DNS serverclass SinDNSServer: def __init__(self, port=53): SinDNSServer.namemap = {} self.port = port def addname(self, name, ip): SinDNSServer.namemap[name] = ip def start(self): HOST, PORT = "0.0.0.0", self.port server = socketserver.UDPServer((HOST, PORT), SinDNSUDPHandler) server.serve_forever()# Now, test itif __name__ == "__main__": sev = SinDNSServer() sev.addname('www.aa.com', '192.168.0.1') # add a A record sev.addname('www.aa.com.www.bb.com', '192.168.0.2') # add a A record sev.addname('*', '192.168.0.5') # default address sev.start() # start DNS server# Now, U can use "nslookup" command to test it# Such as "nslookup www.aa.com"
声明:作者初衷用于分享与普及网络知识,若读者因此作出任何危害网络安全行为后果自负,与合天智汇及原作者无关。

别忘了投稿哦

大家有好的技术原创文章

欢迎投稿至邮箱:[email protected]

合天会根据文章的时效、新颖、文笔、实用等多方面评判给予200元-800元不等的稿费哦

有才能的你快来投稿吧!

了解投稿详情点击——重金悬赏 | 合天原创投稿涨稿费啦!

点击“阅读全文”,开始学习

文章来源: http://mp.weixin.qq.com/s?__biz=MjM5MTYxNjQxOA==&amp;mid=2652851626&amp;idx=1&amp;sn=3ae8417856345aad35c91f08d7bed292&amp;chksm=bd5931678a2eb871a551df85968260aa51de69f4f95bf11922908751b3c9b3de7b688506eccb#rd
如有侵权请联系:admin#unsafe.sh