2011-11-09 15 views
10

ho sempre fatto il semplice collegamento di mysql_connect, mysql_pconnect:Sostituzione mysql_ * funzioni con DOP e le istruzioni preparate

$db = mysql_pconnect('*host*', '*user*', '*pass*'); 

if (!$db) { 
    echo("<strong>Error:</strong> Could not connect to the database!"); 
    exit; 
} 

mysql_select_db('*database*'); 

Durante l'utilizzo di questo ho sempre usato il metodo semplice per sfuggire tutti i dati prima di prendere una interrogazione, sia che si tratti INSERT, SELECT, UPDATE o DELETE utilizzando mysql_real_escape_string

$name = $_POST['name']; 

$name = mysql_real_escape_string($name); 

$sql = mysql_query("SELECT * FROM `users` WHERE (`name` = '$name')") or die(mysql_error()); 

Ora ho capito questo è sicuro, in una certa misura!

Sfugge a personaggi pericolosi; tuttavia, è ancora vulnerabile ad altri attacchi che possono contenere caratteri sicuri, ma possono essere dannosi per la visualizzazione di dati o, in alcuni casi, per la modifica o l'eliminazione di dati maliziosi.

Così, ho cercato un po 'e ho trovato informazioni su PDO, MySQLi e istruzioni preparate. Sì, potrei essere in ritardo con il gioco ma ho letto molti, molti tutorial (tizag, W3C, blog, ricerche Google) là fuori e nessuno li ha menzionati. Sembra molto strano il motivo per cui, come solo la fuga di input da parte dell'utente non è davvero sicuro e non è una buona pratica per non dire altro. Sì, sono consapevole che potresti usare Regex per affrontarlo, ma sono sicuro che non è abbastanza?

È a mio avviso che l'utilizzo di istruzioni PDO/preparato è un modo molto più sicuro di archiviare e recuperare i dati da un database quando le variabili sono fornite dall'input dell'utente. L'unico problema è che il passaggio (specialmente dopo essere rimasto bloccato nei miei modi/abitudini di codifica precedente) è un po 'difficile.

questo momento ho capito che per la connessione al database utilizzando il mio DOP userei

$hostname = '*host*'; 
$username = '*user*'; 
$password = '*pass*'; 
$database = '*database*' 

$dbh = new PDO("mysql:host=$hostname;dbname=$database", $username, $password); 

if ($dbh) { 
    echo 'Connected to database'; 
} else { 
    echo 'Could not connect to database'; 
} 

Ora, i nomi di funzione sono diversi in modo non sarà più il mio mysql_query, mysql_fetch_array, mysql_num_rows ecc lavoro. Quindi devo leggere/ricordare un carico di nuovi, ma è qui che mi sto confondendo.

Se volevo inserire dati da un modulo di iscrizione/registrazione, come potrei fare, ma soprattutto come procedere in modo sicuro? Presumo che questo è dove arrivano le dichiarazioni preparate, ma usandole questo elimina la necessità di utilizzare qualcosa come mysql_real_escape_string? So che mysql_real_escape_string richiede che tu sia connesso a un database tramite mysql_connect/mysql_pconnect, quindi ora non stiamo usando nessuno di questi non produrrà solo un errore?

Ho visto diversi modi per avvicinarsi al metodo PDO, ad esempio, ho visto :variable e ? come quelli che penso siano noti come segnaposto (scusate se è sbagliato).

Ma penso che questo è più o meno l'idea di ciò che dovrebbe essere fatto per andare a prendere un utente da un database

$user_id = $_GET['id']; // For example from a URL query string 

$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id"); 

$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT); 

Ma poi mi sono bloccato su un paio di cose, se la variabile non era un numero ed era una stringa di testo, devi dare una lunghezza dopo PDO:PARAM_STR se non sbaglio. Ma come puoi dare una lunghezza impostata se non sei sicuro del valore dato dai dati inseriti dall'utente, può variare ogni volta? Ad ogni modo, per quanto ne so per visualizzare i dati che poi fare

$stmt->execute(); 

$result = $stmt->fetchAll(); 

// Either 

foreach($result as $row) { 
    echo $row['user_id'].'<br />'; 
    echo $row['user_name'].'<br />'; 
    echo $row['user_email']; 
} 

// Or 

foreach($result as $row) { 
    $user_id = $row['user_id']; 
    $user_name = $row['user_name']; 
    $user_email = $row['user_email']; 
} 

echo("".$user_id."<br />".$user_name."<br />".$user_email.""); 

Ora, è questo tutto al sicuro?

Se ho ragione, sarebbe l'inserimento di dati sia lo stesso per esempio:

