PHP-Einfach.de
  • PHP Tutorial
  • MySQL Tutorial
  • Für Fortgeschrittene
  • Webhosting
  • Forum

Prepared Statements

3. Mai 2019
  1. Home
  2. »
  3. MySQL Tutorial
  4. »
  5. Prepared Statements

Bisher haben wir stets feste Suchkriterien gehabt, beispielsweise alle User mit einer ID kleiner 5. Dies ist wenig spannend und meistens wollen wir die Suchkriterien dynamisch gestalten, beispielsweise wenn der Besucher nach einer gewissen E-Mail-Adresse suchen möchte.

Inhaltsverzeichnis

  • 1 Gefährlicher Code und SQL-Injections
  • 2 Prepared Statements
  • 3 Benannte Parameter
  • 4 LIMIT mit Parametern

Gefährlicher Code und SQL-Injections

Der einfachste Gedanke wäre wie folgt (diese Variante ist nicht zu empfehlen):

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');

if(isset($_GET['id'])) {
   $id = $_GET['id'];
} else {
   die("Bitte eine ?id übergeben");
} 

echo "User mit der ID $id: <br>";
$sql = "SELECT * FROM users WHERE id = $id";
foreach ($pdo->query($sql) as $row) {
   echo $row['vorname']." ".$row['nachname']."<br />";
   echo "E-Mail: ".$row['email']."<br /><br />";
}
?>

Dies funktioniert zwar, ist aber anfällig für sogenannte SQL Injections. Ein Angreifer kann über den GET-Parameter unsere SQL-Abfrage manipulieren und weiteren SQL-Code einschleusen. Im schlimmsten Fall werden dadurch sensible Daten ausgegeben, Tabelle verändert oder gar ganze Tabellen gelöscht.

Ruft die Seite wie folgt auf um dies zu sehen:
seite.php?id=1 OR id > 1

Wie ihr seht, werden nun alle Benutzer ausgegeben, denn an die Datenbank wird folgender Befehl gesendet:

1
$sql = "SELECT * FROM users WHERE id = 1 OR id > 1";

Prepared Statements

Um SQL-Injections zu verhindern empfiehlt sich der Einsatz von prepared statements . Sobald ihr irgendwelche Daten vom Benutzer an die Datenbank übergebt, sollte ihr stets auf prepared Statements zurückgreifen.

Unser Script von oben sieht dann wie folgt aus:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');

if(isset($_GET['id'])) {
   $id = $_GET['id'];
} else {
   die("Bitte eine ?id übergeben");
} 

echo "User mit der ID $id: <br>";
$statement = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$statement->execute(array($id));   
while($row = $statement->fetch()) {
   echo $row['vorname']." ".$row['nachname']."<br />";
   echo "E-Mail: ".$row['email']."<br /><br />";
}
?>

Dies scheint auf den ersten Blick etwas komplizierter als zuvor zu sein, mit etwas Hintergrundwissen aber recht einfach. Zuerst führen wir $pdo->prepare($sql) und erhalten ein prepared Statement, also eine vorbereitetes Statement. In diesem Statement haben wir definiert, dass wir den Wert für id gerne als Parameter vom Benutzer setzen wollen indem wir dort ein ? stehen haben.

Mit $statement->execute(array($id)); führen wir das Statement aus und übergeben als Wert für den Parameter den Wert in der Variable $id. Anschließend können wir mittels der while-Schleife durch alle Einträge iterieren. Das $statement->fetch() liefert einen neuen Datenbankeintrag, oder false, falls keine weiteren Einträge mehr vorhanden sind.

Mittels prepared Statements seid ihr gegen SQL-Injections geschützt. Wenn ihr die Seite mittels seite.php?id=1 OR id > 1 aufruft, werdet ihr feststellen, dass kein Eintrag ausgegeben wird. Denn in der Datenbank wird geschaut, ob ein Eintrag mit der ID '1 OR id > 1' vorhanden ist, was nicht der Fall ist.

Benannte Parameter

