Htmlspecialchars e mysql_real_escape_string proteggono il mio codice PHP dall'iniezione?


Risposte:


241

Quando si tratta di query di database, provare sempre a utilizzare query parametrizzate preparate. Le librerie mysqlie lo PDOsupportano. Questo è infinitamente più sicuro rispetto all'utilizzo di funzioni di escape come mysql_real_escape_string.

Sì, mysql_real_escape_stringè effettivamente solo una funzione di escape della stringa. Non è una bacchetta magica. Tutto ciò che farà è sfuggire a caratteri pericolosi in modo che possano essere usati in sicurezza in una singola stringa di query. Tuttavia, se non disinfetti in anticipo i tuoi input, sarai vulnerabile a determinati vettori di attacco.

Immagina il seguente SQL:

$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);

Dovresti essere in grado di vedere che questo è vulnerabile agli exploit.
Immagina che il idparametro contenga il vettore di attacco comune:

1 OR 1=1

Non ci sono caratteri rischiosi da codificare, quindi passerà direttamente attraverso il filtro di fuga. Lasciandoci:

SELECT fields FROM table WHERE id= 1 OR 1=1

Che è un adorabile vettore di SQL injection e consentirebbe all'autore dell'attacco di restituire tutte le righe. O

1 or is_admin=1 order by id limit 1

che produce

SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1

Ciò consente all'autore dell'attacco di restituire i dettagli del primo amministratore in questo esempio completamente fittizio.

Sebbene queste funzioni siano utili, devono essere utilizzate con cautela. È necessario assicurarsi che tutti gli input Web siano convalidati in una certa misura. In questo caso, vediamo che possiamo essere sfruttati perché non abbiamo verificato che una variabile che stavamo utilizzando come numero fosse effettivamente numerica. In PHP dovresti usare ampiamente un insieme di funzioni per verificare che gli input siano numeri interi, float, alfanumerici ecc. Ma quando si tratta di SQL, fai attenzione al valore dell'istruzione preparata. Il codice precedente sarebbe stato sicuro se fosse stata un'istruzione preparata poiché le funzioni del database avrebbero saputo che 1 OR 1=1non è un valore letterale valido.

Quanto a htmlspecialchars(). Questo è un campo minato a sé stante.

C'è un vero problema in PHP in quanto ha un'intera selezione di diverse funzioni di escape relative a HTML e nessuna guida chiara su esattamente quali funzioni fanno cosa.

In primo luogo, se ti trovi all'interno di un tag HTML, sei davvero nei guai. Guarda a

echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';

Siamo già all'interno di un tag HTML, quindi non abbiamo bisogno di <o> per fare nulla di pericoloso. Il nostro vettore di attacco potrebbe esserejavascript:alert(document.cookie)

Ora appare l'HTML risultante

<img src= "javascript:alert(document.cookie)" />

L'attacco arriva dritto.

Peggiora. Perché? perché htmlspecialchars(se chiamato in questo modo) codifica solo virgolette doppie e non singole. Quindi se lo avessimo fatto

echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";

Il nostro malvagio aggressore ora può iniettare parametri completamente nuovi

pic.png' onclick='location.href=xxx' onmouseover='...

ci da

<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />

In questi casi, non c'è nessuna bacchetta magica, devi solo santizzare l'input da solo. Se provi a filtrare i personaggi cattivi, fallirai sicuramente. Adotta un approccio da whitelist e lascia passare solo i caratteri che sono buoni. Guarda il cheat sheet XSS per esempi su come possono essere diversi i vettori

Anche se utilizzi htmlspecialchars($string)al di fuori dei tag HTML, sei comunque vulnerabile ai vettori di attacco con set di caratteri multibyte.

Il più efficace che puoi essere è usare una combinazione di mb_convert_encoding e htmlentities come segue.

$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
$str = htmlentities($str, ENT_QUOTES, 'UTF-8');

Anche questo lascia IE6 vulnerabile, a causa del modo in cui gestisce UTF. Tuttavia, potresti ricorrere a una codifica più limitata, come ISO-8859-1, fino a quando l'utilizzo di IE6 non diminuisce.

Per uno studio più approfondito sui problemi multibyte, vedere https://stackoverflow.com/a/12118602/1820


24
L'unica cosa mancata qui è che il primo esempio per la query DB ... un semplice intval () risolverebbe l'iniezione. Usa sempre intval () al posto di mysqlescape ... () quando hai bisogno di un numero e non di una stringa.
Robert K,

11
e ricorda che l'utilizzo di query parametrizzate ti consentirà di avere sempre i dati trattati come dati e non come codice. Utilizzare una libreria come PDO e utilizzare query parametrizzate quando possibile.
Cheekysoft

