[php] SQL injection che aggira mysql_real_escape_string ()



Answers

La risposta breve è sì, sì, c'è un modo per aggirare mysql_real_escape_string() .

Per CASI DI BORDO OSSCURE !!!

La lunga risposta non è così facile. Si basa su un attacco dimostrato qui .

L'attacco

Quindi, iniziamo mostrando l'attacco ...

mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

In alcune circostanze, questo restituirà più di 1 riga. Analizziamo cosa sta succedendo qui:

  1. Selezione di un set di caratteri

    mysql_query('SET NAMES gbk');
    

    Perché questo attacco funzioni, abbiamo bisogno della codifica che il server si aspetta dalla connessione sia per codificare ' come in ASCII, cioè 0x27 sia per avere un carattere il cui byte finale è un ASCII \ ie 0x5c . Come risulta, ci sono 5 codifiche supportate in MySQL 5.6 di default: cp932 , gb2312 , gbk , gbk e sjis . Selezioneremo gbk qui.

    Ora, è molto importante notare l'uso di SET NAMES qui. Questo imposta il set di caratteri sul server . Se mysql_set_charset() la chiamata alla funzione API C mysql_set_charset() , mysql_set_charset() bene (sulle versioni di MySQL dal 2006). Ma più sul perché in un minuto ...

  2. Il carico utile

    Il carico utile che useremo per questa iniezione inizia con la sequenza di byte 0xbf27 . In gbk , questo è un carattere multibyte non valido; in latin1 , è la stringa ¿' . Notare che in latin1 e gbk , 0x27 da solo è un carattere letterale.

    Abbiamo scelto questo payload perché, se avessimo chiamato addslashes() , inseriremmo un ASCII \ ie 0x5c , prima del carattere. Quindi ci 0xbf5c27 con 0xbf5c27 , che in gbk è una sequenza di due caratteri: 0xbf5c seguita da 0x27 . O in altre parole, un carattere valido seguito da un carattere senza caratteri ' . Ma non stiamo usando addslashes() . Quindi, al prossimo passo ...

  3. mysql_real_escape_string ()

    La chiamata API C a mysql_real_escape_string() differisce da addslashes() in quanto conosce il set di caratteri di connessione. Quindi può eseguire correttamente l'escaping per il set di caratteri che il server si aspetta. Tuttavia, fino a questo punto, il cliente pensa che stiamo ancora usando latin1 per la connessione, perché non l'abbiamo mai detto diversamente. Abbiamo detto al server che stiamo usando gbk , ma il client pensa ancora che sia latin1 .

    Quindi la chiamata a mysql_real_escape_string() inserisce la barra rovesciata, e abbiamo un carattere ' libero ' nel nostro contenuto "escapato"! In effetti, se dovessimo guardare $var nel set di caratteri gbk , gbk :

    縗' OR 1=1 /*

    Quale è esattamente ciò che richiede l'attacco.

  4. La domanda

    Questa parte è solo una formalità, ma ecco la query resa:

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
    

Congratulazioni, hai appena attaccato con successo un programma usando mysql_real_escape_string() ...

Il cattivo

La situazione peggiora. PDO impostazione predefinita, PDO emula le istruzioni preparate con MySQL. Ciò significa che dal punto di vista del client, fondamentalmente fa uno sprintf attraverso mysql_real_escape_string() (nella libreria C), il che significa che il seguente risultato sarà un'iniezione riuscita:

$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Ora, vale la pena notare che è possibile prevenire ciò disabilitando le dichiarazioni preparate emulate:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Questo di solito si tradurrà in una vera dichiarazione preparata (cioè i dati inviati in un pacchetto separato dalla query). Tuttavia, si tenga presente che PDO eseguirà silenziosamente il fallback delle istruzioni di emulazione che MySQL non è in grado di preparare in modo nativo: quelle che possono essere listed nel manuale, ma attenzione a selezionare la versione del server appropriata).

Il brutto

All'inizio ho detto che avremmo potuto prevenire tutto ciò se avessimo usato mysql_set_charset('gbk') invece di SET NAMES gbk . E questo è vero se si utilizza una versione di MySQL dal 2006.

