0x01 DNS解析类型
DNS服务器的解析大致分为几类,A
、AAAA
、CNAME
、MX
、NS
、TXT
、SRV
、SOA
、PTR
等,大致为
A记录: 将域名指向一个IPV4的ip地址
CNAME记录: 将该域名指向另外一个域名
MX记录: 建立电子邮箱服务,将指向邮件服务地址
NS记录: 域名解析记录,如果要将子域名指定某个域名服务器来解析,需要设置NS记录
TXT记录: 可任意填写,一般为某主机或域名设置使用,用于做一些备注
AAAA记录: 将域名指向一个IPV6记录
PTR记录: 记录A记录的逆向记录,又称做IP反查记录或指针记录,负责将IP反向解析为域名
SOA记录: 表示开始权限记录,记录用于在众多NS记录中那一台是主服务器
0x02 dnslib
dnslib为python的一个用于编码/解码的DNS有线格式数据包的库。dnslib最主要的就是用于一个dns解析器。其主要的类为DNSServer()。我们可以使用如下的命令来构造我们的第一个dns服务器
resolver = BaseResolver()
logger = DNSLogger(prefix=False)
server = DNSServer(resolver,port=8053,address="localhost",logger=logger)
server.start_thread()
q = DNSRecord.question("example.com")
a = q.send("localhost",8053)
print(DNSRecord.parse(a))
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: ...
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;example.com. IN A
同时我们可以自定义我们dns服务器需要解析域名的地址,可以使用如下代码
class TestResolver:
def resolve(self,request,handler):
reply = request.reply()
reply.add_answer(*RR.fromZone("example IN A 1.2.3.4"))
return reply
resolver = TestResolver()
server = DNSServer(resolver,port=8053,address="localhost",logger=logger,tcp=True)
server.start_thread()
a = q.send("localhost",8053,tcp=True)
因此我们可以将example.com
的域名指向1.2.3.4
首先跟一下DNSServer
类,其为自定义DNS解析的主要类,其调用了socketserver模块来进行发包
class DNSServer(object):
def __init__(self,resolver,
address="",
port=53,
tcp=False,
logger=None,
handler=DNSHandler,
server=None):
if not server:
if tcp:
server = TCPServer
else:
server = UDPServer
self.server = server((address,port),handler)
self.server.resolver = resolver
self.server.logger = logger or DNSLogger()
class UDPServer(socketserver.ThreadingMixIn,socketserver.UDPServer):
allow_reuse_address = True
class TCPServer(socketserver.ThreadingMixIn,socketserver.TCPServer):
allow_reuse_address = True
resolver则为解析器或者转发的作用,同时继承了socketserver中UPDP和TCP的发包.
dnslib.client
client脚本为dnslib中用来查询DNS信息文件,其使用方法为
python -m dnslib.client example.com
跟一下cilent查询的整个过程,从client.py
文件中,代码首先执行到了
q = DNSRecord(q=DNSQuestion("christa.top",getattr(QTYPE,args.qtype)))
这一行来,DNSQuestion
类中对域名和域名的了行做了初始化
class DNSQuestion(object):
def __init__(self,qname=None,qtype=1,qclass=1):
self.qname = qname
self.qtype = qtype
self.qclass = qclass
紧接着到DNSRecord
里面,进行了认证等一系列的初始化
然后,设置解析地址和端口
之后,进入到send阶段,send函数大致如下
def send(self,dest,port=53,tcp=False,timeout=None,ipv6=False):
"""
Send packet to nameserver and return response
"""
data = self.pack()
if ipv6:
inet = socket.AF_INET6
else:
inet = socket.AF_INET
if tcp:
if len(data) > 65535:
raise ValueError("Packet length too long: %d" % len(data))
data = struct.pack("!H",len(data)) + data
sock = socket.socket(inet,socket.SOCK_STREAM)
if timeout is not None:
sock.settimeout(timeout)
sock.connect((dest,port))
sock.sendall(data)
response = sock.recv(8192)
length = struct.unpack("!H",bytes(response[:2]))[0]
while len(response) - 2 < length:
response += sock.recv(8192)
sock.close()
response = response[2:]
else:
sock = socket.socket(inet,socket.SOCK_DGRAM)
if timeout is not None:
sock.settimeout(timeout)
sock.sendto(self.pack(),(dest,port))
response,server = sock.recvfrom(8192)
sock.close()
return response
将打包好的数据发送到执行的DNS解析器上,那么跟踪DNS查询数据的打包过程,继续跟进data = self.pack()
代码
def pack(self):
self.set_header_qa()
buffer = DNSBuffer()
self.header.pack(buffer)
for q in self.questions:
q.pack(buffer)
for rr in self.rr:
rr.pack(buffer)
for auth in self.auth:
auth.pack(buffer)
for ar in self.ar:
ar.pack(buffer)
return buffer.data
在set_header_qa
中将去除查询的数组的长度
def set_header_qa(self):
self.header.q = len(self.questions)
self.header.a = len(self.rr)
self.header.auth = len(self.auth)
self.header.ar = len(self.ar)
之后进入DNSBuffer
的构造,在DNSBuffer类中继承了Buffer类,主要将字符转化为bytes类型
然后进入到pack打包阶段
def pack(self,buffer):
buffer.pack("!HHHHHH",self.id,self.bitmap,
self.q,self.a,self.auth,self.ar)
其中每一个参数的调用为
改函数为struct !HHHHHH
打包
之后将需要解析的域名进行struct !B
打包
最后将需要查询的type,qclass进行打包,也就是查询域名类型,类似A
,TXT
,AAAA
记录等进行!HH打包
最后得出查询数据bytearray(b'\xd8\xff\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07christa\x03top\x00\x00\x01\x00\x01')
,最后发送给8.8.8.8的53端口上
因此dnslib查询的过程为此
dnslib.proxy
该模块为一个代理模式,用于查看DNS的解析记录,用法为
python -m dnslib.proxy
dnslib.intercept
改模块为自定义一个DNS的返回模式,可用于DNS重绑定漏洞,其原理为当网站查询一个网址的时候其原来指向一个外网地址,之后对网络资源进行操作的时候,重新将网址的地址指向到127.0.0.1,
这样就读取了服务器自身的文件,也就造成了SSRF漏洞,只需要我们将需要的域名设置一个NS记录,设置它的域名解析器为我们另一个网址,用阿里云域名举例来说
这样将dns.christa.top
的域名指名到另一个域名来接解析,因此设置执行dns服务器返回的DNS的解析结果即可,可以使用命令
python3 -m dnslib.intercept -i "dns.christa.top IN A 127.0.0.2"
之后进行DNS查询
可以看到dns.christa.top
域名被成功指向到127.0.0.2
地址
DNS存储IP的形式是五元组,dnslib的例子为
[(<DNSLabel: 'dns.christa.top.'>, 'A', <DNS RR: 'dns.christa.top.' rtype=A rclass=IN ttl=60 rdata='127.0.0.2'>)]