Zum Inhalt springen
Planen wir gemeinsam die passende Software für Ihre Prozesse. Demo oder Angebot: +90 546 737 48 29

TR EN DE

SQL-Injection: Was ist das und wie verhindern Sie sie?

Eine SQL-Injection entsteht, wenn ein vom Benutzer stammender Wert per Zeichenkettenverkettung in eine Datenbankabfrage eingefügt wird. Die Anwendung schickt das, was der Benutzer eingegeben hat, nicht als Daten, sondern als Teil der Abfrage an die Datenbank; dadurch kann die Eingabe die Bedeutung der Abfrage verändern. Die Folgen können unbefugtes Lesen von Daten, das Ändern oder Löschen von Datensätzen oder das Umgehen der Anmeldeprüfung sein.

Die gute Nachricht: SQL-Injection gehört zu den Schwachstellen, deren Ursache und Lösung am besten bekannt sind. Abfragen als Prepared Statements zu schreiben, löst das Problem an der Wurzel. Diese Anleitung erklärt das Konzept und die richtige Umsetzung mit PHP und PDO.

Kurz gefasst

  • Die Schwachstelle entsteht, wenn Benutzereingaben in den Abfragetext eingefügt werden.
  • Die Lösung sind Prepared Statements: Daten und Befehl werden getrennt übermittelt.
  • Teile, die sich nicht binden lassen, etwa Spaltenname und Sortierung, werden aus einer Positivliste gewählt.
  • Ein Datenbankbenutzer mit geringsten Rechten und verborgene Fehlermeldungen begrenzen den Schaden.
Auf dieser Seite

Wo beginnt das Problem, wo endet es?

  1. Daten und Befehl vermischen sich

    Ein Suchfeld, ein Anmeldeformular, ein id-Parameter in der Adresszeile oder der Wert eines Cookies: All das steht unter der Kontrolle des Benutzers. Setzt die Anwendung einen solchen Wert in Anführungszeichen und fügt ihn in den Abfragetext ein, kann die Datenbank nicht mehr unterscheiden, welcher Teil der vom Entwickler geschriebene Befehl ist und welcher Teil die Daten des Benutzers sind. Das ist der Kern der Schwachstelle: Daten und Befehl werden im selben Text zusammengeführt.

    Schaubild: Wird die Benutzereingabe in der Anwendung mit dem Abfragetext verkettet, kann die Datenbank Daten und Befehl nicht unterscheiden : Vergrößern
  2. Das Prepared Statement trennt Daten vom Befehl

    Bei einem Prepared Statement werden die Struktur der Abfrage und die Daten getrennt an die Datenbank gesendet. Im Abfragetext stehen anstelle der Werte Platzhalter (? oder :name); die Werte werden nachträglich gebunden und unter keinen Umständen als Befehl interpretiert. Was auch immer der Benutzer eingibt, es bleibt ein Wert, nach dem gesucht oder der gespeichert wird.

    PHP
    <?php
    // FALSCH: Die Benutzereingabe wird in den Abfragetext verkettet
    // $sql = "SELECT id, ad FROM urunler WHERE kategori = '" . $_GET['kategori'] . "'";
    
    // RICHTIG: Platzhalter + Bindung
    $sorgu = $pdo->prepare('SELECT id, ad FROM urunler WHERE kategori = ? AND durum = ?');
    $sorgu->execute([$_GET['kategori'] ?? '', 1]);
    $urunler = $sorgu->fetchAll();
    Vergleich: Abfragen per Zeichenkettenverkettung sind falsch, Prepared Statements mit Platzhaltern richtig : Vergrößern
  3. Wo sich nichts binden lässt, hilft eine Positivliste

    Platzhalter lassen sich nur für Werte verwenden. Strukturelle Teile der Abfrage wie Tabellenname, Spaltenname oder Sortierrichtung (ASC/DESC) können nicht gebunden werden. Hängen diese Teile von der Auswahl des Benutzers ab, wird die Eingabe nicht direkt in die Abfrage übernommen, sondern einer vorab definierten Positivliste (Allowlist) zugeordnet.

    Schaubild: Für Sortierung und Spaltennamen wird die Benutzereingabe den festen Werten einer Positivliste zugeordnet : Vergrößern
  4. Zweite Schichten, die den Schaden begrenzen

    Das Prepared Statement ist die eigentliche Lösung; trotzdem kann irgendwo ein Fehler passiert sein. Dem Datenbankbenutzer der Anwendung nur die Rechte zu geben, die er tatsächlich braucht, den Besuchern keine Fehlerdetails zu zeigen und Eingaben nach ihrem Typ zu validieren, verringert die Auswirkungen einer möglichen Schwachstelle.

    Schaubild: Verteidigungsschichten; Prepared Statement, Positivliste, Eingabevalidierung, geringste Rechte, verborgene Fehlermeldungen : Vergrößern