9
Due osservazioni: 1. Nel primo esempio, saresti al sicuro se mettessi anche le virgolette intorno al parametro, come $result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'";2. Nel secondo caso (attributo contenente URL), non serve htmlspecialcharsa niente; in questi casi, dovresti codificare l'input utilizzando uno schema di codifica URL, ad esempio utilizzando rawurlencode. In questo modo, un utente non può inserire javascript:et al.
Marcel Korpel

7
“Htmlspecialchars codifica solo virgolette doppie e non singole”: non è vero, dipende dai flag impostati, vedi i suoi parametri .
Marcel Korpel

2
Dovrebbe essere in grassetto: Take a whitelist approach and only let through the chars which are good.una lista nera mancherà sempre qualcosa. +1
Jo Smo

10

Oltre all'eccellente risposta di Cheekysoft:

  • Sì, ti terranno al sicuro, ma solo se vengono utilizzati in modo assolutamente corretto. Usali in modo errato e sarai comunque vulnerabile e potresti avere altri problemi (ad esempio il danneggiamento dei dati)
  • Utilizzare invece query parametrizzate (come indicato sopra). È possibile utilizzarli tramite, ad esempio, PDO o tramite un wrapper come PEAR DB
  • Assicurati che magic_quotes_gpc e magic_quotes_runtime siano sempre disattivati ​​e che non vengano mai attivati ​​accidentalmente, nemmeno brevemente. Si tratta di un tentativo precoce e profondamente fuorviante da parte degli sviluppatori di PHP di prevenire problemi di sicurezza (che distruggono i dati)

Non c'è davvero un proiettile d'argento per prevenire l'iniezione di HTML (es. Cross site scripting), ma potresti essere in grado di ottenerlo più facilmente se stai usando una libreria o un sistema di modelli per l'output di HTML. Leggi la documentazione per quello su come sfuggire alle cose in modo appropriato.

In HTML, è necessario eseguire l'escape delle cose in modo diverso a seconda del contesto. Ciò è particolarmente vero per le stringhe inserite in Javascript.


3

Sarei sicuramente d'accordo con i post sopra, ma ho una piccola cosa da aggiungere in risposta alla risposta di Cheekysoft, in particolare:

Quando si tratta di query di database, provare sempre a utilizzare query parametrizzate preparate. Le librerie mysqli e PDO supportano questo. Questo è infinitamente più sicuro rispetto all'utilizzo di funzioni di escape come mysql_real_escape_string.

Sì, mysql_real_escape_string è effettivamente solo una funzione di escape delle stringhe. Non è una bacchetta magica. Tutto ciò che farà è sfuggire a caratteri pericolosi in modo che possano essere usati in sicurezza in una singola stringa di query. Tuttavia, se non disinfetti in anticipo i tuoi input, sarai vulnerabile a determinati vettori di attacco.

Immagina il seguente SQL:

$ risultato = "SELEZIONA campi DALLA tabella WHERE id =" .mysql_real_escape_string ($ _ POST ['id']);

Dovresti essere in grado di vedere che questo è vulnerabile agli exploit. Immagina che il parametro id contenga il vettore di attacco comune:

1 OPPURE 1 = 1

Non ci sono caratteri rischiosi da codificare, quindi passerà direttamente attraverso il filtro di fuga. Lasciandoci:

SELEZIONA i campi DALLA tabella DOVE id = 1 O 1 = 1

Ho codificato una piccola funzione veloce che ho inserito nella mia classe di database che eliminerà tutto ciò che non è un numero. Usa preg_replace, quindi c'è probabilmente una funzione un po 'più ottimizzata, ma funziona in un pizzico ...

function Numbers($input) {
  $input = preg_replace("/[^0-9]/","", $input);
  if($input == '') $input = 0;
  return $input;
}

Quindi, invece di usare

$ risultato = "SELEZIONA i campi DALLA tabella WHERE id =" .mysqlrealescapestring ("1 OR 1 = 1");

io userei

$ risultato = "SELEZIONA campi dalla tabella WHERE id =" .Numeri ("1 OR 1 = 1");

e potrebbe eseguire la query in modo sicuro

SELEZIONA i campi DALLA tabella DOVE id = 111

Certo, questo gli ha semplicemente impedito di visualizzare la riga corretta, ma non penso che sia un grosso problema per chiunque stia cercando di iniettare sql nel tuo sito;)


1
Perfetto! Questo è esattamente il tipo di sanificazione di cui hai bisogno. Il codice iniziale non è riuscito perché non ha convalidato che un numero fosse numerico. Il tuo codice fa questo. dovresti chiamare Numbers () su tutte le variabili ad uso intero i cui valori provengono dall'esterno della base di codice.
Cheekysoft

1
Vale la pena ricordare che intval () funzionerà perfettamente per questo, poiché PHP costringe automaticamente gli interi alle stringhe per te.
Adam Ernst

11
Preferisco intval. Gira 1abc2 a 1, non 12.
jmucchiello

