SQL-Injection vermeiden mit PDO Prepared Statements
AI generated
<?php
8.4
PHP · Sicherheit · SQL · PDO
SQL-Injection vermeiden mit PDO Prepared Statements
von benannten Platzhaltern bis zum sicheren Identifier-Whitelisting

Wer SQL-Queries per String-Konkatenation baut, öffnet eine der ältesten und zugleich gefährlichsten Schwachstellenklassen der Webentwicklung. PDO Prepared Statements trennen Code und Daten strukturell und schließen SQL-Injection unabhängig vom Inhalt der Eingabe aus, vorausgesetzt sie werden korrekt konfiguriert und für Identifier, LIKE-Muster und IN-Listen richtig eingesetzt. Dieser Beitrag zeigt die vollständige technische Umsetzung mit PDO in PHP 8.4.

18 Min. Lesezeit PDO · Prepared Statements · Type-Binding · Identifier-Whitelisting PHP 8.4 · MySQL · PostgreSQL

1. Warum SQL-Injection trotz jahrzehntelanger Lösung noch verbreitet ist

SQL-Injection steht seit über zwei Jahrzehnten in der OWASP Top 10 in der Kategorie Injection, und die Lösung ist technisch seit ebenso langer Zeit bekannt: Prepared Statements trennen Code und Daten strikt voneinander. Trotzdem tauchen in Audits und Penetrationstests regelmäßig Anwendungen auf, die Benutzereingaben direkt in SQL-Query-Strings verketten. Der Grund liegt selten in Unwissenheit über die Existenz von Prepared Statements, sondern in der Bequemlichkeit einer scheinbar einfacheren Zeile Code: Ein String wird per Konkatenation zusammengesetzt, die Query läuft im Test durch, und niemand denkt an die eine Eingabe, die die komplette Syntax verändert. Genau diese Bequemlichkeit macht SQL-Injection zu einer der langlebigsten Schwachstellenklassen überhaupt.

Der reale Schaden einer erfolgreichen SQL-Injection reicht von unautorisiertem Lesezugriff auf komplette Kundendatenbanken über die Manipulation von Bestell- und Zahlungsdaten bis zum vollständigen Löschen von Tabellen. In Legacy-Code-Basen, in schnell zusammengebauten internen Tools und in Skripten ohne Framework-Unterstützung fehlt häufig jede automatische Absicherung, weil kein ORM und keine Query-Builder-Schicht dazwischenliegt, die Konkatenation von vornherein verhindert. Gerade in einem Framework-unabhängigen PHP-Kontext, in dem Entwickler direkt mit PDO arbeiten, liegt die Verantwortung für sichere Queries vollständig beim Entwickler selbst, und ein einziger übersehener String-Aufbau reicht für eine kritische Lücke.

2. Wie SQL-Injection technisch funktioniert

Eine naive Login-Abfrage sieht in unsicherem Code oft so aus: Der Benutzername wird direkt in einen SQL-String eingefügt, etwa als "SELECT * FROM users WHERE username = '" . $username . "'". Solange die Eingabe aus normalen Zeichen besteht, funktioniert diese Query wie erwartet. Gibt ein Angreifer stattdessen den Wert admin' OR '1'='1 ein, wird aus der Query SELECT * FROM users WHERE username = 'admin' OR '1'='1'. Das einfache Anführungszeichen im Eingabewert beendet die ursprünglich vom Entwickler beabsichtigte String-Grenze vorzeitig, und der nachfolgende Ausdruck OR '1'='1' ist für jede Zeile in der Tabelle wahr. Die WHERE-Klausel filtert damit nicht mehr nach einem bestimmten Benutzer, sondern liefert die komplette Tabelle zurück, und je nach Anwendungslogik reicht das bereits für eine Umgehung der Anmeldung.

Über dieses einfache Beispiel hinaus reichen die Angriffstechniken deutlich weiter: Mit UNION SELECT lassen sich Daten aus völlig anderen Tabellen in das Ergebnis einer eigentlich harmlosen Abfrage einschleusen, mit stacked queries bei manchen Treibern sogar zusätzliche Befehle wie DROP TABLE anhängen. Ältere Ansätze wie addslashes versuchten, gefährliche Zeichen im Eingabewert zu maskieren, bevor er in den String eingefügt wird. Dieses Vorgehen ist fragil, weil es vom jeweiligen Zeichensatz und Treiber abhängt und in der Vergangenheit mehrfach durch Encoding-Tricks umgangen wurde. Das eigentliche Problem liegt nicht in fehlendem Escaping, sondern darin, dass Code und Daten im selben String vermischt werden. Genau das löst PDO mit Prepared Statements strukturell, ohne auf Escaping-Heuristiken angewiesen zu sein.

