Da­ten­bank­ma­nage­ment­sys­te­me (DBMS), die mit der Da­ten­bank­spra­che SQL arbeiten, erfreuen sich zwar großer Be­liebt­heit, sind aber auch seit jeher anfällig für Ma­ni­pu­la­tio­nen bei der Da­ten­ein­spei­sung. So sind Be­nut­zer­ein­ga­ben, die nicht aus­rei­chend maskiert sind und Me­ta­zei­chen wie das An­füh­rungs­zei­chen oder das Semikolon enthalten, für Angreifer ein ge­fun­de­nes Fressen. Eine mögliche Antwort auf dieses Problem ist der Einsatz von Prepared State­ments, also vor­ge­fer­tig­ten An­wei­sun­gen an die Datenbank, die erst zur Laufzeit mit Werten versehen werden.

Was macht diese Technik so besonders und wo kommt sie überall zum Einsatz? Wir zeigen am Beispiel von MySQL, wie Prepared State­ments funk­tio­nie­ren und zum Da­ten­bank­ma­nage­ment ein­ge­setzt werden können.

Was sind Prepared State­ments?

Prepared State­ments (dt. „vor­be­rei­te­te An­wei­sun­gen“) sind ein­satz­fer­ti­ge Muster für Abfragen in SQL-Da­ten­bank­sys­te­men, die keine Werte für die einzelnen Parameter enthalten. Statt­des­sen arbeiten diese An­wei­sungs­mus­ter (auch An­wei­sungs­tem­pla­tes genannt) mit Variablen bzw. Platz­hal­tern, die erst innerhalb des Systems durch die tat­säch­li­chen Werte ersetzt werden – anders als bei der manuellen Eingabe, wo die Werte bereits zum Zeitpunkt der Aus­füh­rung eines Befehls zu­ge­ord­net sind.

Alle großen Da­ten­bank­ma­nage­ment­sys­te­me auf SQL-Basis wie MySQL, MariaDB, Oracle, Microsoft SQL Server oder Post­greS­QL un­ter­stüt­zen Prepared State­ments, wobei die meisten dieser An­wen­dun­gen zu diesem Zweck auf ein NoSQL-Bi­när­pro­to­koll zu­rück­grei­fen. Einige Systeme – u. a. MySQL – nutzen al­ler­dings auch ge­wöhn­li­che SQL-Syntax für die Im­ple­men­tie­rung. Zudem un­ter­stüt­zen einige Pro­gram­mier­spra­chen wie Java, Perl, Python und PHP Prepared State­ments durch ihre Stan­dard­bi­blio­the­ken oder Er­wei­te­run­gen. Nutzen Sie etwa letzt­ge­nann­te Skript­spra­che für den Da­ten­bank­zu­griff, können Sie wahlweise die ob­jekt­ori­en­tier­te Schnitt­stel­le PHP Data Objects (PDO) oder die PHP-Er­wei­te­rung MySQLi nutzen, um Prepared State­ments ein­zu­set­zen.

Warum ist der Einsatz von Prepared State­ments in MySQL und Co. so sinnvoll?

Der ent­schei­den­de Grund dafür, in Da­ten­bank­ma­nage­ment­sys­te­men wie MySQL mit Prepared State­ments zu arbeiten, ist der Aspekt Si­cher­heit. Das wohl größte Problem an stan­dard­mä­ßi­gen Zugriffen auf Da­ten­ban­ken mit SQL-Sprach­hin­ter­grund ist nämlich die Tatsache, dass diese sich leicht ma­ni­pu­lie­ren lassen. Man spricht in diesem Fall von einer SQL-Injection, bei der Code hin­zu­ge­fügt oder angepasst wird, um an sensible Daten zu gelangen oder gar gänzlich die Kontrolle über das Da­ten­bank­sys­tem zu erlangen. Prepared State­ments in PHP bzw. anderen Sprachen bieten eine solche Si­cher­heits­lü­cke nicht, da ihnen erst innerhalb des Systems konkrete Werte zu­ge­ord­net werden.

Hinweis

Vor­aus­set­zung für den hohen Si­cher­heits­stan­dard von Prepared State­ments ist, dass keine Kom­po­nen­te einer solchen vor­ge­fer­tig­ten Anweisung aus einer externen Quelle generiert wird.