1
intval è migliore, specialmente su ID. Il più delle volte, se è stato danneggiato, è proprio come sopra, 1 o 1 = 1. Non dovresti davvero far trapelare l'ID di altre persone. Quindi intval restituirà l'ID corretto. Dopodiché, dovresti controllare se i valori originali e puliti sono gli stessi. È un ottimo modo non solo per fermare gli attacchi, ma anche per trovare gli aggressori.
triunenature

2
La riga sbagliata sarebbe disastrosa se mostrassi dati personali, vedresti le informazioni di un altro utente! invece sarebbe meglio controllarereturn preg_match('/^[0-9]+$/',$input) ? $input : 0;
Frank Forte

2

Un pezzo importante di questo puzzle sono i contesti. Qualcuno che invia "1 OR 1 = 1" come ID non è un problema se citi ogni argomento nella tua query:

SELECT fields FROM table WHERE id='".mysql_real_escape_string($_GET['id'])."'"

Che si traduce in:

SELECT fields FROM table WHERE id='1 OR 1=1'

che è inefficace. Poiché esegui l'escape della stringa, l'input non può uscire dal contesto della stringa. L'ho testato fino alla versione 5.0.45 di MySQL e l'utilizzo di un contesto stringa per una colonna intera non causa alcun problema.


15
e poi inizierò il mio vettore di attacco con il carattere multibyte 0xbf27 che nel tuo database latin1 verrà convertito dalla funzione di filtro come 0xbf5c27 - che è un singolo carattere multibyte seguito da una singola virgoletta.
Cheekysoft

8
Cerca di non proteggerti da un singolo vettore di attacco noto. Finirai per inseguire la tua coda fino alla fine del tempo applicando patch dopo patch al tuo codice. Stare indietro e guardare i casi generali passerà a un codice più sicuro e a una mentalità più incentrata sulla sicurezza.
Cheekysoft

Sono d'accordo; idealmente, OP utilizzerà dichiarazioni preparate.
Lucas Oman

1
Sebbene la citazione degli argomenti suggeriti da questo post non sia infallibile, mitigherà molti dei comuni attacchi di tipo 1 OR 1 = 1, quindi è degno di menzione.
Night Owl

2
$result = "SELECT fields FROM table WHERE id = ".(INT) $_GET['id'];

Funziona bene, anche meglio su sistemi a 64 bit. Fai attenzione ai limiti del tuo sistema sull'indirizzamento di grandi numeri, ma per gli ID di database questo funziona alla grande il 99% delle volte.

Dovresti usare una singola funzione / metodo anche per pulire i tuoi valori. Anche se questa funzione è solo un wrapper per mysql_real_escape_string (). Perché? Perché un giorno, quando viene rilevato un exploit per il tuo metodo preferito di pulizia dei dati, devi aggiornarlo solo in un punto, piuttosto che trovare e sostituire a livello di sistema.


-3

perché, oh PERCHÉ, non includeresti virgolette intorno all'input dell'utente nella tua istruzione sql? sembra abbastanza sciocco non farlo! includere le virgolette nella tua istruzione sql renderebbe "1 o 1 = 1" un tentativo infruttuoso, no?

quindi ora dirai "cosa succede se l'utente include una citazione (o virgolette doppie) nell'input?"

beh, soluzione facile per questo: basta rimuovere le virgolette inserite dall'utente. ad esempio: input =~ s/'//g;. ora, mi sembra comunque, che l'input dell'utente sarebbe protetto ...


"perché, oh PERCHÉ, non includeresti virgolette intorno all'input dell'utente nella tua istruzione sql?" - La domanda non dice nulla sul non citare l'input dell'utente.
Quentin

1
"beh, soluzione facile per quello" - Terribile soluzione per quello. Questo butta via i dati. La soluzione menzionata nella domanda stessa è un approccio migliore.
Quentin

anche se sono d'accordo che la domanda non riguarda la citazione dell'input dell'utente, sembra comunque che non citi l'input. e preferirei lanciare dati piuttosto che inserire dati non validi. generalmente, in un attacco injection, NON vuoi comunque quei dati .... giusto?
Jarett L

"Anche se sono d'accordo che la domanda non riguarda la citazione dell'input dell'utente, sembra comunque che non citi l'input." - No, non lo è. La domanda non lo dimostra in un modo o nell'altro.
Quentin

1
@JarettL Abituati a usare istruzioni preparate o abituati a Bobby Tables che distrugge i tuoi dati ogni martedì . L'SQL parametrizzato è l'unico modo migliore per proteggersi dall'iniezione di SQL. Non è necessario eseguire "controlli SQL injection" se si utilizza un'istruzione preparata. Sono estremamente facili da implementare (e, a mio parere, rendono il codice MOLTO più facile da leggere), proteggono da varie idiosincrasie di concatenazione di stringhe e sql injection e, soprattutto, non è necessario reinventare la ruota per implementarlo .
Siyual
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.