Anzeichen: Woran erkennt man das Problem?

  • Im Code-Review: Sehen Sie im Abfragetext eine Verkettung mit Variablen ("... WHERE id = " . $id oder eine $variable innerhalb doppelter Anführungszeichen), ist diese Stelle verdächtig. Verfolgen Sie jeden Weg, auf dem Werte aus $_GET, $_POST, $_COOKIE und $_SERVER in eine Abfrage gelangen.
  • Auf der Website: Zeigt die Seite einen Datenbankfehler, sobald in ein Feld ein Sonderzeichen wie ein einfaches Anführungszeichen eingegeben wird, ist das ein Hinweis darauf, dass diese Eingabe ohne Escaping in die Abfrage gelangt. Probieren Sie das ausschließlich auf Ihrer eigenen Website und in einer Testumgebung aus.
  • In den Protokollen: Zahlreiche Anfragen mit SQL-Schlüsselwörtern in den Parametern im Zugriffsprotokoll, aufeinanderfolgende Syntaxfehler im Fehlerprotokoll, ungewöhnlich langsame Abfragen in der Datenbank.
  • In den Daten: Administratorkonten, die Sie nicht angelegt haben, veränderte Inhalte, in Seiten eingefügte fremde Links.

Die richtige Verbindung mit PDO

Beim Aufbau der Verbindung sind drei Einstellungen wichtig: Fehler werden als Ausnahmen (Exceptions) ausgelöst, es werden echte Prepared Statements verwendet (die Emulation wird abgeschaltet), und der Zeichensatz wird in der Verbindungszeichenfolge angegeben.

PHP
<?php
$pdo = new PDO(
    'mysql:host=localhost;dbname=ornek_db;charset=utf8mb4',
    $dbKullanici,
    $dbParola,
    [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES   => false,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]
);

In Projekten, die mysqli verwenden, wird dasselbe Prinzip mit prepare und bind_param umgesetzt. Mischen Sie in einem Projekt nicht beide Schichten, sondern verwenden Sie die vorhandene konsequent.

Häufige Sonderfälle

Sortierung und Spaltenname

Übernehmen Sie das vom Benutzer gewählte Sortierfeld nicht direkt in die Abfrage; ordnen Sie es einem festen Wert aus der Positivliste zu.

PHP
<?php
// Auswahl des Benutzers -> fester Spaltenname, der in der Abfrage verwendet wird
$siralamaAlanlari = [
    'ad'    => 'ad',
    'fiyat' => 'fiyat',
    'tarih' => 'eklenme_tarihi',
];
$secim = $_GET['sirala'] ?? 'ad';
$kolon = $siralamaAlanlari[$secim] ?? 'ad';                    // nicht in der Liste: Standardwert
$yon   = (($_GET['yon'] ?? '') === 'azalan') ? 'DESC' : 'ASC'; // nur zwei feste Werte

$sayfa = max(1, (int) ($_GET['sayfa'] ?? 1));
$adet  = 20;
$bas   = ($sayfa - 1) * $adet;

$sorgu = $pdo->prepare("SELECT id, ad, fiyat FROM urunler WHERE durum = ? ORDER BY $kolon $yon LIMIT ?, ?");
$sorgu->bindValue(1, 1, PDO::PARAM_INT);
$sorgu->bindValue(2, $bas, PDO::PARAM_INT);
$sorgu->bindValue(3, $adet, PDO::PARAM_INT);
$sorgu->execute();

Suche mit LIKE

Auch der Suchbegriff wird über einen Platzhalter gebunden. Da die Zeichen % und _ in LIKE als Jokerzeichen gelten, verhindert das Escapen dieser vom Benutzer eingegebenen Zeichen unerwartet breite Treffer.

