Temi


Debolezze


  1. Presenza di output rivelatore. L’app web consente di riconoscere alcuni stati tramite il tipo di output mostrato. → Regole del silenzio
  2. Viene stampata ogni riga del set di risultati, anche se avrebbe senso stamparne sempre e solo al massimo una. → Regola del silenzio / Non fidarti dell’input
  3. L’applicazione costruisce ed esegue una query SQL usando il parametro della query string name. Esso non è controllato in alcun modo, portando alla possibilità di iniettare codice SQL arbitrario in cascata → Non fidarti dell’input

🐲 Strategia di attacco


1. Si analizzano i canali di output

Viene stampata una tabella a schermo, e l’URL contiene il parametro name=.

2. Analisi codice sorgente

Analizzando il codice sorgente si nota che:

3. Tentativi di input malizioso

Con un codice ben calibrato l’attaccante potrebbe mandare in crash la query SQL, e quindi come visto dal codice sorgente, non verrà più stampata alcuna tabella.

name='

Con il seguente input la query va in crash. L’attaccante anche senza avere a disposizione il codice sorgente è in grado di osservare la differenza di comportamento e intuire l’errore.

Nell’implementazione attuale vengono stampate tutte le righe prodotte dalla query SQL. Con un input ben calibrato, l’attaccante è in grado di ottenere tutte le righe della tabella.

Si segue la solita sintassi per iniezione di codice:

INPUT_LEGITTIMO + CARATTERE_SEPARATORE_STATEMENT + STATEMENT_SQL_ARBITRARIO + CARATTERE_CHIUSURA.

Per agganciare uno statement sql ad un altro, esistono diversi modi:


Si può ad esempio usare OR per iniettare una tautologia → espressione logica sempre vera (es: 1=1). Tale iniezione rende la clausola WHERE della query SQL sempre vera.

name=' or 1=1%23

Viene stampato ogni elemento della tabella.


Si può usare anche l’operatore UNION come separatore dei comandi. Esso unisce l’output di più query SQL omogenee:

Dopo una UNION non ci si limita a inserire una condizione di verità, ma un vero e proprio statement SQL arbitrario per:

<aside> 💡

*Enumeration of a DBMS refers to the process of systematically gathering information about a database system to understand its structure, users, tables, and potential vulnerabilities. This is a crucial phase in penetration testing and database security auditing.

</aside>

Come fare a rispettare l’omogeneità delle query?

Per recuperare il numero di colonne usate dalle query originali si possono usare due metodi:

  1. Uso della clausola ORDER BY
  2. Inserimento di uno statement SELECT con un numero crescente di colonne

La clausola ORDER BY consente di ordinare il risultato di una query in base a una o più colonne. Tali colonne possono essere specificate per nome o per indice. In questo modo, se si immette un indice superiore al numero di colonne, viene emesso un errore.

Si inietta quindi la clausola ORDER BY con indice crescente finché non si produce l’errore. L’indice -1 sarà il numero di colonne.

name=root' ORDER BY 6 %23

All’indice 6, la tabella non viene più stampata e sappiamo che quando l’input produce un errore, avviene proprio questo comportamento. Le colonne quindi sono → 5.

Alternativamente si può iniettare uno statement SELECT banale che recupera un numero di colonne crescenti, tramite UNION. Nella SELECT si può utilizzare il valore nullo NULL che è compatibile con tutti gli altri tipi di dato. Se il numero di colonne usatto nella SELECT iniettata è uguale a quello della query originale, la tabella viene stampata, altrimenti no.

name=root' UNION SELECT NULL, NULL, NULL, NULL, NULL %23

Ancora una volta le colonne sono → 5.


Prima di poter iniettare statement SQL arbitrari, è necessario capire quali colonne sono riflesse nell’output (tra le 5 recuperate nella query originale). Si inietta nuovamente una SELECT, ma questa volta con valori costanti e diversi: possono essere valori interi o caratteri, in funzione del tipo di dati usati dall’applicazione.

name=root' UNION SELECT 1,2,3,4,5 %23
name=root' UNION SELECT 'a','b','c','d','e' %23

Dall’output notiamo che le colonne riflesse sono le prime 3 (1, 2, 3 oppure a, b, c).


Siamo finalmente pronti ad iniettare statement SQL arbitrari. Sarà sufficiente inserire nelle colonne riflesse:

Gli obiettivi degli statement sono:

Il workflow per l’enumerazione di un DBMS è il seguente:

  1. Identificazione versione DBMS (funzione di sistema version())
  2. Identificazione nome database attuale (funzione di sistema database())
  3. Identificazione username connesso al DBMS (funzione current_user())
  4. Identificazione tabelle del database attuale (database di sistema information_schema)
  5. Identificazione colonne di una tabella interessante
  6. Dump di una tabella interessante

Si inietta una prima query per ottenere le prime 3 informazioni:

name=root' UNION SELECT version(),database(),current_user(),4,5 %23

Si ottengono i risultati:

Il database di sistema information_schema contiene lo schema di tutti i database serviti dal server, in particolare:

Per estrarre i nomi delle tabelle del database attuale (exercises) occorre eseguire la query seguente:

SELECT table_name FROM information_schema.tables WHERE table_schema='exercises';

Lo si esegue quindi nell’URL:

name=root' UNION SELECT table_name,2,3,4,5 FROM information_schema.tables 
WHERE table_schema='exercises' %23

Il database exercises contiene una sola tabella di nome users.

La tabella columns di information_schema definisce la struttura di un campo della tabella. Il campo column_name di columns contiene il nome del campo della tabella.

Per estrarre dunque le colonne della tabella users si deve iniettare la query seguente:

SELECT column_name from information_schema.columns where table_schema='exercises'
AND table_name='users';

Si inietta come sopra e si ottengono i risultati:

| --- |

Si può procedere al dump della tabella interessante trovata. Basterà eseguire una SELECT delle colonne significative dalla tabella di interesse.

SELECT id,name,passwd FROM users;

Che diventa:

name=root' UNION SELECT id,name,passwd,4,5 from users %23

Si possono ora vedere in chiaro id, nome utente e password degli utenti memorizzati nella tabella users.