PHP - Är detta "prepared statement" säkert?

Permalänk
Medlem

PHP - Är detta "prepared statement" säkert?

Skulle den här PHP-koden skydda mot SQL-injection eller måste jag använda en placeholder och skicka variabeln som ett argument till execute()?

$results = $db->prepare("SELECT * FROM table WHERE id IN (".$var.")");
$results->execute();

Permalänk
Medlem

PHP-dokumentationen säger följande:

"mysqli::prepare -- mysqli_prepare — Prepare an SQL statement for execution"

Men jag tror att du bör göra följande:

$query = $db->prepare("SELECT * FROM table WHERE id IN (".$var.")");
$query->execute();
$results = $query->get_result();

Permalänk

Vad menar du med säkert? Att det inte går att skicka en något till databasen som du inte vill ha där?

Visa signatur

Intel core i5 6600k, Sapphire Radeon R9 380 Nitro 4GB, 16GB RAM
MacBook Pro 13 tum 2017 i7 16GB RAM 512GB SSD

Permalänk
Legendarisk

@Murloc: Nej, det är inte säkert. Den säkerhetsmässiga fördelen med prepared statements kommer utav att frågans struktur separeras från de värden som ska skickas; när strukturen har tolkats kan värdena inte påverka denna och även om värdena i sig kan vara något än vad du har tänkt dig så kan de inte direkt påverka vad som ska utföras.

I ditt exempel är det viktigt att separera vad som händer i PHP från vad som händer i databasen. Din sträng kommer expanderas till att inkludera vad nu $var innehåller innan databasen kan bedöma den alls, och den har ingen möjlighet att veta om detta påverkar vad du avsett att frågan ska göra eller ej. Ponera att du vill förbereda en fråga likt "DELETE FROM yourTable WHERE id = ".$var.";" men att $var = "1 OR id > 0", din fråga kommer alltså expanderas till DELETE FROM yourTable WHERE id = 1 OR id > 0; och blir det du säger till databasen att förbereda; det är uppenbarligen inte vad du hade tänkt dig men det finns inget databasen kan göra för att förhindra det.

Lösningen är att använda platshållare för att berätta för databasen var du vill använda egna värden, inte vad dessa ska vara: DELETE FROM yourTable WHERE id = ?; Nu kan databasen förstå frågans struktur oberoende av värdet, och inte göra något annat än radera rader med id motsvarande värdet.

# # Exempel med PDO # $stmt = $conn->prepare("DELETE FROM yourTable WHERE id = :id;"); $stmt->bindParam(":id", $id, \PDO::PARAM_INT); $stmt->execute(); # # Exempel med Mysqli # $stmt = $conn->prepare("DELETE FROM yourTable WHERE id = ?;"); $stmt->bind_param("i", $id); $stmt->execute();

Det är dock viktigt att ha i åtanke att detta endast hjälper till med att skydda mot så kallad SQL injection; det är fortfarande ditt ansvar att kontrollera att värdet är giltigt innan du skickar det till databasen. Det involverar bland annat att kontrollera om det är av rätt typ, inom ett rimligt intervall, av rätt teckenkodning (särskilt om du använder Unicode), att du hanterar fel korrekt om frågan skulle misslyckas, att användaren ö.h.t. har rätt att utföra operationen på det objektet etc. Prepared statements ersätter aldrig korrekt validering av användarinput, de gör det bara lite svårare att göra fel.

Visa signatur

Abstractions all the way down.

Permalänk
Medlem

@Tunnelsork Tack för ett mycket informativt svar. Jag brukar köra med placeholders för värden precis som man ska göra, men jag har stött på en situation där det inte fungerar så bra. Jag har kod som ser ut så här:

$var = "11,12,13";
$results = $db->prepare("SELECT * FROM table WHERE id IN (?)");
$results->execute(array($var));

Av någon anledning innehåller nu $results bara en rad, och det är den med id=11, resten kommer inte med. Koden jag skrev i mitt första inlägg fungerar dock och ger mig tre rader precis som den ska. Vet du hur jag kan få det att fungera?

Permalänk
Legendarisk

@Murloc: Vad som händer är att du skickar "11,12,13" som ett enda argument, en sträng, till databasen och när den försöker jämföra värdet mot kolumnen så konverteras det till ett heltal: 11 (exempel A, exempel B). Du måste hantera dina ID:n separat och formatera frågan så att den har lika många placeholders som du har värden för att det ska fungera som avsett: SELECT * FROM yourTable WHERE id IN(?, ?, ?); Ett vanligt sätt att göra det är via array_fill():

$var = [11, 12, 13]; // En array istället för en sträng $query = sprintf( "SELECT * FROM yourTable WHERE id IN (%s);", implode(", ", array_fill(0, count($var), "?")) // array_fill returnerar ["?","?","?"] ); // $query innehåller nu SELECT * FROM yourTable WHERE id IN(?, ?, ?); $stmt = $conn->prepare($query); $stmt->execute($var);

Visa signatur

Abstractions all the way down.