Neben anonymen Parametern wie im vorherigen Beispiel die ihr mittels ? angebt, könnt ich auch benannte Parameter nutzen. Statt dem Fragezeichen schreibt ihr dann :name, sprich, ihr beginnt mit einem Doppelpunkt und dann dem Namen. Ein Minus darf im Namen nicht enthalten sein, nutzt dort stattdessen den Unterstrich.

Die Benennung von Parametern ist besonders praktisch, wenn ihr mehr als nur einen Parameter in dem SQL-Query habt. So lauft ihr nicht Gefahr, aus Versehen die falschen Werte zu übergeben. Eure Parameternamen müssen nicht so heißen wie eure Spaltennamen, auch wenn dies stark zu empfehlen ist:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');

$vorname = "Max";
$nachname = "Mustermann";
$email = "[email protected]";

$statement = $pdo->prepare("SELECT * FROM users WHERE vorname = :vorname OR nachname = :nachname OR email = :email");
$statement->execute(array('vorname' => $vorname, 'nachname' => $nachname, 'email' => $email));   
while($row = $statement->fetch()) {
   echo $row['vorname']." ".$row['nachname']."<br />";
   echo "E-Mail: ".$row['email']."<br /><br />";
}
?>

Möchten wir mittels LIKE und % nach einem gewissen Wort suchen, dann geht dies korrekterweise wie folgt:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');

$suchwort = "Diet";
$statement = $pdo->prepare("SELECT * FROM users WHERE vorname LIKE :vorname");
$statement->execute(array('vorname' => "%$suchwort%"));   
while($row = $statement->fetch()) {
   echo $row['vorname']." ".$row['nachname']."<br />";
   echo "E-Mail: ".$row['email']."<br /><br />";
}
?>

LIMIT mit Parametern

Möchten wir die LIMIT-Anweisung mit Parametern ausstatten, beispielsweise damit der Benutzer entscheiden kann wie viele Datensätze anzeigt werden sollen, so müssen wir einen kleinen Trick anwenden damit dies mittels Prepared Statements funktioniert.

Folgender Code führt zu einer Fehlermeldung:

1
2
3
4
5
6
7
8
9
10
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');
 
$statement = $pdo->prepare("SELECT * FROM users LIMIT :limit");
$statement->execute(array('limit' => 5));  
while($row = $statement->fetch()) {
   echo $row['vorname']." ".$row['nachname']."<br />";
   echo "E-Mail: ".$row['email']."<br /><br />";
}
?>

Diese Fehlermeldung resultiert daraus, dass PDO standardmäßig Prepared Statements nur emuliert (weitere Infos zu emulierten Statements). Da hierbei nicht unterschieden wird ob wir eine Zahl oder einen String als Parameter angegeben wurde, konstruiert PHP daraus folgenden Query, der natürlich fehlerhaft ist:

1
SELECT * FROM users LIMIT '5'

Um dieses Problem zu lösen, existieren zwei Vorgehensweisen. Die erste ist die Deaktivierung der emulierten Prepared Statements, dadurch werden die tatsächlichen von MySQL bereitgestellten Prepared Statements verwendet. Der Code sieht dann wie folgt aus:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

$statement = $pdo->prepare("SELECT * FROM users LIMIT :limit");
$statement->execute(array('limit' => 5));   
while($row = $statement->fetch()) {
   echo $row['vorname']." ".$row['nachname']."<br />";
   echo "E-Mail: ".$row['email']."<br /><br />";
}
?>

Durch das setzen des Attributs PDO::ATTR_EMULATE_PREPARES auf false wird das Emulieren deaktiviert. Das Deaktivieren hat keine negativen Auswirkungen auf eure Scripts. Diese sind immer noch genauso performant und genauso sicher wie zuvor. Das Abschalten der Emulation funktioniert nur, wenn ihr  MySQL5 oder neuer verwendet. Mehr Informationen.

Solltet ihr mit einer veralteten MySQL-Version arbeiten müssen, oder mit einer anderen Datenbank die keine Prepared Statements unterstützt, so könnt ihr das obige Problem lösen indem ihr den Typ des Parameters als int definiert:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');

$limit = 5;

