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
  • DGUV V3
  • DGUV 3
  • CasinoAndy Finland
  • Casinopilot24.com
  • Neueonline-Casinos.com
  • CasinoHEX.at
  • Decasinos.de
  • Privatkredit247.com
  • Online Casino Spielautomaten
  • Casinofrog.com
  • parhaatuudetkasinot.com

Hoster – Geringste Ausfallzeit

  1. webgo Ø 0 Min.
  2. Linevast Ø 3 Min.
  3. netcup Ø 3 Min.
  4. dogado Ø 6 Min.
  5. Mittwald Ø 9 Min.
  6. All-Inkl.com Ø 10 Min.
  7. manitu Ø 10 Min.
  8. bplaced Ø 11 Min.
  9. checkdomain Ø 11 Min.
  10. Host Europe Ø 14 Min.
» Mehr erfahren

Impressum | Datenschutz | Auf PHP-Einfach.de werben

© PHP-Einfach.de 2003 - 2022

Um dich beim Lernen von PHP und MySQL zu unterstützen verwenden wir Cookies. OK Weitere Infos
Privacy & Cookies Policy

Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Notwendige
immer aktiv

Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.

Nicht notwendige

Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.