Der Schutz vor SQL-In­jec­tions ist aber nicht das einzige Argument, das für den Einsatz von An­wei­sungs­mus­tern spricht: Einmal ana­ly­siert und kom­pi­liert, können diese vom je­wei­li­gen Da­ten­bank­sys­tem im Anschluss immer wieder verwendet werden (mit den je­wei­li­gen, ab­ge­än­der­ten Werten). Dadurch ver­brau­chen Prepared State­ments we­sent­lich weniger Res­sour­cen und sind schneller als manuelle Da­ten­bank­ab­fra­gen, wann immer es um SQL-Aufgaben geht, die wie­der­holt aus­ge­führt werden müssen.

Wie funk­tio­niert die Prepared-Statement-Nutzung genau?

Lässt man die Syntax der zu­grun­de­lie­gen­den Skript­spra­che und Ei­gen­hei­ten des je­wei­li­gen Da­ten­bank­ma­nage­ment­sys­tems außer Acht, erfolgt die Ein­bin­dung und Ver­wen­dung von Prepared State­ments generell in folgenden Phasen:

Phase 1: Vor­be­rei­tung des Prepared State­ments

Der erste Schritt besteht darin, dass ein An­wei­sungs­tem­p­la­te erzeugt wird – in PHP z. B. mit der Funktion prepare(). Anstelle der konkreten Werte für die re­le­van­ten Parameter werden die bereits erwähnten Platz­hal­ter eingebaut, die man auch als Bind-Variablen be­zeich­net. Ty­pi­scher­wei­se sind diese wie im folgenden Beispiel durch ein „?“ ge­kenn­zeich­net:

INSERT INTO Produkte (Name, Preis) VALUES (?, ?);

Voll­stän­di­ge Prepared State­ments werden dann an das jeweilige Da­ten­bank­ma­nage­ment­sys­tem wei­ter­ge­lei­tet.

Phase 2: Ver­ar­bei­tung des An­wei­sungs­mus­ters durch das DBMS

Vom Da­ten­bank­ma­nage­ment­sys­tem wird das An­wei­sungs­mus­ter zunächst geparst (ana­ly­siert), damit es im nächsten Schritt kom­pi­liert, also in eine aus­führ­ba­re Anweisung um­ge­wan­delt werden kann. Bei diesem Vorgang wird das Prepared Statement außerdem optimiert.

Phase 3: Aus­füh­rung des Prepared State­ments

Zu einem späteren Zeitpunkt kann das ver­ar­bei­te­te Muster im Da­ten­bank­sys­tem beliebig häufig aus­ge­führt werden. Vor­aus­set­zung hierfür ist lediglich der passende Input durch die an­ge­schlos­se­ne Anwendung bzw. eine Da­ten­quel­le, die die ent­spre­chen­den Werte für die Platz­hal­ter-Felder liefern muss. Passend zum oben auf­ge­führ­ten Code-Beispiel (Phase 1) könnte es sich bei­spiels­wei­se um die Werte „Buch“ (Name) und „10“ (Preis) oder auch um „Computer“ und „1000“ handeln.

Tutorial: So verwenden Sie Prepared State­ments in MySQL mit MySQLi

Nachdem wir die Funk­ti­ons­wei­se von Prepared State­ments im All­ge­mei­nen be­schrie­ben haben, soll das nun folgende Tutorial die Ver­wen­dung der prak­ti­schen An­wei­sun­gen mithilfe konkreter Beispiele erläutern. Ex­em­pla­risch stützt sich die Anleitung dabei auf

  • MySQL als Da­ten­bank­ma­nage­ment­sys­tem und
  • PHP als Prepared-Statement-Sprache.

Aktuelle Versionen von MySQL un­ter­stüt­zen den ser­ver­sei­ti­gen Einsatz von Prepared State­ments auf Basis eines Bi­när­pro­to­kolls, das alle SQL-Befehle enthält, die Daten ak­tua­li­sie­ren, und zudem sämtliche Ak­tua­li­sie­run­gen seit der letzten Da­ten­si­che­rung pro­to­kol­liert. Als Interface für den Zugriff dient in diesem Tutorial die PHP-Er­wei­te­rung MySQLi, die Prepared State­ments über das Bi­när­pro­to­koll ebenfalls un­ter­stützt.

Hinweis

Eine gute, häufig ver­wen­de­te Al­ter­na­ti­ve zu MySQLi als Prepared-Statement-API ist die ob­jekt­ori­en­tier­te Schnitt­stel­le PDO (PHP Data Objects). Diese Variante gilt im All­ge­mei­nen als ein­steig­er­freund­li­che­re Lösung.