$statement = $pdo->prepare("SELECT * FROM users LIMIT :limit");
$statement->bindParam('limit', $limit, PDO::PARAM_INT);
$statement->execute();   
while($row = $statement->fetch()) {
   echo $row['vorname']." ".$row['nachname']."<br />";
   echo "E-Mail: ".$row['email']."<br /><br />";
}
?>

Mittels der Methode $statement->bindParam() wird die Variable $limit mit dem Parameter :limit verbunden. Als Typ für den Parameter wurde mittels der Konstanten PDO:PARAM_INT definiert dass es sicher hierbei um einen Integer handelt.

 

Autor: Nils Reimers
Zurück: Komplexere Datenabfrage per SELECT
Weiter: Daten einfügen per INSERT

MySQL Tutorial

  • MySQL installieren und starten
  • phpMyAdmin
  • Verbindung aufbauen
  • Simple Datenabfrage
  • Komplexere Datenabfrage
  • Prepared Statements
  • Daten einfügen
  • Daten aktualisieren
  • Daten löschen
  • Gefundene Einträge zählen
  • DATE-Datenbankspalten
  • Datum- und Zeitfunktionen
  • MySQL Fehlermeldungen
  • Datenbankverbindung schließen
  • JOIN
  • Indizes
  • Normalformen
  • Fremdschlüssel
  • Übersicht SQL-Befehle
  • Crashkurs MySQL
  • Crashkurs MySQLi
  • Crashkurs PDO
Mit freundlicher Unterstützung von:
  • Punkt191 Werbeagentur

Hoster – Geringste Ausfallzeit

  1. netcup Ø 0 Min.
  2. webgo Ø 0 Min.
  3. Linevast Ø 3 Min.
  4. All-Inkl.com Ø 3 Min.
  5. checkdomain Ø 4 Min.
  6. dogado Ø 6 Min.
  7. Strato Ø 8 Min.
  8. manitu Ø 10 Min.
  9. 1&1 Ø 10 Min.
  10. DomainFactory Ø 14 Min.
» Mehr erfahren

Impressum | Datenschutz | Auf PHP-Einfach.de werben

© PHP-Einfach.de 2003 - 2025

Cookie-Zustimmung verwalten
Um dir ein optimales Erlebnis zu bieten, verwenden wir Technologien wie Cookies, um Geräteinformationen zu speichern und/oder darauf zuzugreifen. Wenn du diesen Technologien zustimmst, können wir Daten wie das Surfverhalten oder eindeutige IDs auf dieser Website verarbeiten. Wenn du deine Zustimmung nicht erteilst oder zurückziehst, können bestimmte Merkmale und Funktionen beeinträchtigt werden.
Funktional Immer aktiv
Die technische Speicherung oder der Zugang ist unbedingt erforderlich für den rechtmäßigen Zweck, die Nutzung eines bestimmten Dienstes zu ermöglichen, der vom Teilnehmer oder Nutzer ausdrücklich gewünscht wird, oder für den alleinigen Zweck, die Übertragung einer Nachricht über ein elektronisches Kommunikationsnetz durchzuführen.
Vorlieben
Die technische Speicherung oder der Zugriff ist für den rechtmäßigen Zweck der Speicherung von Präferenzen erforderlich, die nicht vom Abonnenten oder Benutzer angefordert wurden.
Statistiken
Die technische Speicherung oder der Zugriff, der ausschließlich zu statistischen Zwecken erfolgt. Die technische Speicherung oder der Zugriff, der ausschließlich zu anonymen statistischen Zwecken verwendet wird. Ohne eine Vorladung, die freiwillige Zustimmung deines Internetdienstanbieters oder zusätzliche Aufzeichnungen von Dritten können die zu diesem Zweck gespeicherten oder abgerufenen Informationen allein in der Regel nicht dazu verwendet werden, dich zu identifizieren.
Marketing
Die technische Speicherung oder der Zugriff ist erforderlich, um Nutzerprofile zu erstellen, um Werbung zu versenden oder um den Nutzer auf einer Website oder über mehrere Websites hinweg zu ähnlichen Marketingzwecken zu verfolgen.
Optionen verwalten Dienste verwalten Anbieter verwalten Lese mehr über diese Zwecke
Einstellungen ansehen
{title} {title} {title}