Come posso impedire l'iniezione di SQL in PHP?


2773

Se l'input dell'utente viene inserito senza modifiche in una query SQL, l'applicazione diventa vulnerabile all'iniezione SQL , come nell'esempio seguente:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

Questo perché l'utente può inserire qualcosa di simile value'); DROP TABLE table;--e la query diventa:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

Cosa si può fare per evitare che ciò accada?

Risposte:


8961

Utilizzare istruzioni preparate e query con parametri. Queste sono istruzioni SQL che vengono inviate e analizzate dal server di database separatamente da qualsiasi parametro. In questo modo è impossibile per un utente malintenzionato iniettare codice SQL dannoso.

Fondamentalmente hai due opzioni per raggiungere questo obiettivo:

  1. Utilizzo di PDO (per qualsiasi driver di database supportato):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute([ 'name' => $name ]);
    
    foreach ($stmt as $row) {
        // Do something with $row
    }
    
  2. Utilizzando MySQLi (per MySQL):

    $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
    $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
    $stmt->execute();
    
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        // Do something with $row
    }
    

Se ti stai collegando a un database diverso da MySQL, esiste una seconda opzione specifica del driver a cui puoi fare riferimento (ad esempio, pg_prepare()e pg_execute()per PostgreSQL). La DOP è l'opzione universale.


Impostazione corretta della connessione

Si noti che quando si utilizza PDOper accedere a un database MySQL le istruzioni preparate realmente non vengono utilizzate per impostazione predefinita . Per risolvere questo problema devi disabilitare l'emulazione di istruzioni preparate. Un esempio di creazione di una connessione tramite PDO è:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'password');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Nell'esempio sopra la modalità di errore non è strettamente necessaria, ma si consiglia di aggiungerla . In questo modo la sceneggiatura non si fermerà con un Fatal Errorquando qualcosa va storto. E offre allo sviluppatore la possibilità di catcherrori che sono thrown come PDOExceptions.

Ciò che è obbligatorio , tuttavia, è la prima setAttribute()riga, che dice a PDO di disabilitare le dichiarazioni preparate emulate e di usare le dichiarazioni preparate reali . Questo assicura che l'istruzione e i valori non vengano analizzati da PHP prima di inviarlo al server MySQL (dando a un possibile aggressore nessuna possibilità di iniettare SQL dannoso).

Sebbene sia possibile impostare le charsetopzioni del costruttore, è importante notare che le versioni "precedenti" di PHP (prima della 5.3.6) hanno ignorato silenziosamente il parametro charset nel DSN.


Spiegazione

L'istruzione SQL a cui si passa prepareviene analizzata e compilata dal server di database. Specificando i parametri (uno ?o un parametro denominato come :namenell'esempio sopra) si comunica al motore di database su cui si desidera filtrare. Quindi quando si chiama execute, l'istruzione preparata viene combinata con i valori dei parametri specificati.

La cosa importante qui è che i valori dei parametri sono combinati con l'istruzione compilata, non con una stringa SQL. L'iniezione SQL funziona inducendo lo script a includere stringhe dannose quando crea SQL da inviare al database. Quindi, inviando l'SQL effettivo separatamente dai parametri, si limita il rischio di finire con qualcosa che non si intendeva.

Tutti i parametri inviati quando si utilizza un'istruzione preparata verranno semplicemente trattati come stringhe (anche se il motore di database potrebbe eseguire alcune ottimizzazioni, quindi i parametri potrebbero finire anche come numeri). Nell'esempio sopra, se la $namevariabile contiene 'Sarah'; DELETE FROM employeesil risultato sarebbe semplicemente una ricerca della stringa "'Sarah'; DELETE FROM employees"e non si otterrà una tabella vuota .

Un altro vantaggio dell'utilizzo di istruzioni preparate è che se si esegue più volte la stessa istruzione nella stessa sessione, questa verrà analizzata e compilata una sola volta, ottenendo alcuni guadagni di velocità.

Oh, e dal momento che hai chiesto come farlo per un inserto, ecco un esempio (usando PDO):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute([ 'column' => $unsafeValue ]);

È possibile utilizzare istruzioni preparate per query dinamiche?

Sebbene sia ancora possibile utilizzare istruzioni preparate per i parametri della query, la struttura della query dinamica non può essere parametrizzata e alcune funzioni della query non possono essere parametrizzate.

Per questi scenari specifici, la cosa migliore da fare è utilizzare un filtro whitelist che limita i possibili valori.

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}

86
Solo per aggiungere perché non l'ho visto da nessun'altra parte qui, un'altra linea di difesa è un firewall per applicazioni web (WAF) che può avere delle regole da impostare per cercare attacchi di iniezione sql:
jkerak,

37
Inoltre, la documentazione ufficiale di mysql_query consente di eseguire solo una query, quindi qualsiasi altra query oltre; viene ignorato. Anche se questo è già deprecato, ci sono molti sistemi in PHP 5.5.0 e che possono usare questa funzione. php.net/manual/en/function.mysql-query.php
Randall Valenciano,

12
Questa è una cattiva abitudine ma è una soluzione post-problema: non solo per l'iniezione SQL ma per qualsiasi tipo di iniezioni (ad esempio c'era un foro di iniezione del modello di visualizzazione in F3 framework v2) se hai un vecchio sito Web pronto o l'app sta soffrendo dai difetti di iniezione, una soluzione è quella di riassegnare i valori dei tuoi vars predefiniti supperglobal come $ _POST con valori di escape al bootstrap. Dal PDO, è ancora possibile scappare (anche per i framework di oggi): substr ($ pdo-> quote ($ str, \ PDO :: PARAM_STR), 1, -1)
Alix

15
Questa risposta è priva della spiegazione di ciò che è un'istruzione preparata - una cosa - è un hit prestazionale se usi molte dichiarazioni preparate durante la tua richiesta e talvolta rappresenta un hit prestazionale 10x. Il caso migliore sarebbe usare la DOP con l'associazione dei parametri disattivata, ma la preparazione dell'istruzione è disattivata.
donis,

6
Usare PDO è meglio, nel caso in cui tu stia usando la query diretta assicurati di usare mysqli :: escape_string
Kassem Itani

1652

Avviso obsoleto: il codice di esempio di questa risposta (come il codice di esempio della domanda) utilizza l' MySQLestensione PHP , che è stata deprecata in PHP 5.5.0 e rimossa interamente in PHP 7.0.0.

Avviso di sicurezza : questa risposta non è in linea con le migliori pratiche di sicurezza. L'escaping è inadeguato per impedire l'iniezione SQL , utilizzare invece le istruzioni preparate . Utilizzare la strategia descritta di seguito a proprio rischio. (Inoltre, è mysql_real_escape_string()stato rimosso in PHP 7.)

Se stai usando una versione recente di PHP, l' mysql_real_escape_stringopzione descritta di seguito non sarà più disponibile (anche se mysqli::escape_stringè un equivalente moderno). In questi giorni l' mysql_real_escape_stringopzione avrebbe senso solo per il codice legacy su una vecchia versione di PHP.