PHP
<?php
$terim = trim((string) ($_GET['q'] ?? ''));
// LIKE-Jokerzeichen (% und _) und das Escape-Zeichen entschärfen
$terim = addcslashes($terim, '%_\\');

$sorgu = $pdo->prepare('SELECT id, baslik FROM yazilar WHERE baslik LIKE ? LIMIT 50');
$sorgu->execute(['%' . $terim . '%']);

IN-Liste

Erzeugen Sie für eine variable Anzahl von Werten genau so viele Platzhalter, wie es Elemente gibt; die Werte übergeben Sie auch hier gebunden.

PHP
<?php
$idler = array_map('intval', (array) ($_POST['idler'] ?? []));
$idler = array_values(array_filter($idler, fn ($id) => $id > 0));

if ($idler !== []) {
    $yerTutucular = implode(',', array_fill(0, count($idler), '?'));
    $sorgu = $pdo->prepare("SELECT id, ad FROM urunler WHERE id IN ($yerTutucular)");
    $sorgu->execute($idler);
    $urunler = $sorgu->fetchAll();
}

LIMIT und Zahlenwerte

Wandeln Sie numerische Eingaben wie die Seitennummer in eine Ganzzahl um und prüfen Sie den Wertebereich. Die Umwandlung in eine Ganzzahl ist eine gute Validierung, ersetzt das Prepared Statement aber nicht; verwenden Sie beides zusammen.

Wenn Sie ein ORM oder einen Query Builder einsetzen

Die ORMs und Query Builder von Laravel, Symfony und ähnlichen Frameworks binden Werte bei normaler Verwendung von selbst. Das Risiko kehrt dort zurück, wo „rohe Abfragen“ geschrieben werden: Wird Benutzereingabe in whereRaw, orderByRaw, DB::raw und ähnliche Methoden hineinverkettet, entsteht dieselbe Schwachstelle. Wenn eine rohe Abfrage nötig ist, nutzen Sie die Bindungsparameter (Bindings) dieser Methoden; für Sortierfelder gilt auch hier die Positivliste.

Datenbankbenutzer mit geringsten Rechten

Eine Webanwendung darf sich nicht als root oder mit einem Benutzer, der sämtliche Rechte besitzt, mit der Datenbank verbinden. Legen Sie einen eigenen Benutzer für die Anwendung an und erlauben Sie ihm ausschließlich in seiner eigenen Datenbank ausschließlich die benötigten Operationen. Für Arbeiten, die eine Schemaänderung erfordern (Tabellen anlegen oder löschen), verwenden Sie einen separaten Benutzer.

SQL
-- Eigener Benutzer für die Anwendung: nur in der eigenen Datenbank, nur Datenoperationen
CREATE USER 'ornek_uyg'@'localhost' IDENTIFIED BY 'ein-langes-und-einzigartiges-passwort';
GRANT SELECT, INSERT, UPDATE, DELETE ON ornek_db.* TO 'ornek_uyg'@'localhost';

-- Nicht vergeben: DROP, ALTER, CREATE, FILE, GRANT OPTION und andere Datenbanken

Beim Shared Hosting werden Benutzer und Rechte in der Regel über das Control Panel festgelegt; das Prinzip bleibt gleich: für jede Website eine eigene Datenbank und ein eigener Benutzer. Der Datenbankserver darf aus externen Netzen nicht erreichbar sein.

Fehlermeldungen verbergen und protokollieren

Fehlermeldungen der Datenbank verraten Tabellen- und Spaltennamen, die Struktur der Abfrage und Dateipfade. Im Live-Betrieb dürfen Fehler den Besuchern nicht angezeigt werden; sie werden ins Protokoll geschrieben, und der Benutzer erhält eine allgemeine Meldung.

PHP
<?php
try {
    $sorgu = $pdo->prepare('SELECT id, ad FROM musteriler WHERE eposta = ?');
    $sorgu->execute([$eposta]);
    $musteri = $sorgu->fetch();
} catch (PDOException $hata) {
    // Details ins Protokoll; allgemeine Meldung für die Besucher
    error_log('Datenbankfehler: ' . $hata->getMessage());
    http_response_code(500);
    echo 'Der Vorgang kann derzeit nicht ausgeführt werden. Bitte versuchen Sie es später erneut.';
    return;
}