PREPARE, EXECUTE und DEAL­LO­CA­TE PREPARE: Die drei ele­men­ta­ren SQL-Befehle für die Nutzung von Prepared State­ments

Drei SQL-Befehle spielen für die Anwendung von Prepared State­ments in MySQL-Da­ten­ban­ken eine ent­schei­den­de Rolle:

Das Kommando „PREPARE“ wird dazu benötigt, ein Prepared Statement für den Einsatz vor­zu­be­rei­ten und diesem u. a. einen ein­deu­ti­gen Namen zu­zu­ord­nen, unter dem die Anweisung im späteren Verlauf an­ge­steu­ert werden kann.

PREPARE stmt_name FROM preparable_stmt

Für die Aus­füh­rung vor­be­rei­te­ter SQL-An­wei­sun­gen benötigen Sie den Befehl „EXECUTE“. Auf das jeweilige Prepared Statement beziehen Sie sich durch die Angabe des mit „PREPARE“ ge­ne­rier­ten Namens. Wie oft Sie ein Statement ausführen, liegt dabei in Ihrer Hand: So können Sie wahlweise beliebige, un­ter­schied­li­che Variablen de­fi­nie­ren oder un­be­grenzt neue Werte für die gesetzten Variablen übergeben.

EXECUTE stmt_name
	[USING @var_name [, @var_name] ...]

Um ein PHP-Prepared-Statement wieder frei­zu­ge­ben, verwendet man die Anweisung „DEAL­LO­CA­TE PREPARE“. Al­ter­na­tiv werden State­ments beim Beenden einer Sitzung au­to­ma­tisch frei­ge­ge­ben. Die Freigabe ist insofern relevant, als dass an­dern­falls schnell die Ober­gren­ze erreicht wird, die durch die Sys­tem­va­ria­ble max_prepared_stmt_count vor­ge­ge­ben ist. Dann könnten Sie keine neuen Prepared State­ments erzeugen.

{DEALLOCATE | DROP} PREPARE stmt_name

Diese SQL-An­wei­sun­gen können Sie als MySQL-Prepared-State­ments verwenden

Sie können nahezu alle von MySQL un­ter­stütz­ten SQL-An­wei­sun­gen als Prepared State­ments auf­be­rei­ten und ausführen. Eine Ausnahme bilden dabei einzig die Diagnose-An­wei­sun­gen, die zur Ein­hal­tung des SQL-Standards als Prepared State­ments aus­ge­schlos­sen sind. Im Detail handelt es sich also um folgende State­ments:

  • SHOW WARNINGS
  • SHOW COUNT(*) WARNINGS
  • SHOW ERRORS
  • SHOW COUNT(*) ERRORS

Zudem können Sie keine Muster für SQL-Abfragen mit Bezug auf die Sys­tem­va­ria­blen warning_count und error_count ge­ne­rie­ren.

Nutzbar sind hingegen folgende State­ments:

ALTER TABLE
ALTER USER
ANALYZE TABLE
CACHE INDEX
CALL
CHANGE MASTER
CHECKSUM {TABLE | TABLES}
COMMIT
{CREATE | DROP} INDEX
{CREATE | RENAME | DROP} DATABASE
{CREATE | DROP} TABLE
{CREATE | RENAME | DROP} USER
{CREATE | DROP} VIEW
DELETE
DO
FLUSH {TABLE | TABLES | TABLES WITH READ LOCK | HOSTS | PRIVILEGES
	| LOGS | STATUS | MASTER | SLAVE | USER_RESOURCES}
GRANT
INSERT
INSTALL PLUGIN
KILL
LOAD INDEX INTO CACHE
OPTIMIZE TABLE
RENAME TABLE
REPAIR TABLE
REPLACE
RESET {MASTER | SLAVE}
REVOKE
SELECT
SET
SHOW {WARNINGS | ERRORS}
SHOW BINLOG EVENTS
SHOW CREATE {PROCEDURE | FUNCTION | EVENT | TABLE | VIEW}
SHOW {MASTER | BINARY} LOGS
SHOW {MASTER | SLAVE} STATUS
SLAVE {START | STOP}
TRUNCATE TABLE
UNINSTALL PLUGIN
UPDATE

Syn­tak­ti­sche Be­son­der­hei­ten der SQL-Syntax von Prepared State­ments in PHP