Hai due opzioni: sfuggire ai caratteri speciali nel tuo unsafe_variableo usare una query con parametri. Entrambi ti proteggerebbero dall'iniezione SQL. La query con parametri è considerata la migliore pratica ma richiederà di passare a una nuova estensione MySQL in PHP prima di poterla utilizzare.

Copriremo prima la stringa di impatto inferiore che fuoriesce da una.

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

Vedi anche, i dettagli della mysql_real_escape_stringfunzione.

Per utilizzare la query con parametri, è necessario utilizzare MySQLi anziché le funzioni MySQL . Per riscrivere il tuo esempio, avremmo bisogno di qualcosa di simile al seguente.

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

La funzione chiave che vorresti leggere lì sarebbe mysqli::prepare.

Inoltre, come altri hanno suggerito, potresti trovare utile / più semplice aumentare uno strato di astrazione con qualcosa come il DOP .

Si noti che il caso richiesto è piuttosto semplice e che casi più complessi possono richiedere approcci più complessi. In particolare:

  • Se si desidera modificare la struttura dell'SQL in base all'input dell'utente, le query con parametri non sono utili e l'escaping richiesto non è coperto da mysql_real_escape_string. In questo tipo di caso, sarebbe meglio passare l'input dell'utente attraverso una whitelist per garantire che siano ammessi solo valori "sicuri".
  • Se usi numeri interi dall'input dell'utente in una condizione e segui l' mysql_real_escape_stringapproccio, soffrirai del problema descritto da Polynomial nei commenti seguenti. Questo caso è più complicato perché i numeri interi non sarebbero racchiusi tra virgolette, quindi potresti occuparti convalidando che l'input dell'utente contiene solo cifre.
  • Probabilmente ci sono altri casi di cui non sono a conoscenza. Potresti scoprire che questa è una risorsa utile su alcuni dei problemi più sottili che puoi incontrare.

1
usando mysql_real_escape_stringè abbastanza o devo usare anche parametrizzato?
Peiman F.

5
@peimanF. mantenere una buona pratica nell'uso di query parametrizzate, anche su un progetto locale. Con le query parametrizzate hai la garanzia che non ci sarà iniezione SQL. Ma tieni a mente che dovresti disinfettare i dati per evitare il recupero fasullo (cioè l'iniezione XSS, come l'inserimento di un codice HTML in un testo) con htmlentitiesad esempio
Goufalite

2
@peimanF. Buona pratica per query parametrizzate e valori di bind, ma la vera stringa di escape è buona per ora
Richard

Capisco l'inclusione di mysql_real_escape_string()completezza, ma non sono un fan di elencare per primo l'approccio più soggetto a errori. Il lettore potrebbe semplicemente prendere rapidamente il primo esempio. Meno male che ora è deprecato :)
Steen Schütt il

1
@ SteenSchütt - Tutte le mysql_*funzioni sono obsolete. Sono stati sostituiti da funzioni simili mysqli_* , come ad esempio mysqli_real_escape_string.
Rick James,

1073

Ogni risposta qui copre solo una parte del problema. In effetti, ci sono quattro diverse parti di query che possiamo aggiungere in modo dinamico a SQL: -

  • una stringa
  • un numero
  • un identificatore
  • una parola chiave di sintassi

E le dichiarazioni preparate ne coprono solo due.

Ma a volte dobbiamo rendere la nostra query ancora più dinamica, aggiungendo anche operatori o identificatori. Quindi, avremo bisogno di diverse tecniche di protezione.

In generale, tale approccio alla protezione si basa sulla whitelisting .

In questo caso, ogni parametro dinamico dovrebbe essere codificato nello script e scelto da quel set. Ad esempio, per eseguire un ordine dinamico:

$orders  = array("name", "price", "qty"); // Field names
$key = array_search($_GET['sort'], $orders)); // if we have such a name
$orderby = $orders[$key]; // If not, first one will be set automatically. 
$query = "SELECT * FROM `table` ORDER BY $orderby"; // Value is safe

Per facilitare il processo ho scritto una funzione di supporto per la whitelist che svolge tutto il lavoro in una riga:

$orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name");
$query  = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe

Esiste un altro modo per proteggere gli identificatori: la fuga, ma preferisco attenermi alla whitelist come approccio più solido ed esplicito. Tuttavia, finché hai un identificatore citato, puoi sfuggire al carattere di citazione per renderlo sicuro. Ad esempio, per impostazione predefinita per mysql è necessario raddoppiare il carattere di citazione per evitarlo . Per altri DBMS le regole di escape sarebbero diverse.

Tuttavia, c'è un problema con le parole chiave della sintassi SQL (come AND, DESCe simili), ma la lista bianca sembra l'unico approccio in questo caso.

Quindi, una raccomandazione generale può essere formulata come

  • Qualsiasi variabile che rappresenta un dato letterale di dati SQL (o, per dirla semplicemente - una stringa SQL o un numero) deve essere aggiunta tramite un'istruzione preparata. Nessuna eccezione.
  • Qualsiasi altra parte della query, come una parola chiave SQL, una tabella o un nome di campo o un operatore, deve essere filtrata attraverso una lista bianca.

Aggiornare

Sebbene vi sia un accordo generale sulle migliori pratiche relative alla protezione dell'iniezione SQL, esistono anche molte cattive pratiche. E alcuni di loro sono troppo radicati nelle menti degli utenti di PHP. Ad esempio, in questa stessa pagina ci sono (sebbene invisibili per la maggior parte dei visitatori) più di 80 risposte eliminate , tutte rimosse dalla community a causa di cattiva qualità o promozione di pratiche scadenti e obsolete. Peggio ancora, alcune delle cattive risposte non vengono cancellate, ma piuttosto prospere.

Ad esempio, ci sono (1) ci sono (2) ancora (3) molte (4) risposte (5) , inclusa la seconda risposta più votata che suggerisce la fuga manuale di stringhe - un approccio obsoleto che si è dimostrato insicuro.

Oppure c'è una risposta leggermente migliore che suggerisce solo un altro metodo di formattazione delle stringhe e addirittura la vanta come la panacea definitiva. Anche se ovviamente non lo è. Questo metodo non è migliore della normale formattazione delle stringhe, tuttavia mantiene tutti i suoi svantaggi: è applicabile solo alle stringhe e, come qualsiasi altra formattazione manuale, è essenzialmente una misura facoltativa, non obbligatoria, soggetta a errori umani di qualsiasi tipo.

Penso che tutto ciò sia dovuto a una superstizione molto antica, supportata da autorità come OWASP o il manuale PHP , che proclama l'uguaglianza tra qualunque "fuga" e protezione dalle iniezioni di SQL.

Indipendentemente da ciò che il manuale di PHP ha detto per anni, *_escape_stringnon rende in alcun modo i dati sicuri e non è mai stato previsto. Oltre ad essere inutile per qualsiasi parte SQL diversa dalla stringa, l'escaping manuale è errato, perché è manuale in quanto opposto all'automazione.