Auch in den PHP-Einstellungen müssen im Live-Betrieb display_errors = Off und log_errors = On gesetzt sein.

Methoden, die nicht ausreichen

  • Negativliste (Blocklist): Bestimmte Wörter oder Zeichen aus der Eingabe zu entfernen, ist nicht verlässlich; es lässt sich umgehen und beschädigt legitime Eingaben.
  • addslashes und manuelles Escapen von Anführungszeichen: Wegen unterschiedlicher Zeichensätze und Kontexte ist das nicht sicher.
  • Ausschließlich clientseitige Validierung: JavaScript-Prüfungen im Browser dienen dem Bedienkomfort; die Anfrage kann direkt an den Server geschickt werden.
  • Ausschließlich eine Firewall (WAF): Sie ist eine nützliche zusätzliche Schicht, beseitigt die Schwachstelle im Code aber nicht.
  • Zeichenkettenverkettung innerhalb einer gespeicherten Prozedur: Eine gespeicherte Prozedur (Stored Procedure) zu verwenden, schützt für sich allein nicht; wird in der Prozedur dynamisches SQL zusammengesetzt, bleibt die Schwachstelle bestehen.

Checkliste

  • Im Projekt gibt es keine Stelle mehr, an der Variablen in den Abfragetext verkettet werden.
  • In der PDO-Verbindung ist der Exception-Modus aktiv, die Emulation abgeschaltet und der Zeichensatz utf8mb4.
  • Sortierung, Spalten- und Tabellennamen stammen aus der Positivliste.
  • Numerische Eingaben werden in Ganzzahlen umgewandelt, und ihr Wertebereich wird geprüft.
  • Die Methoden für rohe Abfragen im Framework wurden durchgesehen.
  • Der Datenbankbenutzer der Anwendung besitzt nur die benötigten Rechte; jede Website verwendet einen eigenen Benutzer.
  • Im Live-Betrieb werden Fehlerdetails nicht auf dem Bildschirm ausgegeben, sondern protokolliert.
  • Die Datenbank wird regelmäßig gesichert.

Häufige Fragen

Muss ich noch etwas tun, wenn ich Prepared Statements verwende?

Für Werte ist das Prepared Statement der eigentliche Schutz. Für Teile, die sich nicht binden lassen, etwa Sortierung und Spaltenname, brauchen Sie eine Positivliste, und als zusätzliche Schichten einen Datenbankbenutzer mit geringsten Rechten sowie verborgene Fehlermeldungen.

Verhindert es eine SQL-Injection, die Eingabe mit htmlspecialchars zu bereinigen?

Nein. htmlspecialchars wird bei der Ausgabe auf der Seite gegen XSS eingesetzt; für die Sicherheit der Abfrage ist ein Prepared Statement erforderlich. Jede Maßnahme wird in ihrem eigenen Kontext angewendet.

In einem alten Projekt gibt es Hunderte von Abfragen; wo fange ich an?

Zuerst bei den Seiten, die ohne Anmeldung erreichbar sind, und bei Formularen wie Anmeldung, Suche und Kontakt; danach beim Administrationsbereich. Suchen Sie im Code nach Stellen, an denen Variablen in den Abfragetext verkettet werden, erstellen Sie daraus eine Liste und stellen Sie jede Stelle auf ein Prepared Statement um.

Ich verwende eine NoSQL-Datenbank; betrifft mich dieses Risiko?

Injection ist nicht auf SQL beschränkt. In jedem System, in dem Benutzereingaben in die Struktur der Abfrage gelangen, besteht ein ähnliches Risiko; nutzen Sie die parametrisierten Abfragen Ihrer Datenbank und validieren Sie die Typen der Eingaben.

BYK Yazılım Support-Team
Diese Anleitung wird vom Support-Team von BYK Yazılım erstellt und regelmäßig überprüft. Letzte Aktualisierung: 04.10.2026.

Verwandte Anleitungen

Sehen wir uns Ihre Website gemeinsam an

BYK Yazılım entwickelt Unternehmenswebsites. Schreiben Sie uns, wenn Sie Fragen zu Ihrer Website haben.

Kontakt aufnehmen Unsere Leistung: Unternehmenswebsite