3. PDO Prepared Statements richtig einsetzen

PDO unterstützt zwei Formen von Platzhaltern: benannte Platzhalter wie :username und positionelle Platzhalter in Form von einfachen Fragezeichen. Benannte Platzhalter sind bei Queries mit mehreren Parametern deutlich lesbarer, weil die Zuordnung von Wert zu Platzhalter über einen sprechenden Namen erfolgt statt über die Reihenfolge im Array. Positionelle Platzhalter sind kompakter und eignen sich für kurze Queries mit ein oder zwei Werten, verlangen aber strikte Reihenfolgetreue zwischen Query und dem an execute() übergebenen Array. Ein weiterer praktischer Unterschied: Ein benannter Platzhalter kann in derselben Query mehrfach referenziert werden, ein positioneller Platzhalter mit PDO dagegen nur für ein einziges Vorkommen.

Der entscheidende Unterschied zur String-Konkatenation liegt darin, wie PDO die Query intern behandelt. Bei einem echten Prepared Statement sendet PDO zunächst nur das Query-Template mit den Platzhaltern an den Datenbankserver, der es dort parst und einen Ausführungsplan vorbereitet. Erst danach werden die eigentlichen Werte separat als Daten übertragen und in die vorbereiteten Platzhalter eingesetzt. Die Werte durchlaufen zu keinem Zeitpunkt den SQL-Parser, sie können also die Struktur der Query nicht verändern, unabhängig davon, welche Zeichen sie enthalten. Diese strukturelle Trennung von Code und Daten löst das Problem an der Wurzel, nicht durch Filtern gefährlicher Zeichen, sondern dadurch, dass Werte gar nicht erst als SQL-Syntax interpretiert werden können. Das folgende Beispiel zeigt die verwundbare Variante direkt neben der korrekten Umsetzung mit einem benannten Platzhalter.


<?php

declare(strict_types=1);

// WRONG: user input concatenated directly into the SQL string
$username = $_POST['username'] ?? '';
$sql = "SELECT id, email FROM users WHERE username = '" . $username . "'";
$statement = $pdo->query($sql); // vulnerable to admin' OR '1'='1

// RIGHT: named placeholder, value is never parsed as SQL
$sql = 'SELECT id, email FROM users WHERE username = :username';
$statement = $pdo->prepare($sql);
$statement->execute(['username' => $username]);
$user = $statement->fetch();

4. Emulierte Prepares abschalten: PDO::ATTR_EMULATE_PREPARES

PDO bietet zwei grundlegend unterschiedliche Implementierungen für Prepared Statements. Bei nativen Prepares wird die Query, wie im vorherigen Abschnitt beschrieben, tatsächlich an den Datenbankserver geschickt und dort vorbereitet. Bei emulierten Prepares baut PDO selbst clientseitig die finale Query, indem es die gebundenen Werte lokal maskiert und in das Query-Template einsetzt, bevor überhaupt etwas an den Server geht. Für den PDO-MySQL-Treiber war diese Emulation historisch die Standardeinstellung, unter anderem aus Performance-Gründen bei häufig wiederholten Queries und wegen fehlender nativer Unterstützung für bestimmte Platzhalter-Konstruktionen in älteren MySQL-Versionen.

Das Problem an emulierten Prepares: Die Trennung von Code und Daten findet dabei nicht mehr auf Protokollebene im Datenbankserver statt, sondern hängt vollständig von der clientseitigen Maskierungslogik in PDO ab. In der Vergangenheit gab es dokumentierte Fälle, in denen fehlerhafte Zeichensatz-Konfigurationen, etwa bei bestimmten Multi-Byte-Encodings, die clientseitige Maskierung aushebeln konnten. Aus diesem Grund sollte PDO::ATTR_EMULATE_PREPARES beim Verbindungsaufbau explizit auf false gesetzt werden, damit tatsächlich native Prepared Statements auf Serverseite verwendet werden. Ergänzend gehört PDO::ATTR_ERRMODE auf PDO::ERRMODE_EXCEPTION, damit Datenbankfehler als Exceptions geworfen werden, statt lautlos einen falschen Rückgabewert zu liefern, den ein Entwickler leicht übersieht.