E OWASP lo rende ancora peggio, sottolineando di sfuggire all'input dell'utente che è una totale assurdità: non dovrebbero esserci parole del genere nel contesto della protezione dell'iniezione. Ogni variabile è potenzialmente pericolosa, indipendentemente dalla fonte! Oppure, in altre parole, ogni variabile deve essere formattata correttamente per essere inserita in una query, indipendentemente dalla fonte. È la destinazione che conta. Nel momento in cui uno sviluppatore inizia a separare le pecore dalle capre (pensando che una determinata variabile sia "sicura" o no), fa il suo primo passo verso il disastro. Per non parlare del fatto che anche la formulazione suggerisce una fuga di massa nel punto di ingresso, simile alla caratteristica delle citazioni molto magiche - già disprezzata, deprecata e rimossa.

Quindi, diversamente da qualsiasi "fuga", le istruzioni preparate sono la misura che effettivamente protegge dall'iniezione SQL (quando applicabile).


848

Consiglio di utilizzare PDO (PHP Data Objects) per eseguire query SQL con parametri.

Non solo protegge dall'iniezione SQL, ma accelera anche le query.

E usando PDO piuttosto che mysql_, mysqli_e pgsql_funzioni, rendi la tua applicazione un po 'più astratta dal database, nel raro caso in cui devi cambiare provider di database.


619

Usa PDOe prepara domande.

( $connè un PDOoggetto)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();

549

Come puoi vedere, le persone suggeriscono di utilizzare al massimo le dichiarazioni preparate. Non è sbagliato, ma quando la query viene eseguita una sola volta per processo, ci sarà una leggera penalità per le prestazioni.

Stavo affrontando questo problema, ma penso di averlo risolto in modo molto sofisticato - il modo in cui gli hacker usano per evitare di usare le virgolette. L'ho usato insieme a dichiarazioni preparate emulate. Lo uso per prevenire tutti i tipi di possibili attacchi di iniezione SQL.

Il mio approccio:

  • Se ti aspetti che l'input sia intero, assicurati che sia veramente intero. In un linguaggio di tipo variabile come PHP è molto importante. Puoi usare ad esempio questa soluzione molto semplice ma potente:sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • Se ti aspetti qualcos'altro dall'intero esadecimale . Se lo esagoni, sfuggirai perfettamente a tutti gli input. In C / C ++ c'è una funzione chiamata mysql_hex_string(), in PHP puoi usare bin2hex().

    Non preoccuparti del fatto che la stringa con escape avrà una dimensione 2x della sua lunghezza originale perché anche se lo usi mysql_real_escape_string, PHP deve allocare la stessa capacità ((2*input_length)+1), che è la stessa.

  • Questo metodo esadecimale viene spesso utilizzato quando si trasferiscono dati binari, ma non vedo alcun motivo per non utilizzarlo su tutti i dati per prevenire attacchi di iniezione SQL. Nota che devi anteporre i dati con 0xo utilizzare UNHEXinvece la funzione MySQL .

Quindi, ad esempio, la query:

SELECT password FROM users WHERE name = 'root'

Diventerà:

SELECT password FROM users WHERE name = 0x726f6f74

o

SELECT password FROM users WHERE name = UNHEX('726f6f74')

Hex è la fuga perfetta. Non c'è modo di iniettare.

Differenza tra funzione UNHEX e prefisso 0x

C'è stata qualche discussione nei commenti, quindi voglio finalmente chiarirlo. Questi due approcci sono molto simili, ma per certi versi sono leggermente diversi:

Il prefisso ** 0x ** può essere utilizzato solo per colonne di dati come char, varchar, text, block, binary, ecc .
Inoltre, il suo utilizzo è un po 'complicato se stai per inserire una stringa vuota. Dovrai sostituirlo completamente con ''o riceverai un errore.

UNHEX () funziona su qualsiasi colonna; non devi preoccuparti della stringa vuota.


I metodi esadecimali vengono spesso utilizzati come attacchi

Si noti che questo metodo esadecimale viene spesso utilizzato come attacco di iniezione SQL in cui numeri interi sono proprio come stringhe e sono scappati con mysql_real_escape_string. Quindi puoi evitare l'uso delle virgolette.

Ad esempio, se fai qualcosa del genere:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

un attacco può iniettarti molto facilmente . Considera il seguente codice iniettato restituito dallo script:

SELEZIONA ... DOVE id = -1 unione seleziona tutti table_name da information_schema.tables

e ora basta estrarre la struttura della tabella:

SELEZIONA ... DOVE id = -1 unione seleziona tutti nome_colonna da information_schema.column dove table_name = 0x61727469636c65

E quindi basta selezionare i dati desiderati. Non è bello?

Ma se il codificatore di un sito iniettabile lo escludesse, nessuna iniezione sarebbe possibile perché la query sarebbe simile a questa: SELECT ... WHERE id = UNHEX('2d312075...3635')


6
@SumitGupta Sì, l'hai fatto. MySQL non si concatena con +ma con CONCAT. E per le prestazioni: non penso che influisca sulle prestazioni perché mysql deve analizzare i dati e non importa se l'origine è stringa o esadecimale
Zaffy,

11
@YourCommonSense Non capisci il concetto ... Se vuoi avere una stringa in mysql, la citi in questo modo 'root'o puoi esadecimarla 0x726f6f74MA se vuoi un numero e inviarlo come stringa probabilmente scriverai '42' non CHAR (42 ) ... '42' in esadecimale sarebbe 0x3432non0x42
Zaffy

7
@YourCommonSense Non ho niente da dire ... solo lol ... se vuoi ancora provare esadecimale su campi numerici, vedi il secondo commento. Scommetto con te che funzionerà.
Zaffy,

2
La risposta afferma chiaramente che non funzionerà in questo modo con valori interi, il motivo è che bin2hex converte il valore passato in una stringa (e quindi bin2hex (0) è 0x30 e non 0x03) - questa è probabilmente la parte che ti confonde . Se lo segui, funziona perfettamente (almeno sul mio sito, testato con 4 diverse versioni mysql su macchine debian, da 5.1.xa 5.6.x). Dopo tutto, l'esadecimale è solo il modo di rappresentare, non il valore;)
griffin

5
@YourCommonSense non hai ancora capito? Non è possibile utilizzare 0x e concat perché se la stringa è vuota si finirà con un errore. Se vuoi una semplice alternativa alla tua domanda, prova questaSELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ')
Zaffy,

499

Avviso obsoleto: il codice di esempio di questa risposta (come il codice di esempio della domanda) utilizza l' MySQLestensione PHP , che è stata deprecata in PHP 5.5.0 e rimossa interamente in PHP 7.0.0.