Im Vergleich zur stan­dard­mä­ßi­gen SQL-Syntax weist die Syntax von Prepared State­ments ein paar Be­son­der­hei­ten auf, die es unbedingt zu beachten gilt. Allen voran steht hierbei die Ver­wen­dung von Platz­hal­tern für Parameter-Werte, die die vor­be­rei­te­ten An­wei­sun­gen so in­ter­es­sant für den Zugriff auf Da­ten­bank­ma­nage­ment­sys­te­me macht. Ab MySQL 8.0 sind solche Platz­hal­ter bei­spiels­wei­se auch für „OUT“- und „INOUT“-Parameter in „PREPARE“- und „EXECUTE“-State­ments möglich – für „IN“-Parameter sind diese sogar un­ab­hän­gig von der Da­ten­bank­sys­tem-Version verfügbar. Weitere spe­zi­fi­sche Ei­gen­schaf­ten der Prepared-Statement-Syntax sind folgende:

  • SQL-Syntax für PHP-Prepared-State­ments kann nicht ver­schach­telt werden. Eine Anweisung, die an ein „PREPARE“-Statement übergeben wurde, kann also nicht gleich­zei­tig selbst ein „PREPARE“-, „EXECUTE“- oder „DEAL­LO­CA­TE PREPARE“-Statement sein.
  • Prepared State­ments lassen sich in ge­spei­cher­ten Pro­ze­du­ren (Funktion zum Aufruf kom­plet­ter Abläufe von An­wei­sun­gen) verwenden.
  • Mehrere An­wei­sun­gen, auch Multi State­ments genannt, sind innerhalb eines vor­be­rei­te­ten State­ments bzw. innerhalb einer Zei­chen­ket­te durch Trennung per Semikolon nicht möglich.

Prepared State­ments in MySQLi: Beispiel

Wie eine Eingabe mit PHP-Prepared-State­ments in MySQLi genau aussieht, zeigt dieses Beispiel:

<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";
// Verbindung erstellen
$conn = new mysqli($servername, $username, $password, $dbname);
// Verbindung überprüfen
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}
// Vorbereiten der Prepared Statements
$stmt = $conn->prepare("INSERT INTO MeineKunden (Vorname, Nachname, Mail) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $vorname, $nachname, $mail);
// Setzen der Parameter und Ausführung
$Vorname = "Max";
$Nachname = "Mustermann";
$Mail = "max@beispiel.de";
$stmt->execute();
$Vorname = "Erika";
$Nachname = "Mustermann";
$Mail = "erika@beispiel.de";
$stmt->execute();
$Vorname = "Erik";
$Nachname = "Mustermann";
$Mail = "erik@beispiel.de";
$stmt->execute();
echo "Neue Einträge erfolgreich angelegt";
$stmt->close();
$conn->close();
?>

Dieses PHP-Skript baut zunächst die Ver­bin­dung zur MySQL-Datenbank auf ($conn), wobei die in­di­vi­du­el­len Ser­ver­da­ten wie Hostname, Be­nut­zer­na­me, Passwort und Da­ten­bank­na­me anzugeben sind.

Mit der Zeile "INSERT INTO Mei­ne­Kun­den (Vorname, Nachname, Mail) VALUES (?, ?, ?)" beginnt der ent­schei­den­de Prepared-Statement-Teil: Die Kun­den­da­ten­bank „Mei­ne­Kun­den“ soll Input erhalten (INSERT INTO) – jeweils für die Spalten „Vorname“, „Nachname“ und „Mail“. Für die Werte (VALUES) werden zunächst Platz­hal­ter verwendet, die durch das Fra­ge­zei­chen-Symbol (?) ge­kenn­zeich­net sind.

Im Anschluss müssen die Parameter gebunden werden (bind_param). Zudem benötigt die Datenbank In­for­ma­tio­nen darüber, um was für einen Typ von Daten es sich handelt. Das hier ver­wen­de­te Argument „sss“ zeigt bei­spiels­wei­se an, dass es sich bei allen drei Pa­ra­me­tern um Strings, also Zei­chen­ket­ten handelt. Mögliche Al­ter­na­ti­ven wären an dieser Stelle:

  • i: INTEGER (ganz­zah­li­ger Wert)
  • d: DOUBLE (annähernd nu­me­ri­scher Datenwert)
  • b: BLOB (großes binäres Da­ten­ob­jekt)
aN5KqxK1slc.jpg Zur Anzeige dieses Videos sind Cookies von Drittanbietern erforderlich. Ihre Cookie-Einstellungen können Sie hier aufrufen und ändern.
Zum Hauptmenü