<?php

declare(strict_types=1);

$dsn = 'mysql:host=127.0.0.1;dbname=shop;charset=utf8mb4';

$options = [
    // Use real, server-side prepared statements instead of client-side emulation
    PDO::ATTR_EMULATE_PREPARES => false,
    // Fail loudly instead of silently returning false on error
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    // Return associative arrays by default
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];

try {
    $pdo = new PDO($dsn, 'app_user', 'secret', $options);
} catch (PDOException $exception) {
    throw new RuntimeException('Database connection failed', previous: $exception);
}

5. bindValue vs. bindParam und Datentypen

bindValue und bindParam wirken auf den ersten Blick austauschbar, unterscheiden sich aber grundlegend in der Bindungsart. bindValue bindet den übergebenen Wert sofort zum Zeitpunkt des Aufrufs an den Platzhalter, per Wert. Wird die zugrunde liegende PHP-Variable danach verändert, hat das keinen Einfluss mehr auf den bereits gebundenen Wert. bindParam dagegen bindet die Variable per Referenz, der tatsächliche Wert wird erst beim Aufruf von execute() ausgelesen. Das ist nützlich in Schleifen, in denen sich eine Variable zwischen mehreren execute()-Aufrufen ändert, kann aber zu subtilen Fehlern führen, wenn die Variable zwischen Bindung und Ausführung unerwartet einen anderen Wert annimmt.

Für beide Methoden lässt sich der Datentyp explizit über die PDO::PARAM_*-Konstanten angeben, etwa PDO::PARAM_INT, PDO::PARAM_STR oder PDO::PARAM_BOOL. Wird kein Typ angegeben, behandelt PDO den Wert standardmäßig als String, was bei Vergleichen mit numerischen Spalten in manchen Treibern zu unerwartetem Type Juggling führen kann. Besonders bei LIMIT- und OFFSET-Werten, die in einigen Datenbanktreibern als String gebunden zu Syntaxfehlern führen, ist die explizite Angabe von PDO::PARAM_INT relevant. Auch bei Werten, die aus externen Quellen wie HTTP-Parametern oder JSON-Payloads kommen und daher zunächst immer als String vorliegen, schafft eine bewusste Typumwandlung vor dem Binden zusätzliche Klarheit über die erwartete Semantik der Query.


<?php

declare(strict_types=1);

// bindValue: value is bound immediately, by value
$statement = $pdo->prepare(
    'SELECT * FROM orders WHERE customer_id = :id AND is_active = :active'
);
$statement->bindValue(':id', $customerId, PDO::PARAM_INT);
$statement->bindValue(':active', true, PDO::PARAM_BOOL);
$statement->execute();

// bindParam: value is bound by reference, read only at execute() time
$statement = $pdo->prepare('INSERT INTO log_entries (message) VALUES (:message)');
$statement->bindParam(':message', $message, PDO::PARAM_STR);

foreach ($messages as $message) {
    $statement->execute(); // reads the current value of $message on every call
}

6. Dynamische Identifier: Tabellen- und Spaltennamen sicher whitelisten

Ein wichtiger Punkt, den viele Entwickler bei Prepared Statements übersehen: Platzhalter funktionieren ausschließlich für Werte, niemals für Identifier wie Tabellen- oder Spaltennamen. Das liegt am Protokoll selbst, ein gebundener Platzhalter wird immer als Daten-Literal behandelt, niemals als Teil der SQL-Grammatik. Ein Versuch, einen Tabellennamen über einen Platzhalter zu binden, etwa für eine dynamische Sortierspalte, führt entweder zu einem Syntaxfehler oder dazu, dass der Name fälschlich als String-Literal interpretiert wird, was die Query funktional unbrauchbar macht. Für dynamische Sortierungen, Tabellennamen in Multi-Tenant-Systemen oder konfigurierbare Report-Spalten braucht es deshalb einen anderen Ansatz als Prepared Statements.

Die einzig sichere Lösung ist eine feste Whitelist erlaubter Werte in Form eines Arrays im Code, gegen das die Benutzereingabe geprüft wird, bevor der Identifier in die Query eingesetzt wird. Kommt der übergebene Wert in der Whitelist nicht vor, wird eine Exception geworfen und die Query gar nicht erst gebaut. Unter keinen Umständen darf die rohe Benutzereingabe direkt in einen Identifier interpoliert werden, auch nicht nach vermeintlicher Bereinigung durch reguläre Ausdrücke, denn solche Filter sind erfahrungsgemäß unvollständig und werden mit der Zeit umgangen. Die Whitelist-Prüfung findet vollständig in PHP statt, die Datenbank sieht am Ende nur noch einen fest im Code stehenden, geprüften Identifier.