Avviso di sicurezza : questa risposta non è in linea con le migliori pratiche di sicurezza. L'escaping è inadeguato per impedire l'iniezione SQL , utilizzare invece le istruzioni preparate . Utilizzare la strategia descritta di seguito a proprio rischio. (Inoltre, è mysql_real_escape_string()stato rimosso in PHP 7.)

IMPORTANTE

Il modo migliore per prevenire l'iniezione di SQL è usare le istruzioni preparate invece di scappare , come dimostra la risposta accettata .

Esistono librerie come Aura.Sql e EasyDB che consentono agli sviluppatori di utilizzare più facilmente le istruzioni preparate. Per ulteriori informazioni sui motivi per cui le istruzioni preparate sono migliori per interrompere l'iniezione SQL , fare riferimento a questo mysql_real_escape_string()bypass e alle vulnerabilità di SQL Injection SQL Unicode risolte di recente in WordPress .

Prevenzione delle iniezioni - mysql_real_escape_string ()

PHP ha una funzione appositamente creata per prevenire questi attacchi. Tutto quello che dovete fare è usare il boccone di una funzione, mysql_real_escape_string.

mysql_real_escape_stringaccetta una stringa che verrà utilizzata in una query MySQL e restituisce la stessa stringa con tutti i tentativi di iniezione SQL evitati in modo sicuro. Fondamentalmente, sostituirà le virgolette fastidiose (') che un utente potrebbe inserire con un sostituto sicuro di MySQL, una citazione salvata \'.

NOTA: per utilizzare questa funzione devi essere connesso al database!

// Connettiti a MySQL

$name_bad = "' OR 1'"; 

$name_bad = mysql_real_escape_string($name_bad);

$query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";


$name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; 

$name_evil = mysql_real_escape_string($name_evil);

$query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
echo "Escaped Evil Injection: <br />" . $query_evil;

Puoi trovare maggiori dettagli in MySQL - Prevenzione dell'iniezione SQL .


30
Questo è il meglio che puoi fare con l'estensione mysql legacy. Per il nuovo codice, ti consigliamo di passare a mysqli o DOP.
Álvaro González,

7
Non sono d'accordo con questa "funzione appositamente creata per prevenire questi attacchi". Penso che lo mysql_real_escape_stringscopo sia quello di consentire di creare una query SQL corretta per ogni stringa di dati di input. Prevenzione sql-injection è l'effetto collaterale di questa funzione.
sectus,

4
non si usano le funzioni per scrivere stringhe di dati di input corrette. Basta scrivere quelli corretti che non necessitano di escape o che sono già stati salvati. mysql_real_escape_string () potrebbe essere stato progettato con lo scopo che menzioni in mente, ma il suo unico valore è prevenire l'iniezione.
Nazca,

17
AVVERTIMENTO! mysql_real_escape_string() non è infallibile .
Eggyal

9
mysql_real_escape_stringè ora deprecato, quindi non è più un'opzione praticabile. Sarà rimosso in futuro da PHP. È meglio passare a ciò che consiglia la gente di PHP o MySQL.
jww

462

Potresti fare qualcosa di base come questo:

$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Questo non risolverà tutti i problemi, ma è un ottimo trampolino di lancio. Ho lasciato fuori elementi ovvi come il controllo dell'esistenza della variabile, il formato (numeri, lettere, ecc.).


28
Ho provato il tuo esempio e per me funziona benissimo. Potresti chiarire "questo non risolverà tutti i problemi"
Chinook,

14
Se non citate la stringa, è comunque iniettabile. Prendi $q = "SELECT col FROM tbl WHERE x = $safe_var";ad esempio. Impostazione $safe_vardi 1 UNION SELECT password FROM usersopere in questo caso a causa della mancanza di citazioni. È anche possibile iniettare stringhe nella query utilizzando CONCATe CHR.
Polinomio

4
@Polynomial Completamente giusto, ma lo vedrei semplicemente come un uso sbagliato. Finché lo usi correttamente, funzionerà sicuramente.
glglgl

22
AVVERTIMENTO! mysql_real_escape_string() non è infallibile .
Eggyal

8
mysql_real_escape_stringè ora deprecato, quindi non è più un'opzione praticabile. Sarà rimosso in futuro da PHP. È meglio passare a ciò che consiglia la gente di PHP o MySQL.
jww

380

Qualunque cosa tu finisca per usare, assicurati di controllare che i tuoi input non siano già stati rovinati da magic_quoteso altri rifiuti ben intenzionati e, se necessario, eseguili stripslasheso altro per disinfettarli.


11
Infatti; correre con magic_quotes attivato incoraggia solo la cattiva pratica. Tuttavia, a volte non è sempre possibile controllare l'ambiente a quel livello - o non si ha accesso per gestire il server o l'applicazione deve coesistere con applicazioni che (rabbrividiscono) dipendono da tale configurazione. Per questi motivi, è bene scrivere applicazioni portatili, anche se ovviamente lo sforzo viene sprecato se si controlla l'ambiente di distribuzione, ad esempio perché si tratta di un'applicazione interna o verrà utilizzata solo nel proprio ambiente specifico.
Rob,

24
A partire da PHP 5.4, l'abominio noto come "citazioni magiche" è stato ucciso . E buon viaggio per la cattiva spazzatura.
BryanH,

363

Avviso obsoleto: il codice di esempio di questa risposta (come il codice di esempio della domanda) utilizza l' MySQLestensione PHP , che è stata deprecata in PHP 5.5.0 e rimossa interamente in PHP 7.0.0.

Avviso di sicurezza : questa risposta non è in linea con le migliori pratiche di sicurezza. L'escaping è inadeguato per impedire l'iniezione SQL , utilizzare invece le istruzioni preparate . Utilizzare la strategia descritta di seguito a proprio rischio. (Inoltre, è mysql_real_escape_string()stato rimosso in PHP 7.)

La query con parametri E la convalida dell'input è la strada da percorrere. Esistono molti scenari in cui può verificarsi l'iniezione SQL, anche se mysql_real_escape_string()è stata utilizzata.

Questi esempi sono vulnerabili all'iniezione SQL:

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

o

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

In entrambi i casi, non è possibile utilizzare 'per proteggere l'incapsulamento.

Fonte : l'inaspettata SQL Injection (quando l'escaping non è abbastanza)


2
È possibile impedire l'iniezione SQL se si adotta una tecnica di convalida dell'input in cui l'input dell'utente è autenticato rispetto a un insieme di regole definite per lunghezza, tipo e sintassi e anche rispetto alle regole aziendali.
Josip Ivic,

312

A mio avviso, il modo migliore per prevenire in genere l'iniezione di SQL nella tua applicazione PHP (o qualsiasi altra applicazione web, per quella materia) è di pensare all'architettura della tua applicazione. Se l'unico modo per proteggersi dall'iniezione SQL è di ricordare di usare un metodo o una funzione speciale che fa la cosa giusta ogni volta che parli con il database, stai sbagliando. In questo modo, è solo una questione di tempo fino a quando si dimentica di formattare correttamente la query a un certo punto del codice.

