Den rigtige måde at undgå SQL-injektionsangreb på, uanset hvilken database du bruger, er at adskille dataene fra SQL , så data forbliver data og aldrig bliver fortolket som kommandoer af SQL-parseren. Det er muligt at oprette SQL-sætning med korrekt formaterede datadele, men hvis du ikke fuldt forstå detaljerne, bør du altid bruge forberedte udsagn og parametriserede forespørgsler. Disse er SQL-sætninger, der sendes til og parses af databaseserveren adskilt fra eventuelle parametre. På denne måde er det umuligt for en angriber at injicere ondsindet SQL.
Du har grundlæggende to muligheder for at opnå dette:
-
Brug af PDO (for enhver understøttet databasedriver):
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute([ 'name' => $name ]); foreach ($stmt as $row) { // Do something with $row }
-
Brug af MySQLi (for 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 }
Hvis du opretter forbindelse til en anden database end MySQL, er der en driverspecifik anden mulighed, som du kan henvise til (f.eks. pg_prepare()
og pg_execute()
til PostgreSQL). PDO er den universelle mulighed.
Korrekt opsætning af forbindelsen
Bemærk, at når du bruger PDO for at få adgang til en MySQL-database rigtig forberedte udsagn bruges ikke som standard . For at rette op på dette skal du deaktivere emuleringen af forberedte udsagn. Et eksempel på oprettelse af en forbindelse ved hjælp af PDO er:
$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);
I ovenstående eksempel er fejltilstanden ikke strengt nødvendig, men det anbefales at tilføje den . På denne måde stopper scriptet ikke med en Fatal Error
når noget går galt. Og det giver udvikleren chancen for at catch
enhver fejl, der er throw
n som PDOException
s.
Hvad er obligatorisk er dog den første setAttribute()
linje, som fortæller PDO at deaktivere emulerede forberedte sætninger og bruge rigtige udarbejdede erklæringer. Dette sikrer, at sætningen og værdierne ikke parses af PHP, før den sendes til MySQL-serveren (hvilket giver en eventuel angriber ingen chance for at injicere ondsindet SQL).
Selvom du kan indstille charset
i konstruktørens muligheder er det vigtigt at bemærke, at 'ældre' versioner af PHP (før 5.3.6) ignorerede charset-parameteren stille og roligt
i DSN.
Forklaring
SQL-sætningen du sender til prepare
parses og kompileres af databaseserveren. Ved at specificere parametre (enten en ?
eller en navngivet parameter som :name
i eksemplet ovenfor) fortæller du databasemotoren, hvor du vil filtrere på. Så når du kalder execute
, kombineres den forberedte sætning med de parameterværdier, du angiver.
Det vigtige her er, at parameterværdierne kombineres med den kompilerede sætning, ikke en SQL-streng. SQL-injektion virker ved at narre scriptet til at inkludere ondsindede strenge, når det opretter SQL til at sende til databasen. Så ved at sende selve SQL'en adskilt fra parametrene, begrænser du risikoen for at ende med noget, du ikke havde til hensigt.
Eventuelle parametre, du sender, når du bruger en forberedt erklæring, vil bare blive behandlet som strenge (selvom databasemotoren muligvis udfører en vis optimering, så parametre også kan ende som tal, selvfølgelig). I eksemplet ovenfor, hvis $name
variabel indeholder 'Sarah'; DELETE FROM employees
resultatet ville blot være en søgning efter strengen "'Sarah'; DELETE FROM employees"
, og du ender ikke med et tomt bord
.
En anden fordel ved at bruge forberedte sætninger er, at hvis du udfører den samme sætning mange gange i samme session, vil den kun blive parset og kompileret én gang, hvilket giver dig nogle hastighedsforøgelser.
Åh, og siden du spurgte om, hvordan man gør det for en indsats, er her et eksempel (ved brug af PDO):
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');
$preparedStatement->execute([ 'column' => $unsafeValue ]);
Kan forberedte udsagn bruges til dynamiske forespørgsler?
Selvom du stadig kan bruge forberedte sætninger til forespørgselsparametrene, kan strukturen af selve den dynamiske forespørgsel ikke parametriseres, og visse forespørgselsfunktioner kan ikke parametriseres.
For disse specifikke scenarier er den bedste ting at gøre at bruge et hvidlistefilter, der begrænser de mulige værdier.
// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
$dir = 'ASC';
}