Den rätta sättet att undvika SQL-injektionsattacker, oavsett vilken databas du använder, är att separera data från SQL , så att data förblir data och aldrig tolkas som kommandon av SQL-tolkaren. Det är möjligt att skapa SQL-sats med korrekt formaterade datadelar, men om du inte helt förstår detaljerna bör du alltid använda förberedda satser och parametriserade frågor. Dessa är SQL-satser som skickas till och analyseras av databasservern separat från alla parametrar. På så sätt är det omöjligt för en angripare att injicera skadlig SQL.
Du har i princip två alternativ för att uppnå detta:
-
Använda PDO (för alla databasdrivrutiner som stöds):
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute([ 'name' => $name ]); foreach ($stmt as $row) { // Do something with $row }
-
Använda MySQLi (för 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 }
Om du ansluter till en annan databas än MySQL, finns det ett drivrutinsspecifikt andra alternativ som du kan hänvisa till (till exempel pg_prepare()
och pg_execute()
för PostgreSQL). PDO är det universella alternativet.
Korrekt inställning av anslutningen
Observera att när du använder PDO för att komma åt en MySQL-databas riktig förberedda uttalanden används inte som standard . För att fixa detta måste du inaktivera emuleringen av förberedda uttalanden. Ett exempel på att skapa en anslutning med PDO är:
$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 exemplet ovan är felläget inte strikt nödvändigt, men det rekommenderas att lägga till det . På så sätt kommer skriptet inte att sluta med ett Fatal Error
när något går fel. Och det ger utvecklaren chansen att catch
eventuella fel som är throw
n som PDOException
s.
Vad är obligatoriskt , dock är den första setAttribute()
linje, som talar om för PDO att inaktivera emulerade förberedda uttalanden och använda riktiga upprättade utlåtanden. Detta säkerställer att uttalandet och värdena inte analyseras av PHP innan det skickas till MySQL-servern (vilket ger en eventuell angripare ingen chans att injicera skadlig SQL).
Även om du kan ställa in charset
i konstruktorns alternativ är det viktigt att notera att "äldre" versioner av PHP (före 5.3.6) ignorerade teckenuppsättningsparametern tyst
i DSN.
Förklaring
SQL-satsen du skickar till prepare
tolkas och kompileras av databasservern. Genom att ange parametrar (antingen en ?
eller en namngiven parameter som :name
i exemplet ovan) berättar du för databasmotorn var du vill filtrera på. Sedan när du anropar execute
, kombineras den förberedda satsen med de parametervärden du anger.
Det viktiga här är att parametervärdena kombineras med den kompilerade satsen, inte en SQL-sträng. SQL-injektion fungerar genom att lura skriptet att inkludera skadliga strängar när det skapar SQL för att skicka till databasen. Så genom att skicka den faktiska SQL-koden separat från parametrarna, begränsar du risken att hamna i något du inte tänkt.
Alla parametrar du skickar när du använder en förberedd sats kommer bara att behandlas som strängar (även om databasmotorn kan göra viss optimering så att parametrar också kan sluta som siffror, naturligtvis). I exemplet ovan, om $name
variabel innehåller 'Sarah'; DELETE FROM employees
resultatet skulle helt enkelt bli en sökning efter strängen "'Sarah'; DELETE FROM employees"
, och du kommer inte att få ett tomt bord
.
En annan fördel med att använda förberedda satser är att om du kör samma sats många gånger i samma session kommer den bara att analyseras och kompileras en gång, vilket ger dig en viss hastighetsökning.
Åh, och eftersom du frågade om hur man gör det för en insert, här är ett exempel (med PDO):
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');
$preparedStatement->execute([ 'column' => $unsafeValue ]);
Kan förberedda satser användas för dynamiska frågor?
Även om du fortfarande kan använda förberedda satser för frågeparametrarna, kan strukturen för själva den dynamiska frågan inte parametriseras och vissa frågefunktioner kan inte parametriseras.
För dessa specifika scenarier är det bästa du kan göra att använda ett vitlistafilter som begränsar de möjliga värdena.
// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
$dir = 'ASC';
}