L'adozione del modello MVC e di un framework come CakePHP o CodeIgniter è probabilmente la strada giusta da percorrere: attività comuni come la creazione di query di database sicure sono state risolte e implementate centralmente in tali framework. Ti aiutano a organizzare la tua applicazione web in modo ragionevole e ti fanno pensare più al caricamento e al salvataggio di oggetti che alla costruzione sicura di singole query SQL.


5
Penso che il tuo primo paragrafo sia importante. La comprensione è la chiave. Inoltre, non tutti lavorano per un'azienda. Per una vasta fascia di persone, le strutture in realtà vanno contro l'idea di comprensione . L'intimità con i fondamenti potrebbe non essere valutata mentre si lavora entro una scadenza, ma i fai-da-te là fuori si divertono a sporcarsi le mani. Gli sviluppatori di framework non sono così privilegiati che tutti gli altri devono inchinarsi e presumere che non commettano mai errori. Il potere di prendere decisioni è ancora importante. Chi può dire che la mia struttura non sostituirà qualche altro schema in futuro?
Anthony Rutledge,

@AnthonyRutledge Hai perfettamente ragione. È molto importante capire cosa sta succedendo e perché. Tuttavia, la possibilità che un framework vero e provato, utilizzato e sviluppato attivamente abbia incontrato e risolto molti problemi e risolto molte falle di sicurezza è già piuttosto elevata. È una buona idea esaminare l'origine per avere un'idea della qualità del codice. Se è un pasticcio non testato, probabilmente non è sicuro.
Johannes Fahrenkrug,

3
Qui. Qui. Punti buoni. Tuttavia, concorderesti che molte persone possono studiare e imparare ad adottare un sistema MVC, ma non tutti possono riprodurlo manualmente (controller e server). Si può andare troppo lontano con questo punto. Devo capire il mio forno a microonde prima di riscaldare i miei biscotti al burro di arachidi e noci pecan che mi ha preparato la mia ragazza? ;-)
Anthony Rutledge il

3
@AnthonyRutledge Sono d'accordo! Penso che anche il caso d'uso faccia la differenza: sto creando una galleria fotografica per la mia homepage personale o sto creando un'applicazione web di banking online? In quest'ultimo caso è molto importante capire i dettagli della sicurezza e come un framework che sto usando li sta affrontando.
Johannes Fahrenkrug,

3
Ah, l'eccezione di sicurezza al corollario fai da te. Vedi, tendo ad essere disposto a rischiare tutto e andare per il verde. :-) Prendere in giro. Con abbastanza tempo, le persone possono imparare a creare un'applicazione dannatamente sicura. Troppe persone hanno fretta. Alzano le mani e presumono che i quadri siano più sicuri . Dopotutto, non hanno abbastanza tempo per testare e capire le cose. Inoltre, la sicurezza è un campo che richiede uno studio dedicato. Non è qualcosa che i semplici programmatori conoscono in profondità in virtù della comprensione degli algoritmi e dei modelli di progettazione.
Anthony Rutledge,

298

Ho favore stored procedure ( MySQL ha avuto stored procedure supportano dal 5,0 ) da un punto di vista della sicurezza - i vantaggi sono -

  1. La maggior parte dei database (incluso MySQL ) consente di limitare l'accesso degli utenti all'esecuzione di stored procedure. Il controllo di accesso di sicurezza a grana fine è utile per prevenire l'escalation degli attacchi di privilegi. Ciò impedisce alle applicazioni compromesse di essere in grado di eseguire SQL direttamente sul database.
  2. Estraggono la query SQL non elaborata dall'applicazione, quindi meno informazioni sulla struttura del database sono disponibili per l'applicazione. Ciò rende più difficile per le persone comprendere la struttura sottostante del database e progettare attacchi adeguati.
  3. Accettano solo parametri, quindi ci sono i vantaggi delle query con parametri. Naturalmente - IMO è ancora necessario disinfettare l'input, soprattutto se si utilizza SQL dinamico all'interno della procedura memorizzata.

Gli svantaggi sono:

  1. Sono (procedure memorizzate) difficili da mantenere e tendono a moltiplicarsi molto rapidamente. Questo rende la loro gestione un problema.
  2. Non sono molto adatti per le query dinamiche: se sono costruiti per accettare il codice dinamico come parametri, molti dei vantaggi vengono negati.

297

Esistono molti modi per prevenire iniezioni di SQL e altri hack di SQL. Puoi trovarlo facilmente su Internet (Ricerca Google). Naturalmente la DOP è una delle buone soluzioni. Ma vorrei suggerirvi alcuni buoni collegamenti per prevenire l'iniezione SQL.

Cos'è l'iniezione SQL e come prevenirla

Manuale PHP per iniezione SQL

Spiegazione Microsoft sull'iniezione e la prevenzione di SQL in PHP

E altri come Prevenire l'iniezione di SQL con MySQL e PHP .

Ora, perché è necessario impedire la query dall'iniezione SQL?

Vorrei farvi sapere: perché proviamo a prevenire l'iniezione di SQL con un breve esempio di seguito:

Query per la corrispondenza dell'autenticazione dell'accesso:

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";

Ora, se qualcuno (un hacker) mette

$_POST['email']= admin@emali.com' OR '1=1

e password qualsiasi cosa ....

La query verrà analizzata nel sistema solo fino a:

$query="select * from users where email='admin@emali.com' OR '1=1';

L'altra parte verrà scartata. Quindi, cosa accadrà? Un utente non autorizzato (hacker) sarà in grado di accedere come amministratore senza avere la propria password. Ora può fare tutto ciò che l'amministratore / persona e-mail può fare. Vedi, è molto pericoloso se l'iniezione SQL non viene impedita.


267

Penso che se qualcuno vuole usare PHP e MySQL o qualche altro server DataBase:

  1. Pensa all'apprendimento del DOP (PHP Data Objects): è un livello di accesso al database che fornisce un metodo uniforme di accesso a più database.
  2. Pensa all'apprendimento di MySQLi
  3. Usa funzioni PHP native come: strip_tags , mysql_real_escape_string o, se variabile numerica, solo (int)$foo. Maggiori informazioni sul tipo di variabili in PHP qui . Se stai usando librerie come PDO o MySQLi, usa sempre PDO :: quote () e mysqli_real_escape_string () .

Esempi di biblioteche:

---- DOP

----- Nessun segnaposto - pronto per l'iniezione SQL! È cattivo

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

----- Segnaposto senza nome

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

----- Segnaposti nominati

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

--- MySQLi