<?php

declare(strict_types=1);

final class OrderSorter
{
    /** @var string[] */
    private const array ALLOWED_COLUMNS = ['created_at', 'total', 'status'];

    public function __construct(private readonly PDO $pdo)
    {
    }

    /**
     * @param string $column Column name requested by the caller, must be whitelisted.
     * @param string $direction Sort direction, ASC or DESC.
     * @return array<int, array<string, mixed>>
     */
    public function fetchSorted(string $column, string $direction): array
    {
        if (!in_array($column, self::ALLOWED_COLUMNS, true)) {
            throw new InvalidArgumentException('Column is not allowed for sorting');
        }

        $direction = strtoupper($direction) === 'DESC' ? 'DESC' : 'ASC';

        // $column comes only from the fixed whitelist above, never from raw input
        $sql = sprintf('SELECT * FROM orders ORDER BY %s %s', $column, $direction);
        $statement = $this->pdo->query($sql);

        return $statement->fetchAll();
    }
}

7. LIKE-Queries und IN-Klauseln sicher parametrisieren

LIKE-Abfragen bringen eine Besonderheit mit sich, die über reine SQL-Injection-Prävention hinausgeht. Die Zeichen % und _ haben innerhalb eines LIKE-Musters eine spezielle Bedeutung als Platzhalter für beliebig viele beziehungsweise genau ein Zeichen. Wird ein Suchbegriff, der selbst ein Prozentzeichen oder einen Unterstrich enthält, unverändert in ein LIKE-Muster eingesetzt, etwa als '%' . $suchbegriff . '%', ändert sich die Bedeutung der Suche, selbst wenn der Wert korrekt über einen Prepared-Statement-Platzhalter gebunden wird. Das ist keine SQL-Injection im klassischen Sinn, aber ein funktionaler Fehler, der zu falschen Treffern oder unerwartet breiten Ergebnismengen führt. Die Lösung besteht darin, die Zeichen % und _ im Suchbegriff selbst vor dem Einsetzen ins Muster zu escapen und in der Query eine passende ESCAPE-Klausel anzugeben.

Für IN-Klauseln gilt eine andere Einschränkung: Ein einzelner Platzhalter kann immer nur einen einzelnen Wert binden, niemals eine variable Liste von Werten für ein IN(...)-Konstrukt. Der verbreitete, aber unsichere Ausweg ist, die Werte direkt als kommagetrennten String in die Query zu konkatenieren. Der sichere Weg besteht darin, für jeden Wert im Array zur Laufzeit einen eigenen benannten Platzhalter zu erzeugen, etwa :id0, :id1, :id2, diese Platzhalter durch Kommas getrennt in die Query einzusetzen und anschließend jeden einzelnen Wert regulär über bindValue zu binden. So bleibt die Anzahl der Platzhalter exakt an die Anzahl der tatsächlichen Werte gekoppelt, ohne dass irgendein Wert die Query-Struktur beeinflussen kann.


<?php

declare(strict_types=1);

/**
 * @param int[] $ids Product IDs to fetch.
 * @return array<int, array<string, mixed>>
 */
function fetchProductsByIds(PDO $pdo, array $ids): array
{
    if ($ids === []) {
        return [];
    }

    $placeholders = [];
    $params = [];

    foreach (array_values($ids) as $index => $id) {
        $key = ':id' . $index;
        $placeholders[] = $key;
        $params[$key] = $id;
    }

    $sql = sprintf(
        'SELECT * FROM products WHERE id IN (%s)',
        implode(', ', $placeholders)
    );

    $statement = $pdo->prepare($sql);
    foreach ($params as $key => $value) {
        $statement->bindValue($key, $value, PDO::PARAM_INT);
    }
    $statement->execute();

    return $statement->fetchAll();
}

8. Second-Order SQL-Injection und gespeicherte Daten

