Angenommen, ich habe folgenden Code:
$dbh = new PDO("blahblah");
$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );
In der PDO-Dokumentation heißt es:
Die Parameter für vorbereitete Anweisungen müssen nicht in Anführungszeichen gesetzt werden. Der Fahrer erledigt das für Sie.
Ist das wirklich alles, was ich tun muss, um SQL-Injektionen zu vermeiden? Ist es wirklich so einfach?
Sie können MySQL annehmen, wenn es einen Unterschied macht. Außerdem bin ich wirklich nur neugierig auf die Verwendung vorbereiteter Anweisungen gegen SQL-Injection. In diesem Zusammenhang interessieren mich XSS oder andere mögliche Schwachstellen nicht.
php
security
pdo
sql-injection
Mark Biek
quelle
quelle
Antworten:
Die kurze Antwort lautet NEIN . PDO-Vorbereitungen schützen Sie nicht vor allen möglichen SQL-Injection-Angriffen. Für bestimmte dunkle Randfälle.
Ich passe diese Antwort an, um über PDO zu sprechen ...
Die lange Antwort ist nicht so einfach. Es basiert auf einem hier demonstrierten Angriff .
Der Angriff
Beginnen wir also damit, den Angriff zu zeigen ...
Unter bestimmten Umständen wird mehr als eine Zeile zurückgegeben. Lassen Sie uns analysieren, was hier los ist:
Auswählen eines Zeichensatzes
Für diesen Angriff zu arbeiten, müssen wir die Codierung , dass der Server die auf der Verbindung erwartet sowohl codieren
'
als in ASCII dh0x27
und einige Zeichen zu haben , deren letzte Byte ist ein ASCII\
dh0x5c
. Wie sich herausstellt, gibt es 5 solche Codierungen in MySQL 5.6 standardmäßig unterstützt:big5
,cp932
,gb2312
,gbk
undsjis
. Wir werdengbk
hier auswählen .Nun ist es sehr wichtig, die Verwendung von
SET NAMES
hier zu beachten . Dies setzt den Zeichensatz auf dem Server . Es gibt noch einen anderen Weg, aber wir werden früh genug dort sein.Die Nutzlast
Die Nutzlast, die wir für diese Injektion verwenden werden, beginnt mit der Bytesequenz
0xbf27
. Ingbk
ist das ein ungültiges Multibyte-Zeichen. inlatin1
, es ist die Zeichenfolge¿'
. Beachten Sie, dass inlatin1
undgbk
,0x27
auf seinem eigenen ein wörtlicher ist'
Charakter.Wir haben diese Nutzlast gewählt , weil, wenn wir aufgerufen
addslashes()
darauf, würden wir eine ASCII einfügen\
dh0x5c
, vor dem'
Charakter. Also haben wir mit aufzuwickeln würde0xbf5c27
, die ingbk
eine zwei Zeichenfolge:0xbf5c
gefolgt von0x27
. Oder mit anderen Worten, ein gültiges Zeichen, gefolgt von einem nicht entflohenen'
. Aber wir benutzen nichtaddslashes()
. Also weiter zum nächsten Schritt ...$ stmt-> execute ()
Das Wichtigste dabei ist, dass PDO standardmäßig KEINE wirklich vorbereiteten Anweisungen ausführt. Es emuliert sie (für MySQL). Daher erstellt PDO intern die Abfragezeichenfolge und ruft
mysql_real_escape_string()
(die MySQL C-API-Funktion) für jeden gebundenen Zeichenfolgenwert auf.Der C-API-Aufruf von
mysql_real_escape_string()
unterscheidet sichaddslashes()
darin, dass er den Verbindungszeichensatz kennt. So kann die Escape-Anweisung für den vom Server erwarteten Zeichensatz ordnungsgemäß ausgeführt werden. Bis zu diesem Punkt glaubt der Client jedoch, dass wir immer nochlatin1
für die Verbindung verwenden, da wir es nie anders gesagt haben. Wir haben dem Server mitgeteiltgbk
, dass wir ihn verwenden , aber der Client glaubt immer noch, dass dies der Fall istlatin1
.Daher der Aufruf,
mysql_real_escape_string()
den Backslash einzufügen, und wir haben einen frei hängenden'
Charakter in unserem "entkommenen" Inhalt! Wenn wir uns$var
dengbk
Zeichensatz ansehen würden, würden wir tatsächlich sehen:Welches ist genau das, was der Angriff erfordert.
Die Abfrage
Dieser Teil ist nur eine Formalität, aber hier ist die gerenderte Abfrage:
Herzlichen Glückwunsch, Sie haben gerade ein Programm mit PDO Prepared Statements erfolgreich angegriffen ...
Die einfache Lösung
Nun ist es erwähnenswert, dass Sie dies verhindern können, indem Sie emulierte vorbereitete Anweisungen deaktivieren:
Dies führt normalerweise zu einer wirklich vorbereiteten Anweisung (dh die Daten werden in einem von der Abfrage getrennten Paket gesendet). Beachten Sie jedoch, dass PDO leise Rückfall auf Anweisungen emuliert , dass MySQL nicht nativ vorbereiten können: diejenigen , die es können , sind aufgelistet in dem Handbuch, aber passen Sie den entsprechenden Server - Version wählen).
Die richtige Lösung
Das Problem hierbei ist, dass wir nicht die C-APIs
mysql_set_charset()
anstelle von aufgerufen habenSET NAMES
. In diesem Fall wäre alles in Ordnung, vorausgesetzt, wir verwenden seit 2006 eine MySQL-Version.Wenn Sie eine frühere MySQL - Release verwenden, dann einen Fehler in
mysql_real_escape_string()
gemeint , dass ungültige Mehrbytezeichen wie die in unserer Nutzlast als einzelnes Bytes behandelt wurden , für die Zwecke entkommen , auch wenn der Kunde hat richtig die Verbindung Codierung informiert worden und so dieser Angriff würde immer noch erfolgreich. Der Fehler wurde in MySQL 4.1.20 , 5.0.22 und 5.1.11 behoben .Das Schlimmste ist
PDO
jedoch, dass die C-APImysql_set_charset()
erst in Version 5.3.6 verfügbar gemacht wurde. In früheren Versionen kann sie diesen Angriff nicht für jeden möglichen Befehl verhindern! Es wird jetzt als DSN-Parameter angezeigt , der anstelle vonSET NAMES
...Die rettende Gnade
Wie eingangs erwähnt, muss die Datenbankverbindung mit einem anfälligen Zeichensatz codiert werden, damit dieser Angriff funktioniert.
utf8mb4
ist nicht anfällig und kann dennoch jedes Unicode-Zeichen unterstützen. Sie können sich also dafür entscheiden, dieses zu verwenden. Es ist jedoch erst seit MySQL 5.5.3 verfügbar. Eine Alternative istutf8
, die ebenfalls nicht anfällig ist und die gesamte mehrsprachige Unicode- Grundebene unterstützen kann .Alternativ können Sie den
NO_BACKSLASH_ESCAPES
SQL-Modus aktivieren , der (unter anderem) den Betrieb von ändertmysql_real_escape_string()
. Wenn dieser Modus aktiviert ist,0x27
wird er durch0x2727
und nicht ersetzt,0x5c27
und daher kann der Escape-Prozess keine gültigen Zeichen in einer der anfälligen Codierungen erstellen, in denen sie zuvor nicht vorhanden waren (dh0xbf27
immer noch0xbf27
usw.). Daher lehnt der Server die Zeichenfolge weiterhin als ungültig ab . In der Antwort von @ eggyal finden Sie jedoch eine andere Sicherheitsanfälligkeit, die sich aus der Verwendung dieses SQL-Modus ergeben kann (allerdings nicht mit PDO).Sichere Beispiele
Die folgenden Beispiele sind sicher:
Weil der Server erwartet
utf8
...Weil wir den Zeichensatz richtig eingestellt haben, damit Client und Server übereinstimmen.
Weil wir emulierte vorbereitete Anweisungen deaktiviert haben.
Weil wir den Zeichensatz richtig eingestellt haben.
Weil MySQLi ständig wirklich vorbereitete Anweisungen ausführt.
Einpacken
Wenn du:
ODER
utf8
/latin1
/ascii
/ etc).ODER
NO_BACKSLASH_ESCAPES
SQL-ModusDu bist 100% sicher.
Andernfalls sind Sie anfällig , obwohl Sie PDO Prepared Statements verwenden ...
Nachtrag
Ich habe langsam an einem Patch gearbeitet, um die Standardeinstellung so zu ändern, dass keine Vorbereitungen für eine zukünftige Version von PHP emuliert werden. Das Problem, auf das ich stoße, ist, dass viele Tests dabei unterbrochen werden. Ein Problem ist, dass emulierte Vorbereitungen nur Syntaxfehler bei der Ausführung auslösen, echte Vorbereitungen jedoch Fehler bei der Vorbereitung. Das kann also Probleme verursachen (und ist Teil des Grundes, warum Tests nicht funktionieren).
quelle
mysql_real_escape_string
Fälle, in denen die Verbindung ordnungsgemäß auf BIG5 / GBK eingestellt war, nicht ordnungsgemäß behandelt. Selbst das Aufrufenmysql_set_charset()
von MySQL <5.0.22 wäre für diesen Fehler anfällig! Also nein, dieser Beitrag gilt weiterhin für 5.0.22 (da mysql_real_escape_string nur für Anrufe von Zeichensätzen entferntmysql_set_charset()
ist, worüber in diesem Beitrag die Umgehung gesprochen wird) ...NO_BACKSLASH_ESCAPES
dies auch zu neuen Sicherheitslücken führen kann: stackoverflow.com/a/23277864/1014813Vorbereitete Anweisungen / parametrisierte Abfragen reichen im Allgemeinen aus, um eine Injektion 1. Ordnung in diese Anweisung * zu verhindern . Wenn Sie an anderer Stelle in Ihrer Anwendung ungeprüftes dynamisches SQL verwenden, sind Sie immer noch anfällig für Injektionen 2. Ordnung .
Injektion 2. Ordnung bedeutet, dass Daten einmal durch die Datenbank geleitet wurden, bevor sie in eine Abfrage aufgenommen wurden, und dass es viel schwieriger ist, sie abzurufen. AFAIK, Sie sehen fast nie echte technische Angriffe 2. Ordnung, da es für Angreifer normalerweise einfacher ist, sich als Social-Engineer einzumischen, aber manchmal treten Fehler 2. Ordnung aufgrund besonders harmloser
'
Charaktere oder Ähnlichem auf.Sie können einen Injektionsangriff 2. Ordnung ausführen, wenn Sie veranlassen können, dass ein Wert in einer Datenbank gespeichert wird, die später als Literal in einer Abfrage verwendet wird. Angenommen, Sie geben beim Erstellen eines Kontos auf einer Website die folgenden Informationen als neuen Benutzernamen ein (vorausgesetzt, MySQL DB für diese Frage):
Wenn es keine weiteren Einschränkungen für den Benutzernamen gibt, würde eine vorbereitete Anweisung dennoch sicherstellen, dass die oben eingebettete Abfrage zum Zeitpunkt des Einfügens nicht ausgeführt wird, und den Wert korrekt in der Datenbank speichern. Stellen Sie sich jedoch vor, dass die Anwendung später Ihren Benutzernamen aus der Datenbank abruft und die Zeichenfolgenverkettung verwendet, um diesen Wert in eine neue Abfrage aufzunehmen. Möglicherweise sehen Sie das Passwort einer anderen Person. Da die ersten Namen in der Benutzertabelle in der Regel Administratoren sind, haben Sie möglicherweise auch gerade die Farm verschenkt. (Beachten Sie auch: Dies ist ein weiterer Grund, Passwörter nicht im Klartext zu speichern!)
Wir sehen also, dass vorbereitete Anweisungen für eine einzelne Abfrage ausreichen , aber allein nicht ausreichen, um sich vor SQL-Injection-Angriffen in einer gesamten Anwendung zu schützen, da ihnen ein Mechanismus fehlt, um den gesamten Zugriff auf eine Datenbank innerhalb einer Anwendung zu erzwingen, die sicher verwendet wird Code. Jedoch im Rahmen eines ordnungsgemäßen Anwendung Design verwendet - die Praktiken wie Code - Review oder statische Analyse oder Verwendung eines ORM, Datenschicht oder Dienstschicht umfassen kann , dass Grenzen dynamische SQL - Prepared Statements sind das wichtigste Werkzeug für die Lösung der SQL - Injection Problem.Wenn Sie gute Prinzipien für das Anwendungsdesign befolgen, sodass Ihr Datenzugriff vom Rest Ihres Programms getrennt ist, können Sie leicht erzwingen oder prüfen, ob jede Abfrage die Parametrisierung korrekt verwendet. In diesem Fall wird die SQL-Injektion (sowohl erster als auch zweiter Ordnung) vollständig verhindert.
* Es stellt sich heraus, dass MySql / PHP (okay, waren) nur dumm im Umgang mit Parametern sind, wenn breite Zeichen beteiligt sind, und es gibt immer noch einen seltenen Fall, der in der anderen hoch bewerteten Antwort hier umrissen ist , der es ermöglichen kann, dass die Injektion durch einen parametrisierten Parameter rutscht Abfrage.
quelle
Nein, das sind sie nicht immer.
Dies hängt davon ab, ob Sie zulassen, dass Benutzereingaben in die Abfrage selbst eingefügt werden. Zum Beispiel:
wäre anfällig für SQL-Injektionen und die Verwendung vorbereiteter Anweisungen in diesem Beispiel funktioniert nicht, da die Benutzereingabe als Bezeichner und nicht als Daten verwendet wird. Die richtige Antwort wäre hier eine Art Filterung / Validierung wie:
Hinweis: Sie können PDO nicht zum Binden von Daten verwenden, die außerhalb von DDL (Data Definition Language) liegen. Dies funktioniert also nicht:
Der Grund, warum das oben genannte nicht funktioniert, ist, weil
DESC
undASC
sind keine Daten . PDO kann nur für Daten entkommen . Zweitens können Sie nicht einmal'
Anführungszeichen setzen . Die einzige Möglichkeit, die vom Benutzer gewählte Sortierung zuzulassen, besteht darin, manuell zu filtern und zu überprüfen, ob dies entwederDESC
oder der Fall istASC
.quelle
SELECT * FROM 'table'
Kann zum Beispiel falsch sein, wie es sein sollteSELECT * FROM `table`
oder ohne Backsticks. Dann können einige Dinge wie,ORDER BY DESC
woherDESC
der Benutzer kommt, nicht einfach entkommen. Praktische Szenarien sind also eher unbegrenzt.switch
, um den Tabellennamen abzuleiten.Ja, das reicht aus. Die Art und Weise, wie Injection-Angriffe funktionieren, besteht darin, dass ein Interpreter (die Datenbank) etwas auswertet, das Daten sein sollte, als wäre es Code. Dies ist nur möglich, wenn Sie Code und Daten auf demselben Medium mischen (z. B. wenn Sie eine Abfrage als Zeichenfolge erstellen).
Parametrisierte Abfragen funktionieren, indem der Code und die Daten separat gesendet werden, sodass es niemals möglich wäre, eine Lücke darin zu finden.
Sie können jedoch weiterhin anfällig für andere Angriffe vom Typ Injektion sein. Wenn Sie beispielsweise die Daten in einer HTML-Seite verwenden, können Sie Angriffen vom Typ XSS ausgesetzt sein.
quelle
Nein, das reicht nicht (in bestimmten Fällen)! Standardmäßig verwendet PDO emulierte vorbereitete Anweisungen, wenn MySQL als Datenbanktreiber verwendet wird. Sie sollten emulierte vorbereitete Anweisungen immer deaktivieren, wenn Sie MySQL und PDO verwenden:
Eine andere Sache, die immer getan werden sollte, stellte die korrekte Codierung der Datenbank ein:
Siehe auch diese verwandte Frage: Wie kann ich die SQL-Injection in PHP verhindern?
Beachten Sie auch, dass es nur um die Datenbankseite der Dinge geht, die Sie beim Anzeigen der Daten noch selbst beobachten müssten. ZB durch erneutes Verwenden
htmlspecialchars()
mit dem richtigen Codierungs- und Zitierstil.quelle
Persönlich würde ich immer zuerst eine Form der Bereinigung der Daten durchführen, da Sie Benutzereingaben niemals vertrauen können. Wenn Sie jedoch Platzhalter / Parameterbindungen verwenden, werden die eingegebenen Daten separat an die SQL-Anweisung an den Server gesendet und dann zusammengebunden. Der Schlüssel hier ist, dass dies die bereitgestellten Daten an einen bestimmten Typ und eine bestimmte Verwendung bindet und jede Möglichkeit ausschließt, die Logik der SQL-Anweisung zu ändern.
quelle
Eaven, wenn Sie das Front-End der SQL-Injektion mithilfe von HTML- oder JS-Prüfungen verhindern möchten, müssen Sie berücksichtigen, dass Front-End-Prüfungen "umgehbar" sind.
Sie können js deaktivieren oder ein Muster mit einem Front-End-Entwicklungstool bearbeiten (das heutzutage mit Firefox oder Chrome integriert ist).
Um eine SQL-Injection zu verhindern, ist es daher richtig, das Backend für das Eingabedatum in Ihrem Controller zu bereinigen.
Ich möchte Ihnen vorschlagen, die native PHP-Funktion filter_input () zu verwenden, um GET- und INPUT-Werte zu bereinigen.
Wenn Sie bei vernünftigen Datenbankabfragen mit der Sicherheit fortfahren möchten, möchte ich Ihnen empfehlen, reguläre Ausdrücke zur Überprüfung des Datenformats zu verwenden. preg_match () hilft dir in diesem Fall! Aber pass auf dich auf! Regex Motor ist nicht so leicht. Verwenden Sie es nur bei Bedarf, da sonst die Anwendungsleistung abnimmt.
Sicherheit hat Kosten, aber verschwenden Sie nicht Ihre Leistung!
Einfaches Beispiel:
Wenn Sie überprüfen möchten, ob ein von GET empfangener Wert eine Zahl ist, weniger als 99, wenn (! preg_match ('/ [0-9] {1,2} /')) {...} schwerer ist
Die endgültige Antwort lautet also: "Nein! PDO Prepared Statements verhindern nicht alle Arten von SQL-Injection"; Es verhindert keine unerwarteten Werte, sondern nur eine unerwartete Verkettung
quelle