$request = $mysqliConnection->prepare('
       SELECT * FROM trainers
       WHERE name = ?
       AND email = ?
       AND last_login > ?');

    $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
    $query->execute();

PS :

DOP vince questa battaglia con facilità. Con il supporto di dodici diversi driver di database e parametri denominati, possiamo ignorare la piccola perdita di prestazioni e abituarci alla sua API. Dal punto di vista della sicurezza, entrambi sono sicuri purché lo sviluppatore li usi nel modo in cui dovrebbero essere utilizzati

Ma sia PDO che MySQLi sono abbastanza veloci, MySQLi ha prestazioni insignificanti più veloci nei benchmark: ~ 2,5% per le dichiarazioni non preparate e ~ 6,5% per quelle preparate.

E prova ogni query nel tuo database: è un modo migliore per prevenire l'iniezione.


che mysqli non è corretto. Il primo parametro esprime i tipi di dati.
Mickmackusa,

257

Se possibile, cast i tipi di parametri. Ma funziona solo su tipi semplici come int, bool e float.

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

3
Questo è uno dei pochi casi in cui utilizzerei un "valore di escape" anziché un'istruzione preparata. E la conversione del tipo intero è estremamente efficiente.
HoldOffHunger


224

Per quelli incerti su come usare il PDO (proveniente dalle mysql_funzioni), ho creato un wrapper PDO molto, molto semplice, che è un singolo file. Esiste per mostrare quanto sia facile fare tutte le cose comuni che le applicazioni devono essere fatte. Funziona con PostgreSQL, MySQL e SQLite.

In sostanza, leggerlo mentre si legge il manuale per vedere come mettere le funzioni DOP per l'uso nella vita reale per rendere più semplice per archiviare e recuperare i valori nel formato che si desidera.

Voglio una singola colonna

$count = DB::column('SELECT COUNT(*) FROM `user`);

Voglio una matrice (chiave => valore) risultati (ovvero per creare una casella di selezione)

$pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

Voglio un risultato a riga singola

$user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

Voglio una serie di risultati

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));

222

Usando questa funzione PHP mysql_escape_string()puoi ottenere una buona prevenzione in modo rapido.

Per esempio:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'

mysql_escape_string - Esce da una stringa da utilizzare in un mysql_query

Per una maggiore prevenzione, è possibile aggiungere alla fine ...

wHERE 1=1   or  LIMIT 1

Finalmente ottieni:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1

220

Alcune linee guida per sfuggire a caratteri speciali nelle istruzioni SQL.

Non usare MySQL . Questa estensione è obsoleta. Utilizzare invece MySQLi o DOP .

MySQLi

Per uscire manualmente da caratteri speciali in una stringa puoi usare la funzione mysqli_real_escape_string . La funzione non funzionerà correttamente a meno che non sia impostato il set di caratteri corretto con mysqli_set_charset .

Esempio:

$mysqli = new mysqli('host', 'user', 'password', 'database');
$mysqli->set_charset('charset');

$string = $mysqli->real_escape_string($string);
$mysqli->query("INSERT INTO table (column) VALUES ('$string')");

Per eseguire l'escaping automatico dei valori con istruzioni preparate, utilizzare mysqli_prepare e mysqli_stmt_bind_param dove è necessario fornire i tipi per le variabili di bind corrispondenti per una conversione appropriata:

Esempio:

$stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)");

$stmt->bind_param("is", $integer, $string);

$stmt->execute();

Non importa se usi dichiarazioni preparate o mysqli_real_escape_stringdevi sempre conoscere il tipo di dati di input con cui stai lavorando.

Pertanto, se si utilizza un'istruzione preparata, è necessario specificare i tipi di variabili per la mysqli_stmt_bind_paramfunzione.

E l'uso di mysqli_real_escape_stringfor, come dice il nome, evade caratteri speciali in una stringa, quindi non renderà sicuri gli interi. Lo scopo di questa funzione è impedire la rottura delle stringhe nelle istruzioni SQL e il danno al database che potrebbe causare. mysqli_real_escape_stringè una funzione utile se usata correttamente, specialmente se combinata con sprintf.

Esempio:

$string = "x' OR name LIKE '%John%";
$integer = '5 OR id != 0';

$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5