Second-Order SQL-Injection beschreibt ein Szenario, das in Audits regelmäßig übersehen wird: Ein Wert wird beim ersten Schreibvorgang korrekt über ein Prepared Statement gespeichert, etwa ein Firmenname bei der Registrierung, der ein einfaches Anführungszeichen enthält. Die erste Query ist vollständig sicher, weil der Wert nie als SQL-Syntax interpretiert wurde. Problematisch wird es, wenn genau dieser bereits gespeicherte Wert später, etwa bei der Erstellung eines Reports oder eines Exports, aus der Datenbank gelesen und in einer zweiten Query per String-Konkatenation wiederverwendet wird, statt erneut über einen Platzhalter gebunden zu werden. An dieser zweiten Stelle entsteht dieselbe Schwachstelle wie bei direkter Benutzereingabe, nur zeitlich verzögert und über einen Umweg durch die Datenbank.

Die verbreitete Fehleinschätzung dabei ist die Annahme, ein Wert sei automatisch sicher, weil er ja bereits in der Datenbank steht und offenbar schon einmal erfolgreich verarbeitet wurde. Diese Annahme ignoriert, dass die Vertrauensgrenze nicht am Ursprung der Daten liegt, sondern an jeder einzelnen Stelle, an der eine SQL-Query neu zusammengesetzt wird. Jede Query-Konstruktion muss unabhängig von der Herkunft des Werts konsequent Prepared Statements verwenden, egal ob der Wert direkt aus einem Formularfeld stammt oder Monate zuvor über eine andere, ebenfalls sichere Query in die Datenbank gelangt ist. Konsistenz über die gesamte Codebasis hinweg ist hier wichtiger als die Absicherung einzelner, isoliert betrachteter Eingabepunkte.

9. Unsichere vs. sichere Query-Patterns im Vergleich

Die folgende Übersicht fasst die in diesem Beitrag behandelten Szenarien zusammen und stellt das jeweils unsichere Muster dem sicheren PDO-Pattern direkt gegenüber. Sie eignet sich als schnelle Referenz für Code-Reviews und für die Suche nach vergleichbaren Stellen in bestehendem Code.

Szenario Unsicher Sicheres PDO-Pattern Vorteil
String-Konkatenation "...WHERE name='" . $name . "'" :name mit bindValue Werte durchlaufen nie den SQL-Parser
LIKE-Wildcard-Injection '%' . $suchbegriff . '%' ungeschützt % und _ escapen, ESCAPE-Klausel Korrekte Treffermenge statt Breitensuche
IN-Klausel mit Werten IN (" . implode(',', $ids) . ")" dynamisch erzeugte :id0, :id1, ... Werteanzahl exakt an Platzhalter gekoppelt
Dynamischer Tabellenname "SELECT * FROM " . $_GET['table'] Whitelist-Array erlaubter Namen Identifier niemals aus Rohdaten interpoliert
Fehlendes Type-Binding bindValue ohne Typ (Standard String) bindValue(..., PDO::PARAM_INT) Kein Type Juggling, korrekte Treiber-Semantik

Auffällig ist, dass in jeder Zeile derselbe Grundsatz greift: Werte gehören ausschließlich in gebundene Platzhalter, niemals in den Query-String selbst. Identifier bilden dabei die einzige Ausnahme, für die es überhaupt keinen Platzhalter-Mechanismus gibt und stattdessen zwingend eine Whitelist notwendig ist. Wer diese Tabelle als Checkliste in Code-Reviews verwendet, deckt die meisten SQL-Injection-relevanten Muster zuverlässig ab, bevor sie in Produktion gelangen.

10. Zusammenfassung

Die wichtigste Erkenntnis zur Vermeidung von SQL-Injection ist strukturell, nicht kosmetisch: PDO Prepared Statements trennen Code und Daten auf Protokollebene, sodass Werte niemals als SQL-Syntax interpretiert werden können, unabhängig davon, welche Zeichen sie enthalten. Diese Trennung funktioniert allerdings nur, wenn PDO::ATTR_EMULATE_PREPARES auf false steht, wenn Werte konsequent über bindValue oder bindParam mit korrektem Typ gebunden werden und wenn Identifier wie Tabellen- und Spaltennamen über eine feste Whitelist abgesichert sind, weil Platzhalter für sie schlicht nicht funktionieren.

Besondere Aufmerksamkeit verdienen die Randfälle, die in vielen Code-Reviews übersehen werden: LIKE-Muster mit unescapten Wildcard-Zeichen, dynamisch erzeugte IN-Klauseln und Second-Order-Szenarien, in denen bereits gespeicherte Daten später unsicher in einer zweiten Query verarbeitet werden. Wer alle neun in diesem Beitrag behandelten Muster konsequent in der eigenen Codebasis prüft, schließt die weit überwiegende Mehrheit aller praktisch relevanten SQL-Injection-Einfallstore, ohne die Lesbarkeit oder Wartbarkeit des Codes zu beeinträchtigen.

