TL; DR
mysql_real_escape_string()
volere fornirà alcuna protezione (e potrebbe inoltre confondere i tuoi dati) se:
La NO_BACKSLASH_ESCAPES
modalità SQL di MySQL è abilitata (quale potrebbe essere, a meno che non si selezioni esplicitamente un'altra modalità SQL ogni volta che ci si connette ); e
i valori letterali delle stringhe SQL vengono citati usando la virgoletta doppia "
caratteri .
Questo è stato archiviato come bug # 72458 ed è stato corretto in MySQL v5.7.6 (vedere la sezione intitolata " The Saving Grace ", di seguito).
Questo è un altro, (forse meno?) Oscuro EDGE CASE !!!
In omaggio a all'ottima risposta di @ircmaxell (in realtà, si suppone che sia adulazione e non plagio!), Adotterò il suo formato:
L'attacco
A partire da una dimostrazione ...
mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"'); // could already be set
$var = mysql_real_escape_string('" OR 1=1 -- ');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
Ciò restituirà tutti i record dalla test
tabella. Una dissezione:
Selezione di una modalità SQL
mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');
Come documentato in String Literals :
Esistono diversi modi per includere caratteri di citazione all'interno di una stringa:
Un " '
" all'interno di una stringa citata con " '
" può essere scritto come " ''
".
Un " "
" all'interno di una stringa citata con " "
" può essere scritto come " ""
".
Precede il carattere di citazione da un carattere di escape (" \
").
Un “ '
” all'interno di una stringa citata con “ "
” non ha bisogno di alcun trattamento speciale e non deve essere raddoppiato o evitato. Allo stesso modo, " "
" all'interno di una stringa citata con " '
" non ha bisogno di alcun trattamento speciale.
Se la modalità SQL del server include NO_BACKSLASH_ESCAPES
, la terza di queste opzioni, che è il solito approccio adottato da mysql_real_escape_string()
, non è disponibile: è invece necessario utilizzare una delle prime due opzioni. Si noti che l'effetto del quarto proiettile è che si deve necessariamente conoscere il personaggio che verrà utilizzato per citare il letterale al fine di evitare di confondere i propri dati.
Il payload
" OR 1=1 --
Il payload inizia questa iniezione letteralmente con il "
personaggio. Nessuna codifica particolare. Nessun personaggio speciale. Nessun byte strano.
mysql_real_escape_string ()
$var = mysql_real_escape_string('" OR 1=1 -- ');
Fortunatamente, mysql_real_escape_string()
controlla la modalità SQL e regola di conseguenza il suo comportamento. Vedi libmysql.c
:
ulong STDCALL
mysql_real_escape_string(MYSQL *mysql, char *to,const char *from,
ulong length)
{
if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES)
return escape_quotes_for_mysql(mysql->charset, to, 0, from, length);
return escape_string_for_mysql(mysql->charset, to, 0, from, length);
}
Quindi escape_quotes_for_mysql()
viene invocata una diversa funzione sottostante, se ilNO_BACKSLASH_ESCAPES
viene utilizzata la modalità SQL . Come accennato in precedenza, tale funzione deve sapere quale carattere verrà utilizzato per citare il letterale per ripeterlo senza causare la ripetizione letterale dell'altro carattere di citazione.
Tuttavia, questa funzione presuppone arbitrariamente che la stringa verrà quotata utilizzando il '
carattere a virgoletta singola . Vedi charset.c
:
/*
Escape apostrophes by doubling them up
// [ deletia 839-845 ]
DESCRIPTION
This escapes the contents of a string by doubling up any apostrophes that
it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
effect on the server.
// [ deletia 852-858 ]
*/
size_t escape_quotes_for_mysql(CHARSET_INFO *charset_info,
char *to, size_t to_length,
const char *from, size_t length)
{
// [ deletia 865-892 ]
if (*from == '\'')
{
if (to + 2 > to_end)
{
overflow= TRUE;
break;
}
*to++= '\'';
*to++= '\'';
}
Quindi, lascia i "
caratteri a virgoletta doppia non toccati (e raddoppia tutti i '
caratteri a virgoletta singola ) indipendentemente dal carattere reale che viene utilizzato per citare il letterale ! Nel nostro caso $var
rimane esattamente la stessa come l'argomento che è stato fornito a mysql_real_escape_string()
-E 'come se nessuno fuga ha avuto luogo a tutti .
The Query
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
Qualcosa di formale, la query di rendering è:
SELECT * FROM test WHERE name = "" OR 1=1 -- " LIMIT 1
Come diceva il mio dotto amico: congratulazioni, hai appena attaccato con successo un programma usando mysql_real_escape_string()
...
Il cattivo
mysql_set_charset()
non posso fare a meno, poiché ciò non ha nulla a che fare con i set di caratteri; né può mysqli::real_escape_string()
, dal momento che è solo un wrapper diverso attorno a questa stessa funzione.
Il problema, se non è già ovvio, è che la chiamata a mysql_real_escape_string()
non è in grado di sapere con quale personaggio verrà citato il valore letterale, che viene lasciato allo sviluppatore per decidere in un secondo momento. Quindi, in NO_BACKSLASH_ESCAPES
modalità, non c'è letteralmente modo che questa funzione possa sfuggire ad ogni input in modo sicuro per l'uso con virgolette arbitrarie (almeno, non senza raddoppiare i caratteri che non richiedono il raddoppio e quindi la confusione dei dati).
Il brutto
La situazione peggiora. NO_BACKSLASH_ESCAPES
potrebbe non essere così raro in natura a causa della necessità del suo utilizzo per la compatibilità con lo standard SQL (ad es. vedere la sezione 5.3 della specifica SQL-92 , vale a dire la <quote symbol> ::= <quote><quote>
produzione grammaticale e la mancanza di un significato speciale dato alla barra rovesciata). Inoltre, il suo uso è stato esplicitamente raccomandato come soluzione alternativa al bug (da tempo risolto) descritto dal post di ircmaxell. Chissà, alcuni DBA potrebbero persino configurarlo per essere attivato di default come mezzo per scoraggiare l'uso di metodi di escape errati come addslashes()
.
Inoltre, la modalità SQL di una nuova connessione viene impostata dal server in base alla sua configurazione (che un SUPER
utente può modificare in qualsiasi momento); pertanto, per essere certi del comportamento del server, è necessario specificare sempre in modo esplicito la modalità desiderata dopo la connessione.
La grazia salvifica
Finché imposti sempre esplicitamente la modalità SQL in modo da non includere NO_BACKSLASH_ESCAPES
, o citi i letterali di stringa MySQL usando il carattere a virgoletta singola, questo bug non può allevare la sua brutta testa: rispettivamente escape_quotes_for_mysql()
non verrà usato, o il suo presupposto su quali caratteri di citazione richiedono la ripetizione sarà avere ragione.
Per questo motivo, consiglio a chiunque utilizzi di NO_BACKSLASH_ESCAPES
abilitare anche la ANSI_QUOTES
modalità, poiché forzerà l'uso abituale di valori letterali di stringa a virgoletta singola. Si noti che ciò non impedisce l'iniezione di SQL nel caso in cui vengano utilizzati valori letterali tra virgolette doppie, ma semplicemente riduce la probabilità che ciò accada (poiché le query normali e non dannose fallirebbero).
In DOP, sia la sua funzione equivalente PDO::quote()
che l'emulatore di istruzioni preparate fanno appello, il mysql_handle_quoter()
che fa esattamente questo: assicura che il letterale evaso sia citato tra virgolette singole, quindi puoi essere certo che il PDO sia sempre immune da questo errore.
A partire da MySQL v5.7.6, questo errore è stato corretto. Vedi registro delle modifiche :
Funzionalità aggiunta o modificata
Esempi sicuri
Presi insieme al bug spiegato da ircmaxell, i seguenti esempi sono completamente sicuri (supponendo che uno stia usando MySQL dopo 4.1.20, 5.0.22, 5.1.11; o che non stia usando una codifica di connessione GBK / Big5) :
mysql_set_charset($charset);
mysql_query("SET SQL_MODE=''");
$var = mysql_real_escape_string('" OR 1=1 /*');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
... perché abbiamo selezionato esplicitamente una modalità SQL che non include NO_BACKSLASH_ESCAPES
.
mysql_set_charset($charset);
$var = mysql_real_escape_string("' OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
... perché stiamo citando la nostra stringa letterale con virgolette singole.
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(["' OR 1=1 /*"]);
... perché le dichiarazioni preparate con DOP sono immuni da questa vulnerabilità (e anche quelle di ircmaxell, a condizione che si stia utilizzando PHP≥5.3.6 e che il set di caratteri sia stato impostato correttamente nel DSN o che l'emulazione delle istruzioni preparate sia stata disabilitata) .
$var = $pdo->quote("' OR 1=1 /*");
$stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");
... perché la quote()
funzione di DOP non solo sfugge al letterale, ma lo cita anche (in '
caratteri a virgoletta singola ); notare che per evitare il bug di ircmaxell in questo caso, è necessario utilizzare PHP≥5.3.6 e aver impostato correttamente il set di caratteri nel DSN.
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "' OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();
... perché le dichiarazioni preparate da MySQLi sono sicure.
Avvolgendo
Pertanto, se tu:
- utilizzare dichiarazioni preparate native
O
- utilizzare MySQL v5.7.6 o successivo
O
... allora dovresti essere completamente al sicuro (vulnerabilità al di fuori dell'ambito della fuga di stringhe da parte).