DNS域传送漏洞(四) — python实现DNS axfr客户端[Query]

在DNS查询中,axfr类型是Authoritative Transfer的缩写,指请求传送某个区域的全部记录。

前面三篇日志中,笔者分别使用nmap、dig、nslookup来查询域传送记录。

本篇介绍自己动手,用python写一个简单的DNS客户端,仅实现axfr查询,并且只处理A记录。

DNS消息的格式

DNS请求和响应,都是由5个区块组成的,如下图所示:

    +---------------------+
    |        Header       |
    +---------------------+
    |       Question      | the question for the name server
    +---------------------+
    |        Answer       | RRs answering the question
    +---------------------+
    |      Authority      | RRs pointing toward an authority
    +---------------------+
    |      Additional     | RRs holding additional information
    +---------------------+

axfr请求的包,只填充header和Question区块就可以了。

Header区块的格式

Header的格式是这样的:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      ID                       |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    QDCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ANCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    NSCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ARCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

可以看到,每一行是16位,两个字节。整个Header的大小是12字节:

  1. ID是随机值,2个字节,介于1到65535之间。客户端发送一个随机值,DNS服务器会原样返回该ID。
  2. 第二行是几个标记的集合:QR标记该消息是Query还是Response,查询的时候填充0,响应的时候填充1。而RCODE是Response Code(响应码),只有该值为0的时候才表示查询成功了。得到服务器响应之后应该检查这个值。
  3. QDCOUNT填充Question的个数、ANCOUNT填充Answer的个数,NSCOUNT填充DNS权威服务器的个数,ARCOUNT填充附加记录的个数。对于本文要实现的axfr查询,分别填充: 1, 0, 0, 0。

下图是笔者用WireShak抓包得到一个axfr请求的Header:

dns_axfr_query

图中Additional RRs的值为1,在尾部附加了一些额外的参数。编码的简化,可以不提供。

Question区块的格式

Question区块的基本格式是:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                     QNAME                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     QTYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     QCLASS                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

QNAME是需要查询的域名,比如nwpu.edu.cn。它由一个Lable序列来表示。

格式是这样的:

用点号“.”把域名分割为几个Label,每个label前面带上字符串长度,nwpu.edu.cn.cn最终变为:

4nwpu3edu2cn

结尾处跟一个字节的空白。笔者抓包截图如下:

dns_query_name_format

04表示有4个字符,而6e 77 70 75正好是nwpu。

QTYPE是一个类型常量,axfr记录的值是252。

QCLASS设置为1,表示Inernet。

Python组合一个Query消息

介绍完上面的内容,我们已经可以组一个查询消息了。函数如下:

def gen_query(domain):
    import random
    TRANS_ID = random.randint(1, 65535)       # random ID
    FLAGS = 0; QDCOUNT = 1; ANCOUNT = 0; NSCOUNT = 0; ARCOUNT = 0
    data = struct.pack(
        '!HHHHHH',
        TRANS_ID, FLAGS,QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT
        )
    query = ''
    for label in domain.strip().split('.'):
        query += struct.pack('!B', len(label)) + label.lower()
    query += '\x00'    # end of domain name
    data += query
    global LEN_QUERY
    LEN_QUERY = len(query)    # length of query section
    q_type = 252    # Type AXFR = 252
    q_class = 1    # CLASS IN
    data += struct.pack('!HH', q_type, q_class)
    data = struct.pack('!H', len(data) ) + data    # first 2 bytes should be length
    return data

struct.pack用来格式化字符串,第一个参数中的惊叹号“!”表示字节序使用network (= big-endian)。

B代表Byte,一个字节。H代表unsigned short,两个字节。

消息开头的前两个字节,需要提供消息正文的长度。

因为在decode response时,还要用到Query的长度,所以我将它保存到了一个全局的LEN_QUERY变量中。

本文参考:https://tools.ietf.org/html/rfc1035

《DNS域传送漏洞(四) — python实现DNS axfr客户端[Query]》上有2条评论

  1. 您好,最近正在学习DNS相关知识,对您的这篇博文很感兴趣,故学习了一下。
    现请教您一个问题,在python3.7中运行整个程序发现抛出错误:
    File “C:\axfr_client.py”, line 89, in
    data = gen_query(sys.argv[2])
    File “C:\axfr_client.py”, line 26, in gen_query
    query += struct.pack(‘!B’, len(label)) + label.lower()
    TypeError: can’t concat str to bytes
    自己尝试了很多办法都不能成功,请您指点

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注