$integer = '99999999999999999999';
$query = sprintf("SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647

3
La domanda è molto generica. Alcune grandi risposte sopra, ma la maggior parte suggerisce dichiarazioni preparate. MySQLi async non supporta le istruzioni preparate, quindi lo sprintf sembra un'ottima opzione per questa situazione.
Dustin Graham,

183

La semplice alternativa a questo problema potrebbe essere risolta concedendo le autorizzazioni appropriate nel database stesso. Ad esempio: se si utilizza un database MySQL, accedere al database tramite il terminale o l'interfaccia utente fornita e seguire questo comando:

 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

Ciò limiterà l'utente a rimanere confinato solo con la query specificata. Rimuovi il permesso di cancellazione e così i dati non verrebbero mai cancellati dalla query lanciata dalla pagina PHP. La seconda cosa da fare è svuotare i privilegi in modo che MySQL aggiorni le autorizzazioni e gli aggiornamenti.

FLUSH PRIVILEGES; 

ulteriori informazioni su flush .

Per visualizzare i privilegi correnti per l'utente, attiva la seguente query.

select * from mysql.user where User='username';

Ulteriori informazioni su GRANT .


25
Questa risposta è essenzialmente sbagliata , in quanto non aiuta a prevenire una prevenzione dell'iniezione ma cerca solo di attenuare le conseguenze. Invano.
Il tuo buon senso

1
Bene, non fornisce una soluzione, ma è ciò che puoi fare prima di evitare le cose.
Apurv Nerlekar,

1
@Apurv Se il mio obiettivo è leggere informazioni private dal tuo database, non avere l'autorizzazione DELETE significa niente.
Alex Holsgrove,

1
@AlexHolsgrove: Vacci piano, stavo solo suggerendo buone pratiche per attenuare le conseguenze.
Apurv Nerlekar,

2
@Apurv Non vuoi "attenuare le conseguenze", vuoi fare tutto il possibile per proteggerti. Ad essere onesti, tuttavia, è importante impostare l'accesso utente corretto, ma non proprio quello che l'OP richiede.
Alex Holsgrove,

177

Per quanto riguarda molte risposte utili, spero di aggiungere un po 'di valore a questa discussione.

L'iniezione SQL è un attacco che può essere eseguito tramite input dell'utente (input compilati da un utente e quindi utilizzati all'interno delle query). I modelli di iniezione SQL sono sintassi della query corretta mentre possiamo chiamarla: query errate per motivi errati e supponiamo che potrebbe esserci una persona cattiva che tenta di ottenere informazioni segrete (ignorando il controllo degli accessi) che influiscono sui tre principi di sicurezza (riservatezza , integrità e disponibilità).

Ora, il nostro punto è prevenire minacce alla sicurezza come gli attacchi di SQL injection, la domanda (come prevenire un attacco di SQL injection usando PHP), essere più realistici, il filtraggio dei dati o la cancellazione dei dati di input è il caso quando si utilizzano i dati di input dell'utente all'interno tale query, utilizzando PHP o qualsiasi altro linguaggio di programmazione non è il caso, o come raccomandato da più persone di utilizzare la tecnologia moderna come dichiarazioni preparate o altri strumenti che attualmente supportano la prevenzione dell'iniezione SQL, ritengono che questi strumenti non siano più disponibili? Come si protegge l'applicazione?

Il mio approccio contro l'iniezione SQL è: cancellare i dati immessi dall'utente prima di inviarli al database (prima di usarli all'interno di qualsiasi query).

Filtro dati per (conversione di dati non sicuri in dati sicuri)

Considera che DOP e MySQLi non sono disponibili. Come puoi proteggere la tua applicazione? Mi costringi ad usarli? Che dire di altre lingue diverse da PHP? Preferisco fornire idee generali in quanto possono essere utilizzate per confini più ampi, non solo per una lingua specifica.

  1. Utente SQL (limitazione del privilegio utente): le operazioni SQL più comuni sono (SELEZIONA, AGGIORNA, INSERISCI), quindi perché assegnare il privilegio AGGIORNAMENTO a un utente che non lo richiede? Ad esempio, le pagine di accesso e di ricerca utilizzano solo SELECT, quindi perché utilizzare gli utenti DB in queste pagine con privilegi elevati?

REGOLA: non creare un utente del database per tutti i privilegi. Per tutte le operazioni SQL, puoi creare il tuo schema come (deluser, selectuser, updateuser) come nomi utente per un facile utilizzo.

Vedi il principio del privilegio minimo .

  1. Filtro dati: prima di creare qualsiasi input dell'utente per la query, deve essere convalidato e filtrato. Per i programmatori, è importante definire alcune proprietà per ciascuna variabile di input dell'utente: tipo di dati, modello di dati e lunghezza dei dati . Un campo che è un numero compreso tra (xey) deve essere validato esattamente usando la regola esatta, e per un campo che è una stringa (testo): modello è il caso, ad esempio, un nome utente deve contenere solo alcuni caratteri, dire [a-zA-Z0-9_-.]. La lunghezza varia tra (x e n) dove x e n (numeri interi, x <= n). Regola: la creazione di filtri esatti e regole di convalida sono le migliori pratiche per me.

  2. Utilizza altri strumenti: qui, concorderò anche con te che una dichiarazione preparata (query parametrizzata) e procedure memorizzate. Gli svantaggi qui sono che questi modi richiedono competenze avanzate che non esistono per la maggior parte degli utenti. L'idea di base qui è di distinguere tra la query SQL e i dati utilizzati all'interno. Entrambi gli approcci possono essere utilizzati anche con dati non sicuri, poiché i dati immessi dall'utente qui non aggiungono nulla alla query originale, come (any o x = x).

Per ulteriori informazioni, consultare il foglio informativo OWASP SQL Injection Prevention Prevention .

Ora, se sei un utente avanzato, inizia a utilizzare questa difesa a tuo piacimento, ma, per i principianti, se non sono in grado di implementare rapidamente una procedura memorizzata e hanno preparato la dichiarazione, è meglio filtrare i dati di input il più possibile.

Infine, consideriamo che un utente invia questo testo qui sotto invece di inserire il suo nome utente:

[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

Questo input può essere verificato in anticipo senza alcuna istruzione preparata e stored procedure, ma per essere al sicuro, il loro utilizzo inizia dopo il filtraggio e la convalida dei dati utente.

L'ultimo punto è rilevare comportamenti inaspettati che richiedono maggiore sforzo e complessità; non è raccomandato per le normali applicazioni Web.

Il comportamento imprevisto nell'input dell'utente sopra è SELECT, UNION, IF, SUBSTRING, BENCHMARK, SHA e root. Una volta rilevate queste parole, è possibile evitare l'input.

AGGIORNAMENTO 1:

Un utente ha commentato che questo post è inutile, OK! Ecco cosa ha fornito OWASP.ORG :

Difese primarie:

Opzione n. 1: utilizzo di istruzioni preparate (query con parametri)
Opzione n. 2: utilizzo di procedure memorizzate
Opzione n. 3: fuga da tutti gli input forniti dall'utente

Difese aggiuntive:

Applicazione anche: privilegio minimo
Esecuzione: convalida input lista bianca

Come forse saprai, rivendicare un articolo dovrebbe essere supportato da un argomento valido, almeno da un riferimento! Altrimenti, è considerato un attacco e una cattiva pretesa!

Aggiornamento 2:

Dal manuale di PHP, PHP: Istruzioni preparate - Manuale :

Escaping e SQL injection

Le variabili associate verranno salvate automaticamente dal server. Il server inserisce i valori di escape nei punti appropriati nel modello di istruzione prima dell'esecuzione. È necessario fornire un suggerimento al server per il tipo di variabile associata, per creare una conversione appropriata. Vedere la funzione mysqli_stmt_bind_param () per ulteriori informazioni.

L'escaping automatico dei valori all'interno del server è talvolta considerato una funzionalità di sicurezza per impedire l'iniezione di SQL. Lo stesso grado di sicurezza può essere raggiunto con istruzioni non preparate se i valori di input vengono salvati correttamente.

Aggiornamento 3:

Ho creato casi di test per sapere come PDO e MySQLi inviano la query al server MySQL quando si utilizza un'istruzione preparata:

DOP:

$user = "''1''"; // Malicious keyword
$sql = 'SELECT * FROM awa_user WHERE userame =:username';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':username' => $user));

Registro query:

    189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
    189 Quit

MySQLi:

$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
$stmt->bind_param("s", $user);
$user = "''1''";
$stmt->execute();

Registro query:

    188 Prepare   SELECT * FROM awa_user WHERE username =?
    188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
    188 Quit

È chiaro che anche un'istruzione preparata sta sfuggendo ai dati, nient'altro.

Come menzionato anche nella precedente dichiarazione,

L'escaping automatico dei valori all'interno del server è talvolta considerato una funzionalità di sicurezza per impedire l'iniezione di SQL. Lo stesso grado di sicurezza può essere raggiunto con istruzioni non preparate, se i valori di input vengono evitati correttamente

Pertanto, ciò dimostra che la convalida dei dati come intval()una buona idea per i valori interi prima di inviare qualsiasi query. Inoltre, la prevenzione di dati utente dannosi prima di inviare la query è un approccio corretto e valido .

Vedi questa domanda per maggiori dettagli: PDO invia query non elaborate a MySQL mentre Mysqli invia query preparate, entrambe producono lo stesso risultato

Riferimenti:

  1. Foglio informativo sull'iniezione SQL
  2. SQL Injection
  3. Informazioni di sicurezza
  4. Principi di sicurezza
  5. Convalida dei dati

175

Avviso di sicurezza : questa risposta non è in linea con le migliori pratiche di sicurezza. L'escaping è inadeguato per impedire l'iniezione SQL , utilizzare invece le istruzioni preparate . Utilizzare la strategia descritta di seguito a proprio rischio. (Inoltre, è mysql_real_escape_string()stato rimosso in PHP 7.)

Avviso obsoleto: l'estensione mysql è obsoleta in questo momento. si consiglia di utilizzare l' estensione PDO

Uso tre modi diversi per impedire che la mia applicazione Web sia vulnerabile all'iniezione SQL.

  1. L'uso di mysql_real_escape_string(), che è una funzione predefinita in PHP , e questo codice add backslash per i seguenti caratteri: \x00, \n, \r, \, ', "e \x1a. Passare i valori di input come parametri per ridurre al minimo la possibilità di iniezione SQL.
  2. Il modo più avanzato è usare i DOP.

Spero che questo ti possa aiutare.

Considera la seguente query:

$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string () non proteggerà qui. Se usi virgolette singole ('') attorno alle tue variabili all'interno della tua query è ciò che ti protegge da questo. Ecco una soluzione qui sotto per questo:

$iId = (int) mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

Questa domanda ha alcune buone risposte a riguardo.

Suggerisco che usare DOP sia l'opzione migliore.

Modificare:

mysql_real_escape_string()è obsoleto a partire da PHP 5.5.0. Usa mysqli o DOP.

Un'alternativa a mysql_real_escape_string () è

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

Esempio:

$iId = $mysqli->real_escape_string("1 OR 1=1");
$mysqli->query("SELECT * FROM table WHERE id = $iId");

169

Un modo semplice sarebbe usare un framework PHP come CodeIgniter o Laravel che ha funzionalità integrate come il filtro e il record attivo in modo da non doversi preoccupare di queste sfumature.


7
Penso che il punto centrale della domanda sia quello di farlo senza usare tale framework.
Sanke,

147

Attenzione: l'approccio descritto in questa risposta si applica solo a scenari molto specifici e non è sicuro poiché gli attacchi di iniezione SQL non si basano solo sulla possibilità di iniettare X=Y.

Se gli aggressori stanno cercando di hackerare il modulo tramite la $_GETvariabile di PHP o con la stringa di query dell'URL, sarai in grado di catturarli se non sono sicuri.

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php

Perché 1=1, 2=2, 1=2, 2=1, 1+1=2, ecc ... sono le domande comuni a un database SQL di un attaccante. Forse è anche usato da molte applicazioni di hacking.

Ma devi stare attento a non riscrivere una query sicura dal tuo sito. Il codice sopra ti sta dando un suggerimento, per riscrivere o reindirizzare (dipende da te) quella stringa di query dinamica specifica dell'hacking in una pagina che memorizzerà l' indirizzo IP dell'attaccante , o ANCHE I LORO COOKIE, la cronologia, il browser o qualsiasi altro sensibile informazioni, in modo da poterli gestire in seguito vietando il loro account o contattando le autorità.


Che succede 1-1=0? :)
Rápli András,

@ RápliAndrás Una specie di ([0-9\-]+)=([0-9]+).
5

127

Ci sono così tante risposte per PHP e MySQL , ma ecco il codice per PHP e Oracle per prevenire l'iniezione SQL e l'uso regolare dei driver oci8:

$conn = oci_connect($username, $password, $connection_string);
$stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123');
oci_bind_by_name($stmt, ':xx', $fieldval);
oci_execute($stmt);

Spiegare i parametri oci_bind_by_name.
Jahanzeb Awan,

127

Una buona idea è quella di utilizzare un mappatore relazionale ad oggetto come Idiorm :

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

Non solo ti salva dalle iniezioni di SQL, ma anche dagli errori di sintassi! Supporta inoltre raccolte di modelli con concatenamento di metodi per filtrare o applicare azioni a più risultati contemporaneamente e connessioni multiple.


124

Avviso obsoleto: il codice di esempio di questa risposta (come il codice di esempio della domanda) utilizza l' MySQLestensione PHP , che è stata deprecata in PHP 5.5.0 e rimossa interamente in PHP 7.0.0.

Avviso di sicurezza : questa risposta non è in linea con le migliori pratiche di sicurezza. L'escaping è inadeguato per impedire l'iniezione SQL , utilizzare invece le istruzioni preparate . Utilizzare la strategia descritta di seguito a proprio rischio. (Inoltre, è mysql_real_escape_string()stato rimosso in PHP 7.)

L'uso di PDO e MYSQLi è una buona pratica per prevenire iniezioni di SQL, ma se vuoi davvero lavorare con le funzioni e le query di MySQL, sarebbe meglio usare

mysql_real_escape_string

$unsafe_variable = mysql_real_escape_string($_POST['user_input']);

Ci sono più abilità per impedirlo: come identificare - se l'input è una stringa, un numero, un carattere o un array, ci sono così tante funzioni integrate per rilevarlo. Inoltre, sarebbe meglio usare queste funzioni per controllare i dati di input.

is_string

$unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');

is_numeric

$unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');

Ed è molto meglio usare quelle funzioni con cui controllare i dati di input mysql_real_escape_string.


10
Inoltre, non ha assolutamente senso controllare i membri dell'array $ _POST con is_string ()
Il tuo senso comune

21
AVVERTIMENTO! mysql_real_escape_string() non è infallibile .
Eggyal

10
mysql_real_escape_stringè ora deprecato, quindi non è più un'opzione praticabile. Sarà rimosso da PHP in futuro. È meglio passare a ciò che consiglia la gente di PHP o MySQL.
jww

2
Tema: non fidarti dei dati inviati dall'utente. Tutto ciò che ti aspetti sono dati inutili con caratteri speciali o logica booleana, che dovrebbe a sua volta diventare parte della query SQL che potresti eseguire. Mantieni i valori $ _POST solo come dati, non come parte SQL.
Bimal Poudel,

88

Ho scritto questa piccola funzione diversi anni fa:

function sqlvprintf($query, $args)
{
    global $DB_LINK;
    $ctr = 0;
    ensureConnection(); // Connect to database if not connected already.
    $values = array();
    foreach ($args as $value)
    {
        if (is_string($value))
        {
            $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
        }
        else if (is_null($value))
        {
            $value = 'NULL';
        }
        else if (!is_int($value) && !is_float($value))
        {
            die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
        }
        $values[] = $value;
        $ctr++;
    }
    $query = preg_replace_callback(
        '/{(\\d+)}/', 
        function($match) use ($values)
        {
            if (isset($values[$match[1]]))
            {
                return $values[$match[1]];
            }
            else
            {
                return $match[0];
            }
        },
        $query
    );
    return $query;
}

function runEscapedQuery($preparedQuery /*, ...*/)
{
    $params = array_slice(func_get_args(), 1);
    $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.   
    return $results;
}

Ciò consente l'esecuzione di istruzioni in una stringa C # -ish da una riga. Formatta come:

runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

Sfugge considerando il tipo di variabile. Se si tenta di parametrizzare la tabella, i nomi delle colonne, fallirebbe poiché inserisce ogni stringa tra virgolette che è una sintassi non valida.

AGGIORNAMENTO DI SICUREZZA: la str_replaceversione precedente consentiva iniezioni aggiungendo {#} token nei dati dell'utente. Questa preg_replace_callbackversione non causa problemi se la sostituzione contiene questi token.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.