0x01 前言

近来发现一处mssql注入,可用xp_dirtree方式进行数据外带,但发现利用dnslog.cn等平台只可以监听到二级域名到请求
因为分配的二级域名用以区分不同的用户,请求的数据只能拼接到三级位置

于是想到了自己搭建一个简易dnslog,将外带数据置为二级域名并监听

0x02 搭建过程与坑

域名A的故事

其实在之前用CS搭建DNS隧道中,已经了解清楚了域名的解析过程,但过程中仍然发现了很多坑。

首先从域名说起,在写这文章之前 ,我已经在godaddy注册了一个顶级域名,并创建了A记录 sec 并解析到vps的ip上,然后创建了两个NS记录:ns1ns2指向A记录。

74017118.png

但在godaddy的DNS管理台发现,名称为@的NS记录,会指向两个值,如上图的ns77 和 ns78。这样的话,二级域名的解析中,无法完全指定为自己创建的DNS(即无法指向A记录)

另外,其中的SOA规定了这些NS记录中哪一台是主服务器,可以看到这里也是无法进行修改的。所以此时无法只通过这一个域名监听到想要的请求方式。只能接收到形如 xxx.ns1.dns.log 这样的解析记录

75032596.png

翻查了一些资料,无意间发现暗月大佬的博客有一篇《低成本搭建DNSlog》的文章。只需要通过Freenom这个域名商注册一个免费顶级域名,其中NS服务器可以自己配置解析ip。但购买时候发现,近期好像因为某种原因无法成功注册。

这里到,目前已有的域名暂且叫A。
如果还想要获取 xxx.dns.log 这样的记录该怎样呢

域名B

后来参考了BugScan的readme文档后,发现通过另一个方法还是很容易解决的
只需要拥有另一个域名,域名的NS服务器可以完全DIY指向A域名的ns就可以解决问题。

于是又苦苦地寻找一个合适的域名商,后来发现namesilo的域名 刚好可以满足需求,购买成功B域名后,可以进行NS服务器的配置
这里将NameServers配置为A域名下的两个记录ns1ns2即可

75939707.png

但是到此时,还没完成配置。A域名的ns1和ns2的记录类型需要修改为A记录,值指向自己的公网VPS服务器IP

76230885.png

这样,就完全控制了域名解析的最关键环节。最终NS请求会发送至你输入的ip对应的服务器,然后监听53的UDP请求

但还需要时间,毕竟世界各个地方的DNS服务器更新缓存需要一点点时间,无法及时同步

脚本监听

睡一觉过后,就差不多了,最后需要在VPS上监听上一层NS服务器发送过来的域名解析请求。
发现已经又前人拿python写过了脚本,蛮好用。这里就直接贴上代码

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date : 2014-06-29 03:01:25
# @Author : Your Name (you@example.org)
# @Link : http://example.org
# @Version : $Id$
import SocketServer
import struct
import socket as socketlib
# DNS Query
class SinDNSQuery:
    def __init__(self, data):
        i = 1
        self.name = ''
        while True:
            d = ord(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 RRS
class 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 frame
class 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 query
class 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();
            toip = None
            ifrom = "map"
            if namemap.__contains__(name):
                # If have record, response it
                # dns.setip(namemap[name])
                # socket.sendto(dns.getbytes(), self.client_address)
                toip = namemap[name]
            elif namemap.__contains__('*'):
                # Response default address
                # dns.setip(namemap['*'])
                # socket.sendto(dns.getbytes(), self.client_address)
                toip = namemap['*']
            else:
                # ignore it
                # socket.sendto(data, self.client_address)
                # socket.getaddrinfo(name,0)
                try:
                    toip = socketlib.getaddrinfo(name,0)[0][5][0]
                    ifrom = "sev"
                    # namemap[name] = toip
                    # print socket.getaddrinfo(name,0)
                except Exception, e:
                    print 'get ip fail'
            if toip:
                dns.setip(toip)
            print '%s: %s-->%s (%s)'%(self.client_address[0], name, toip, ifrom)
            socket.sendto(dns.getbytes(), 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 server
class 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 it
if __name__ == "__main__":
    sev = SinDNSServer()
    sev.addname('ns1.xxx.xxx', 'vps-ip') # add a A record
    sev.addname('ns2.xxx.xxx', 'vps-ip') # add a A record
    sev.addname('*', '127.0.0.1') # default address
    sev.start() # start DNS server

ns1和ns2的域名指向为vps的ip地址
星号* 是将其他任何域名请求,解析到127.0.0.1 (也可以设置其他的 如0.0.0.0)

开启监听后,返回物理机Ping下域名,发起dns请求

77420643.png

在VPS成功查看到接收的域名解析记录

81025157.png

0x03 后记

简易的dnslog搭建完毕后,那个注入点也发现了原因所在,参数的接收限定了长度。而dnslog.cn平台的地址,刚好达到payload长度的上限,所以和DNSlog平台本身并无关系 = = 但通过这次折腾,也对域名解析流程有了更清晰的认知。倘若以后真遇到那样的场景,也不会突然僵住

毕竟 只有熟悉规则,才能绕过规则。

标签: dnslog, python

添加新评论