SQL-Injection vermeiden mit PDO Prepared Statements, Das Wichtigste auf einen Blick

Prepared Statements

Code und Daten strukturell trennen: benannte oder positionelle Platzhalter statt String-Konkatenation, für jede Query ohne Ausnahme.

Emulierte Prepares abschalten

PDO::ATTR_EMULATE_PREPARES => false und PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION bei jeder Verbindung setzen.

Type-Binding

bindValue mit expliziten PDO::PARAM_*-Konstanten statt impliziter String-Bindung, besonders bei Integer-Werten.

Identifier-Whitelisting

Tabellen- und Spaltennamen niemals aus Rohdaten interpolieren, immer gegen ein festes Whitelist-Array prüfen.

11. FAQ: SQL-Injection und PDO Prepared Statements

1Was ist SQL-Injection genau?
Eine Schwachstelle, bei der Benutzereingaben Teil der SQL-Syntax werden, weil Code und Daten im selben String vermischt sind. Ein Angreifer kann so die Query-Logik verändern.
2Reichen Prepared Statements allein aus?
Für Werte vollständig. Für Identifier wie Tabellen- oder Spaltennamen nicht, dort ist zwingend eine feste Whitelist notwendig.
3bindValue vs. bindParam?
bindValue bindet sofort per Wert. bindParam bindet per Referenz, der Wert wird erst bei execute() ausgelesen, nützlich in Schleifen mit wechselnden Werten.
4Warum ATTR_EMULATE_PREPARES auf false?
Damit echte, serverseitige Prepared Statements genutzt werden statt clientseitiger Maskierung, die von Zeichensatz und Treiber abhängt.
5Tabellenname als Platzhalter binden?
Nicht möglich. Platzhalter sind immer Daten-Literale. Identifier müssen gegen eine feste Whitelist geprüft und direkt eingesetzt werden.
6% und _ in LIKE sicher escapen?
Die Zeichen im Suchbegriff vor dem Einsetzen ins Muster escapen und in der Query eine passende ESCAPE-Klausel angeben. Der Wert bleibt trotzdem ein gebundener Platzhalter.
7IN-Klausel mit variabler Anzahl?
Für jeden Wert einen eigenen Platzhalter erzeugen, etwa :id0, :id1, in die Query einsetzen und jeden Wert einzeln über bindValue binden.
8Was ist Second-Order SQL-Injection?
Ein Wert wird sicher gespeichert, aber später aus der Datenbank gelesen und unsicher per Konkatenation in einer zweiten Query verwendet. Herkunft macht Daten nicht automatisch sicher.
9Schützt mich ein ORM automatisch?
Standard-Queries eines ORM nutzen meist Prepared Statements. Bei Raw-Query-Methoden mit konkatenierten Werten gilt derselbe Sorgfaltsmaßstab wie bei direktem PDO-Einsatz.
10Welche PDO-Attribute mindestens setzen?
ATTR_EMULATE_PREPARES auf false, ATTR_ERRMODE auf ERRMODE_EXCEPTION und ein sinnvoller Zeichensatz wie utf8mb4 im DSN.

Mironsoft

PHP-Sicherheitsaudits, Datenbank-Hardening und Penetrationstest-Nachbereitung

Sind eure Datenbank-Queries wirklich gegen SQL-Injection abgesichert?

Wir prüfen bestehenden PHP-Code auf unsichere Query-Patterns, härten die PDO-Datenbankschicht mit korrekter Prepared-Statement-Konfiguration und schließen Lücken, die in Penetrationstests typischerweise auffallen, von String-Konkatenation bis zu fehlendem Identifier-Whitelisting.

Query-Security-Review

Systematische Prüfung aller Datenbankzugriffe auf String-Konkatenation, fehlendes Type-Binding und ungeschützte LIKE- und IN-Queries

PDO-Hardening

Korrekte PDO-Konfiguration mit deaktivierten emulierten Prepares, striktem Error-Mode und sauberer Trennung von Code und Daten

Pentest-Nachbereitung

Behebung von SQL-Injection-Findings aus Penetrationstests inklusive Regressionstests für die betroffenen Queries