$username = $_POST['username']; 
$email = $_POST['email']; 

$stmt = $dbh->prepare("INSERT INTO `users` (username, email) 
         VALUES (:username, :email)"); 

$stmt->bindParam(':username, $username, PDO::PARAM_STR, ?_LENGTH_?); 
$stmt->bindParam(':email, $email, PDO::PARAM_STR, ?_LENGTH_?); 

$stmt->execute(); 
Sarebbe

che il lavoro, ed è che sicura troppo? Se è giusto quale valore inserirò per lo ?_LENGTH_?? Ho completamente sbagliato tutto questo?

UPDATE

Le risposte che ho avuto finora sono stati estremamente utili, non è possibile grazie ragazzi abbastanza! Tutti hanno un +1 per aprire gli occhi a qualcosa di un po 'diverso. È difficile scegliere la risposta migliore, ma credo che il Col. Shrapnel se lo meriti dato che tutto è praticamente coperto, persino andando in altri array con librerie personalizzate di cui non ero a conoscenza!

Ma grazie a tutti voi :)

+0

Forse la lunghezza dei campi "nome utente" e "email"? Per esempio. se username è varchar (32), il parametro length dovrebbe essere 32. – deejayy

risposta

12

Grazie per la domanda interessante. Qui si va:

Sfugge personaggi pericolosi,

Il vostro concetto è assolutamente sbagliato.
In effetti "personaggi pericolosi" è un mito, non ce ne sono. E mysql_real_escape_string che escaping ma semplicemente un delimitatore di stringhe. Da questa definizione è possibile concludere i suoi limiti: funziona solo per le stringhe .

tuttavia, è ancora vulnerabile ad altri attacchi che possono contenere caratteri sicuri ma possono essere dannosi per la visualizzazione di dati o, in alcuni casi, la modifica o l'eliminazione di dati maliziosamente.

Stai mescolando qui tutto.
Parlando di banca dati,

  • per le stringhe è non è vulnerabile. Fintanto che le stringhe vengono citate e sfuggite, esse non possono "modificare o eliminare dati maliziosamente". *
  • per gli altri dati tipizzati - sì, è inutile. Ma non perché è un po '"non sicuro" ma solo a causa di un uso improprio.

Per quanto riguarda i dati di visualizzazione, suppongo che è offtopic nel PDO legati questione, come DOP non ha nulla a che fare con la visualizzazione dei dati sia.

fuoriuscita input dell'utente

^^^ Un'altra illusione da notare!

  • un input dell'utente non ha assolutamente nulla a che fare con la fuga. Come puoi imparare dalla precedente definizione, devi sfuggire alle stringhe, non a qualsiasi "input dell'utente". Così, ancora una volta:

    • voi hanno stringhe di fuga, non importa di loro fonte
    • è inutile per sfuggire altri tipi di dati, a prescindere della fonte.

Hai il punto?
Ora, spero che tu capisca i limiti della fuga e il malinteso dei "personaggi pericolosi".

E 'la mia comprensione che l'uso di dichiarazioni preparate DOP/è un molto più sicuro

Non

davvero.
In realtà, ci sono quattro diverse parti della query, che siamo in grado di aggiungere in modo dinamico:

  • una stringa
  • un numero
  • un identificatore
  • una parola chiave della sintassi.

così, è possibile vedere che fughe copre solo un problema.(Ma naturalmente, se si trattano i numeri come stringhe (mettendoli in virgolette), se del caso , è possibile renderli sicuro e)

mentre le istruzioni preparate coprono - ugh - tutta 2 isues! Un grosso problema ;-)

Per gli altri 2 numeri vedere la mia precedente risposta, In PHP when submitting strings to the database should I take care of illegal characters using htmlspecialchars() or use a regular expression?

Ora, i nomi di funzione sono diversi in modo non sarà più il mio mysql_query, mysql_fetch_array, mysql_num_rows ecc lavoro.

Questo è un altro, tomba delirio di PHP utenti, un disastro naturale, una catastrofe:

Anche quando si utilizza vecchio driver mysql, non si dovrebbe mai usare le funzioni API nude nel loro codice ! Bisogna metterli in qualche funzione di libreria per l'uso quotidiano! (Non come un rito magico, ma solo per rendere il codice più breve, meno ripetitivo, a prova di errore, più coerente e leggibile).

Lo stesso vale per il PDO!

Ora avanti con la domanda.

ma con il loro utilizzo questo elimina la necessità di utilizzare qualcosa come mysql_real_escape_string?

SI.

Ma penso che questo è più o meno l'idea di ciò che dovrebbe essere fatto per andare a prendere un utente da un database

Non per andare a prendere, ma per aggiungere un qualsiasi dato alla query!

bisogna dato una lunghezza dopo DOP: PARAM_STR se non sbaglio

È possibile, ma non necessario.

Ora, è tutto sicuro?

In termini di sicurezza del database non ci sono punti deboli in questo codice. Niente da proteggere qui.

per la sicurezza di visualizzazione - basta cercare questo sito per la parola chiave XSS.

Spero di far luce sull'argomento.

BTW, per i lunghi inserti si può fare qualche uso della funzione che ho scritto un giorno, Insert/update helper function using PDO

Tuttavia, non sto usando istruzioni preparate al momento, come io preferisco i miei segnaposti fatta in casa su di loro, utilizzando una libreria ho menzionato sopra. Quindi, per contrastare il codice inviato dal Riha di sotto, sarebbe più breve queste 2 linee:

$sql = 'SELECT * FROM `users` WHERE `name`=?s AND `type`=?s AND `active`=?i'; 
$data = $db->getRow($sql,$_GET['name'],'admin',1); 

Ma naturalmente si può avere lo stesso codice utilizzando le istruzioni preparate pure.


* (yes I am aware of the Schiflett's scaring tales)

+0

Come segnare una risposta come preferita? – TehShrike

+0

Grazie :) Ora, immagino, di solito contrassegno la domanda stessa, se voglio segnalarlo come –

+0

Perché incoraggiare l'OP a utilizzare una libreria codificata quando l'OP è in realtà chiedendo come usare correttamente i PDO Il primo passo viene prima, sai? Un debuttante (che ha appena appreso che c'è di più di mysql_ *) dovrebbe davvero imparare l'uso di base dei PDO prima di prendere in considerazione anche una libreria di wrapper DB personalizzata. la tua è una PITA per leggere/capire/debug/modificare. Il codice breve non è sempre il miglior codice – riha

8

ho mai fastidio con BindParam() o tipi param o lunghezze.

me a passare un array di parametri da eseguire(), in questo modo:

$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id"); 
$stmt->execute(array(':user_id' => $user_id)); 

$stmt = $dbh->prepare("INSERT INTO `users` (username, email) 
         VALUES (:username, :email)"); 
$stmt->execute(array(':username'=>$username, ':email'=>$email)); 

Questo è altrettanto efficace, e più facile da codice.

Potresti essere interessato anche alla mia presentazione SQL Injection Myths and Fallacies o al mio libro SQL Antipatterns: Avoiding the Pitfalls of Database Programming.

+0

Questo potrebbe anche essere fatto con? segnaposti nell'array quindi? Dì "SELECT * FROM users WHERE name =? AND email =? ");' Con 'execute (array ($ name, $ email))' o non funziona in questo caso? – Joe

+0

Sì, puoi usare i parametri posizionali invece dei parametri con nome, proprio come mostri. –

2

Per rispondere alla domanda di lunghezza, specificare che è facoltativo a meno che il parametro che si sta vincolando sia un parametro OUT da una stored procedure, quindi nella maggior parte dei casi è possibile ometterlo in modo sicuro.

Per quanto riguarda la sicurezza, la fuga viene eseguita dietro le quinte quando si vincolano i parametri. Questo è possibile perché devi creare una connessione al database quando hai creato l'oggetto. Sei protetto anche dagli attacchi SQL injection poiché, preparando la dichiarazione, stai comunicando al tuo database il formato della dichiarazione prima che l'input dell'utente possa avvicinarsi ad esso. Un esempio:

$id = '1; MALICIOUS second STATEMENT'; 

mysql_query("SELECT * FROM `users` WHERE `id` = $id"); /* selects user with id 1 
                  and the executes the 
                  malicious second statement */ 

$stmt = $pdo->prepare("SELECT * FROM `users` WHERE `id` = ?") /* Tells DB to expect a 
                   single statement with 
                   a single parameter */ 
$stmt->execute(array($id)); /* selects user with id '1; MALICIOUS second 
           STATEMENT' i.e. returns empty set. */ 

Quindi, in termini di sicurezza, i vostri esempi sopra sembrano soddisfacenti.

Infine, sono d'accordo sul fatto che i parametri di binding singolarmente sono noiosi e vengono eseguiti altrettanto efficacemente con un array passato a PDOStatement-> execute() (vedere http://www.php.net/manual/en/pdostatement.execute.php).

5

Sì,: qualcosa è un segnaposto chiamato in DOP,? è un segnaposto anonimo. Consentono di associare i valori uno alla volta o tutti contemporaneamente.

Quindi, in pratica, sono disponibili quattro opzioni per fornire la query con i valori.

uno ad uno con bindValue()

Questo si lega un valore concreto alla tua segnaposto non appena si chiama. È anche possibile associare stringhe codificate in modo rigido come bindValue(':something', 'foo') se lo si desidera.

Fornire un tipo di parametro è facoltativo (ma consigliato). Tuttavia, poiché il valore predefinito è PDO::PARAM_STR, è necessario specificarlo solo quando non è una stringa. Inoltre, PDO si occuperà della lunghezza qui - non ci sono parametri di lunghezza.

$sql = ' 
    SELECT * 
    FROM `users` 
    WHERE 
    `name` LIKE :name 
    AND `type` = :type 
    AND `active` = :active 
'; 
$stm = $db->prepare($sql); 

$stm->bindValue(':name', $_GET['name']); // PDO::PARAM_STR is the default and can be omitted. 
$stm->bindValue(':type', 'admin'); // This is not possible with bindParam(). 
$stm->bindValue(':active', 1, PDO::PARAM_INT); 

$stm->execute(); 
... 

Di solito preferisco questo approccio. Lo trovo il più pulito e flessibile.

uno ad uno con bindParam()

Una variabile è legata al tuo segnaposto che verrà letto quando la query è executed, non quando BindParam() viene chiamato. Potrebbe essere o non essere quello che vuoi. È utile quando si desidera eseguire ripetutamente la query con valori diversi.

$sql = 'SELECT * FROM `users` WHERE `id` = :id'; 
$stm = $db->prepare($sql); 
$id = 0; 
$stm->bindParam(':id', $id, PDO::PARAM_INT); 

$userids = array(2, 7, 8, 9, 10); 
foreach ($userids as $userid) { 
    $id = $userid; 
    $stm->execute(); 
    ... 
} 

Si prepara e si collega solo una volta che la CPU della sicurezza attiva. :)

Tutto in una volta con nome segnaposto

Basta cadere in una matrice a execute(). Ogni chiave è un segnaposto con nome nella tua query (vedi risposta di Bill Karwins). L'ordine dell'array non è importante.

Nota a margine: con questo approccio non è possibile fornire PDO con suggerimenti sul tipo di dati (PDO :: PARAM_INT ecc.). AFAIK, DOP prova a indovinare.

Tutto in una volta con i segnaposto anonimi

È anche cadere in un array a execute(), ma è indicizzato numericamente (non ci sono chiavi stringa). I valori sostituiranno i segnaposti anonimi uno per uno nell'ordine in cui appaiono nella query/matrice - il primo valore di matrice sostituisce il segnaposto e così via. Vedi la risposta di erm410.

Come con la matrice e i segnaposti con nome, non è possibile fornire suggerimenti sul tipo di dati.

Che cosa hanno in comune

  • Tutti coloro che richiedono di impegnare/fornire i valori per quanto avete segnaposto. Se leghi troppi/pochi, DOP mangerà i tuoi figli.
  • Non devi preoccuparti dell'escursione, PDO lo gestisce. Le istruzioni DOP preparate sono sicure per l'iniezione SQL. Tuttavia, questo non è vero per exec() e query() - in genere si dovrebbero usare solo questi due per le query con hardcoded.

ricordiamo inoltre che DOP genera eccezioni. Questi potrebbero rivelare informazioni potenzialmente sensibili all'utente. Dovresti almeno mettere la tua configurazione iniziale di DOP in in un blocco try/catch!

Se non si desidera eseguire Eccezioni in un secondo momento, è possibile impostare la modalità errore su avviso.

try { 
    $db = new PDO(...); 
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING) 
} catch (PDOException $e) { 
    echo 'Oops, something went wrong with the database connection.'; 
} 
+0

Quindi il blocco try/catch è simile a un'istruzione if/else? E grazie per aver sottolineato le diverse opzioni, questo dovrebbe darmi qualcosa con cui giocare! – Joe

+0

Un'altra domanda veloce, per cosa sarebbe usato '$ e', presumo che tu stia facendo riferimento a un errore? – Joe

+0

Non proprio. Se un'eccezione viene lanciata in qualsiasi punto del blocco try, interrompe l'esecuzione del blocco try ed esegue il blocco catch. $ e è l'eccezione che è stata lanciata. Contiene il messaggio di errore/codice/file/riga ecc. E può essere utilizzato per inviare una email all'amministratore o accedere a un file in modo da poter indagare su cosa è andato storto. Riepilogo: try/catch è eccezionalmente buono per la gestione degli errori. Che pazzo haha. – riha

Problemi correlati