De juiste manier om SQL-injectie-aanvallen te voorkomen, ongeacht welke database u gebruikt, is om de gegevens te scheiden van SQL , zodat gegevens gegevens blijven en nooit worden geïnterpreteerd als opdrachten door de SQL-parser. Het is mogelijk om een SQL-instructie te maken met correct opgemaakte gegevensdelen, maar als u dit niet volledig doet Als u de details begrijpt, moet u altijd voorbereide instructies en geparametriseerde zoekopdrachten gebruiken. Dit zijn SQL-instructies die afzonderlijk van parameters naar de databaseserver worden verzonden en door de databaseserver worden geparseerd. Op deze manier is het voor een aanvaller onmogelijk om kwaadaardige SQL te injecteren.
Je hebt in principe twee opties om dit te bereiken:
-
BOB gebruiken (voor elk ondersteund databasestuurprogramma):
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute([ 'name' => $name ]); foreach ($stmt as $row) { // Do something with $row }
-
MySQLi gebruiken (voor 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 }
Als u verbinding maakt met een andere database dan MySQL, is er een driverspecifieke tweede optie waarnaar u kunt verwijzen (bijvoorbeeld pg_prepare()
en pg_execute()
voor PostgreSQL). PDO is de universele optie.
Correct opzetten van de verbinding
Merk op dat bij gebruik van BOB om toegang te krijgen tot een MySQL-database echt voorbereide verklaringen worden standaard niet gebruikt . Om dit op te lossen, moet u de emulatie van voorbereide verklaringen uitschakelen. Een voorbeeld van het maken van een verbinding met behulp van BOB is:
$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);
In het bovenstaande voorbeeld is de foutmodus niet strikt noodzakelijk, maar het wordt aangeraden om deze toe te voegen . Op deze manier stopt het script niet met een Fatal Error
wanneer er iets misgaat. En het geeft de ontwikkelaar de kans om catch
eventuele fout(en) die throw
. zijn n als PDOException
v.
Wat is verplicht , is echter de eerste setAttribute()
regel, die PDO vertelt om geëmuleerde voorbereide verklaringen uit te schakelen en real . te gebruiken verklaringen opgesteld. Dit zorgt ervoor dat de instructie en de waarden niet door PHP worden geparseerd voordat ze naar de MySQL-server worden verzonden (waardoor een mogelijke aanvaller geen kans krijgt om kwaadaardige SQL te injecteren).
Hoewel u de charset
. kunt instellen in de opties van de constructor is het belangrijk op te merken dat 'oudere' versies van PHP (vóór 5.3.6) stilte negeerde de charset parameter
in de DSN.
Uitleg
De SQL-instructie die u doorgeeft aan prepare
wordt geparseerd en gecompileerd door de databaseserver. Door parameters op te geven (ofwel een ?
of een benoemde parameter zoals :name
in het bovenstaande voorbeeld) vertel je de database-engine waar je op wilt filteren. Wanneer u vervolgens execute
. aanroept, , wordt de voorbereide instructie gecombineerd met de parameterwaarden die u opgeeft.
Het belangrijkste hier is dat de parameterwaarden worden gecombineerd met de gecompileerde instructie, niet met een SQL-string. SQL-injectie werkt door het script te misleiden om kwaadaardige tekenreeksen op te nemen wanneer het SQL maakt om naar de database te verzenden. Dus door de eigenlijke SQL apart van de parameters te sturen, beperk je het risico dat je eindigt met iets wat je niet bedoeld had.
Alle parameters die u verzendt wanneer u een voorbereide instructie gebruikt, worden alleen als tekenreeksen behandeld (hoewel de database-engine enige optimalisatie kan doen, zodat parameters natuurlijk ook als getallen kunnen eindigen). Als in het bovenstaande voorbeeld de $name
variabele bevat 'Sarah'; DELETE FROM employees
het resultaat zou gewoon een zoekopdracht zijn naar de string "'Sarah'; DELETE FROM employees"
, en je krijgt geen een lege tafel
.
Een ander voordeel van het gebruik van voorbereide instructies is dat als u dezelfde instructie vele malen in dezelfde sessie uitvoert, deze slechts één keer wordt geparseerd en gecompileerd, wat u wat snelheidswinst oplevert.
Oh, en aangezien je vroeg hoe je het voor een insert moest doen, hier is een voorbeeld (met PDO):
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');
$preparedStatement->execute([ 'column' => $unsafeValue ]);
Kunnen voorbereide instructies worden gebruikt voor dynamische zoekopdrachten?
Hoewel u nog steeds voorbereide instructies voor de queryparameters kunt gebruiken, kan de structuur van de dynamische query zelf niet worden geparametreerd en kunnen bepaalde queryfuncties niet worden geparametreerd.
Voor deze specifieke scenario's kunt u het beste een whitelist-filter gebruiken dat de mogelijke waarden beperkt.
// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
$dir = 'ASC';
}