Se usi una versione precedente di MySQL, un bug in mysql_real_escape_string() significa che caratteri multibyte non validi come quelli nel nostro payload sono stati trattati come byte singoli per scopi di escape anche se il client era stato correttamente informato della codifica della connessione e così questo attacco avrebbe comunque avuto successo. Il bug è stato corretto in MySQL 4.1.20 , 5.0.22 e 5.1.11 .

Ma la parte peggiore è che PDO non ha esposto l'API C per mysql_set_charset() fino alla versione 5.3.6, quindi nelle versioni precedenti non può impedire questo attacco per ogni comando possibile! Ora è esposto come parametro DSN .

La grazia salvifica

Come abbiamo detto all'inizio, affinché questo attacco funzioni, la connessione al database deve essere codificata utilizzando un set di caratteri vulnerabili. utf8mb4 non è vulnerabile e tuttavia può supportare ogni carattere Unicode: quindi puoi scegliere di usarlo, ma è disponibile solo da MySQL 5.5.3. Un'alternativa è utf8 , che non è vulnerabile e può supportare l'intero piano multilingue multilingue di Unicode.

In alternativa, puoi abilitare la modalità SQL NO_BACKSLASH_ESCAPES , che (tra le altre cose) altera l'operazione di mysql_real_escape_string() . Con questa modalità abilitata, 0x27 verrà sostituito con 0x2727 anziché 0x5c27 e quindi il processo di escape non può creare caratteri validi in nessuna delle codifiche vulnerabili in cui non esistevano in precedenza (cioè 0xbf27 è ancora 0xbf27 ecc.) - quindi il server continuerà a funzionare rifiuta la stringa come non valida. Tuttavia, vedere la risposta di @ eggyal per una diversa vulnerabilità che può sorgere dall'utilizzo di questa modalità SQL.

Esempi sicuri

I seguenti esempi sono sicuri:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Perché il server si aspetta utf8 ...

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Perché abbiamo impostato correttamente il set di caratteri in modo che il client e il server corrispondano.

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Perché abbiamo disattivato le istruzioni preparate emulate.

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Perché abbiamo impostato correttamente il set di caratteri.

$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

Perché MySQLi fa sempre le dichiarazioni preparate.

Avvolgendo

Se tu:

  • Utilizzare Modern Versions of MySQL (late 5.1, all 5.5, 5.6, etc) AND mysql_set_charset() / $mysqli->set_charset() / parametro charset DSN di PDO (in PHP ≥ 5.3.6)

O

  • Non utilizzare un set di caratteri vulnerabili per la codifica della connessione (si usa solo utf8 / latin1 / ascii / etc)

Sei al sicuro al 100%.

Altrimenti, sei vulnerabile anche se stai usando mysql_real_escape_string() ...

Question

Esiste una possibilità di iniezione SQL anche quando si utilizza la funzione mysql_real_escape_string() ?

Considera questa situazione campione. SQL è costruito in PHP come questo:

$login = mysql_real_escape_string(GetFromPost('login'));
$password = mysql_real_escape_string(GetFromPost('password'));

$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";

Ho sentito numerose persone dirmi che un codice del genere è ancora pericoloso e che è possibile violare anche con la funzione mysql_real_escape_string() utilizzata. Ma non riesco a pensare a qualche possibile exploit?

Iniezioni classiche come questa:

aaa' OR 1=1 --

non lavorare.

Sei a conoscenza di una possibile iniezione che avrebbe superato il codice PHP sopra?




Beh, non c'è nulla che possa attraversarlo, oltre alla wildcard % . Potrebbe essere pericoloso se si stesse usando l'istruzione LIKE poichè l'autore dell'attacco potrebbe mettere solo % come login se non lo si filtra, e dovrebbe semplicemente applicare una password a bruteforce a qualcuno dei propri utenti. Le persone spesso suggeriscono di utilizzare dichiarazioni preparate per renderlo sicuro al 100%, poiché i dati non possono interferire con la query stessa in quel modo. Ma per domande così semplici probabilmente sarebbe più efficiente fare qualcosa come $login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);




Related