Gibt es eine SQL-Injection-Möglichkeit, auch wenn die mysql_real_escape_string()
Funktion verwendet wird?
Betrachten Sie diese Beispielsituation. SQL ist in PHP wie folgt aufgebaut:
$login = mysql_real_escape_string(GetFromPost('login'));
$password = mysql_real_escape_string(GetFromPost('password'));
$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";
Ich habe zahlreiche Leute zu mir sagen hören, dass solcher Code immer noch gefährlich ist und auch mit der verwendeten mysql_real_escape_string()
Funktion gehackt werden kann. Aber ich kann mir keinen möglichen Exploit vorstellen?
Klassische Injektionen wie diese:
aaa' OR 1=1 --
arbeite nicht.
Kennen Sie eine mögliche Injektion, die durch den obigen PHP-Code gelangen würde?
mysql_*
Funktionen in neuem Code . Sie werden nicht mehr gepflegt und der Abschreibungsprozess hat damit begonnen. Sehen Sie die rote Box ? Erfahrenmehr über vorbereitete Anweisungen statt, und verwenden Sie PDO oder MySQLi - dieser Artikel wird Ihnen helfen, entscheidenwelche. Wenn Sie sich für PDO entscheiden, finden Sie hier ein gutes Tutorial .mysql_*
erzeugen die Funktionen bereits eineE_DEPRECATED
Warnung. Dieext/mysql
Verlängerung wurde seit mehr als 10 Jahren nicht mehr gewartet. Bist du wirklich so wahnhaft?Antworten:
Betrachten Sie die folgende Abfrage:
mysql_real_escape_string()
wird dich nicht davor schützen. Die Tatsache, dass Sie' '
in Ihrer Abfrage einfache Anführungszeichen ( ) um Ihre Variablen verwenden, schützt Sie davor. Folgendes ist ebenfalls eine Option:quelle
mysql_query()
nicht mehrere Anweisungen ausgeführt werden, oder?DROP TABLE
, ist es in der Praxis wahrscheinlicher, dass der AngreiferSELECT passwd FROM users
. Im letzteren Fall wird die zweite Abfrage normalerweise unter Verwendung einerUNION
Klausel ausgeführt.(int)mysql_real_escape_string
- das macht keinen Sinn. Es unterscheidet sich überhaupt nicht von(int)
. Und sie werden das gleiche Ergebnis für jede Eingabe produzierenmysql_real_escape_string
nichtmysql_real_escape_integer
. Es ist nicht dazu gedacht, mit ganzzahligen Feldern verwendet zu werden.Die kurze Antwort lautet ja, ja, es gibt einen Weg, um herumzukommen
mysql_real_escape_string()
.Für sehr OBSCURE EDGE CASES !!!
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 . Wenn wir den Aufruf der C-API-Funktion verwenden würdenmysql_set_charset()
, wäre dies in Ordnung (bei MySQL-Versionen seit 2006). Aber mehr dazu in einer Minute ...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 ...mysql_real_escape_string ()
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 erfolgreich ein Programm mit
mysql_real_escape_string()
...Das Schlechte
Es wird schlimmer.
PDO
Standardmäßig werden vorbereitete Anweisungen mit MySQL emuliert . Das bedeutet, dass auf der Client-Seite im Grunde genommen ein Sprintfmysql_real_escape_string()
(in der C-Bibliothek) durchgeführt wird, was bedeutet, dass Folgendes zu einer erfolgreichen Injektion führt: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).
Das hässliche
Ich sagte ganz am Anfang, dass wir all dies hätten verhindern können, wenn wir
mysql_set_charset('gbk')
stattdessen verwendet hättenSET NAMES gbk
. Und das stimmt, vorausgesetzt, Sie 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 .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.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:
mysql_set_charset()
/$mysqli->set_charset()
/ PDOs DSN-Zeichensatzparameter (in PHP ≥ 5.3.6).ODER
utf8
/latin1
/ascii
/ etc).Du bist 100% sicher.
Andernfalls sind Sie verwundbar , obwohl Sie
mysql_real_escape_string()
...quelle
addslashes
. Ich basierte diese Sicherheitsanfälligkeit auf , dass ein. Versuch es selber. Holen Sie sich MySQL 5.0, führen Sie diesen Exploit aus und überzeugen Sie sich selbst. Was das in PUT / GET / POST angeht, ist es TRIVIAL. Eingabedaten sind nur Bytestreams.char(0xBF)
ist nur eine lesbare Methode zum Generieren eines Bytes. Ich habe diese Sicherheitsanfälligkeit live vor mehreren Konferenzen demonstriert. Vertrauen Sie mir in diesem Punkt ... Aber wenn Sie es nicht tun, versuchen Sie es selbst. Es funktioniert ...?var=%BF%27+OR+1=1+%2F%2A
in der URL,$var = $_GET['var'];
im Code und Bobs Onkel angeht .Dies ist ein weiterer (vielleicht weniger?) Dunkler EDGE CASE !!!
Als Hommage an @ ircmaxells hervorragende Antwort (eigentlich soll dies Schmeichelei und kein Plagiat sein!) Werde ich sein Format übernehmen:
Der Angriff
Beginnen Sie mit einer Demonstration ...
Dadurch werden alle Datensätze aus der
test
Tabelle zurückgegeben. Eine Dissektion:Auswählen eines SQL-Modus
Wie unter String Literals dokumentiert :
Wenn der SQL-Modus des Servers Folgendes enthält
NO_BACKSLASH_ESCAPES
, ist die dritte dieser Optionen - der übliche Ansatz vonmysql_real_escape_string()
- nicht verfügbar: Stattdessen muss eine der ersten beiden Optionen verwendet werden. Beachten Sie, dass der Effekt des vierten Aufzählungszeichens darin besteht, dass man unbedingt das Zeichen kennen muss, das zum Zitieren des Literals verwendet wird, um zu vermeiden, dass die eigenen Daten durcheinander gebracht werden.Die Nutzlast
Die Nutzlast initiiert diese Injektion buchstäblich mit dem
"
Charakter. Keine besondere Kodierung. Keine Sonderzeichen. Keine seltsamen Bytes.mysql_real_escape_string ()
Zum Glück wird
mysql_real_escape_string()
der SQL-Modus überprüft und sein Verhalten entsprechend angepasst. Siehelibmysql.c
:Daher wird eine andere zugrunde liegende Funktion
escape_quotes_for_mysql()
aufgerufen, wenn derNO_BACKSLASH_ESCAPES
SQL-Modus verwendet wird. Wie oben erwähnt, muss eine solche Funktion wissen, mit welchem Zeichen das Literal zitiert wird, um es zu wiederholen, ohne dass das andere Anführungszeichen wörtlich wiederholt wird.Diese Funktion setzt jedoch willkürlich voraus, dass die Zeichenfolge in Anführungszeichen gesetzt
'
wird. Siehecharset.c
:Es
"
bleiben also doppelte Anführungszeichen unberührt (und alle doppelten Anführungszeichen werden verdoppelt'
), unabhängig von dem tatsächlichen Zeichen, mit dem das Literal zitiert wird ! In unserem Fall$var
bleibt genau das gleiche wie das Argument, die zur Verfügung gestellt wurdemysql_real_escape_string()
-es ist , als ob kein Entkommen stattgefunden hat überhaupt .Die Abfrage
Die gerenderte Abfrage ist eine Art Formalität:
Wie mein gelehrter Freund es ausdrückte: Herzlichen Glückwunsch, Sie haben gerade erfolgreich ein Programm mit
mysql_real_escape_string()
...Das Schlechte
mysql_set_charset()
kann nicht helfen, da dies nichts mit Zeichensätzen zu tun hat; Das kann auch nichtmysqli::real_escape_string()
, da dies nur ein anderer Wrapper um dieselbe Funktion ist.Das Problem ist, wenn nicht bereits offensichtlich, dass der Aufruf,
mysql_real_escape_string()
nicht zu wissen, mit welchem Zeichen das Literal zitiert wird, da dies dem Entwickler überlassen bleibt, um zu einem späteren Zeitpunkt zu entscheiden. ImNO_BACKSLASH_ESCAPES
Modus gibt es also buchstäblich keine Möglichkeit, dass diese Funktion sicher jeder Eingabe für die Verwendung mit willkürlichen Anführungszeichen entgeht (zumindest nicht ohne das Verdoppeln von Zeichen, die kein Verdoppeln und damit das Munging Ihrer Daten erfordern).Das hässliche
Es wird schlimmer.
NO_BACKSLASH_ESCAPES
In freier Wildbahn ist dies möglicherweise nicht allzu ungewöhnlich, da es für die Kompatibilität mit Standard-SQL erforderlich ist (siehe z. B. Abschnitt 5.3 der SQL-92-Spezifikation , nämlich die<quote symbol> ::= <quote><quote>
Grammatikproduktion und das Fehlen einer besonderen Bedeutung für Backslash). Darüber hinaus wurde seine Verwendung ausdrücklich als Problemumgehung für die (längst behoben) empfohlen behobenen Fehler empfohlen, den der Beitrag von ircmaxell beschreibt. Wer weiß, einige Datenbankadministratoren konfigurieren es möglicherweise sogar so, dass es standardmäßig aktiviert ist, um die Verwendung falscher Escape-Methoden wie zaddslashes()
.Außerdem wird der SQL-Modus einer neuen Verbindung vom Server entsprechend seiner Konfiguration festgelegt (welche a
SUPER
Benutzer jederzeit ändern kann). Um sicherzugehen, dass sich der Server verhält, müssen Sie nach dem Herstellen der Verbindung immer explizit den gewünschten Modus angeben.Die rettende Gnade
Solange Sie den SQL-Modus immer explizit so einstellen
NO_BACKSLASH_ESCAPES
, dass MySQL-Zeichenfolgenliterale nicht mit dem einfachen Anführungszeichen eingeschlossen oder zitiert werden, kann dieser Fehler seinen hässlichen Kopf nicht aufrichtenescape_quotes_for_mysql()
wird nicht verwendet, oder die Annahme, welche Anführungszeichen wiederholt werden müssen, wird richtig sein.Aus diesem Grund empfehle ich jedem, der
NO_BACKSLASH_ESCAPES
auch aktiviertANSI_QUOTES
Modus , da dadurch die gewöhnliche Verwendung von String-Literalen in einfachen Anführungszeichen erzwungen wird. Beachten Sie, dass dies die SQL-Injection nicht verhindert, falls Literale in doppelten Anführungszeichen verwendet werden. Es verringert lediglich die Wahrscheinlichkeit, dass dies geschieht (da normale, nicht böswillige Abfragen fehlschlagen würden).In PDO
PDO::quote()
rufen sowohl seine äquivalente Funktion als auch sein vorbereiteter Anweisungsemulator aufmysql_handle_quoter()
- was genau dies tut: Er stellt sicher, dass das maskierte Literal in einfache Anführungszeichen gesetzt wird, sodass Sie sicher sein können, dass PDO immer gegen diesen Fehler immun ist.Ab MySQL v5.7.6 wurde dieser Fehler behoben. Siehe Änderungsprotokoll :
Sichere Beispiele
Zusammen mit dem von ircmaxell erläuterten Fehler sind die folgenden Beispiele völlig sicher (vorausgesetzt, man verwendet MySQL später als 4.1.20, 5.0.22, 5.1.11; oder man verwendet keine GBK / Big5-Verbindungscodierung). ::
... weil wir explizit einen SQL-Modus ausgewählt haben, der nicht enthalten ist
NO_BACKSLASH_ESCAPES
.... weil wir unser String-Literal in einfache Anführungszeichen setzen.
... weil von PDO vorbereitete Anweisungen gegen diese Sicherheitsanfälligkeit immun sind (und auch von ircmaxell, vorausgesetzt, Sie verwenden PHP ≥ 5.3.6 und der Zeichensatz wurde im DSN korrekt festgelegt, oder die Emulation vorbereiteter Anweisungen wurde deaktiviert). .
... weil die
quote()
Funktion von PDO dem Literal nicht nur entgeht, sondern es auch zitiert (in einfachen Anführungszeichen'
); Beachten Sie, dass Sie PHP ≥ 5.3.6 verwenden und den Zeichensatz im DSN korrekt eingestellt haben müssen , um den Fehler von ircmaxell in diesem Fall zu vermeiden .... weil von MySQLi vorbereitete Anweisungen sicher sind.
Einpacken
Also, wenn Sie:
ODER
ODER
in zusätzlich eine der Lösungen in ircmaxell Zusammenfassung, die Verwendung mindestens eines zu beschäftigen:
NO_BACKSLASH_ESCAPES
... dann sollten Sie absolut sicher sein (Schwachstellen außerhalb des Bereichs, in dem Zeichenfolgen ausgeblendet werden).
quelle
"
für Strings verwenden. SQL sagt, das ist für Bezeichner. Aber eh ... nur ein weiteres Beispiel für MySQL, das sagt: "Schraubenstandards, ich mache was ich will". (Glücklicherweise können SieANSI_QUOTES
in den Modus aufnehmen, um diequote()
Nun, es gibt nichts, was das wirklich passieren kann, außer
%
Platzhalter. Es könnte gefährlich sein, wenn Sie eineLIKE
Anweisung verwenden, die der Angreifer%
als Anmeldung verwenden könnte, wenn Sie dies nicht herausfiltern, und nur ein Kennwort eines Ihrer Benutzer brutal erzwingen müssten. Oft wird empfohlen, vorbereitete Anweisungen zu verwenden, um die Sicherheit zu 100% zu gewährleisten, da Daten die Abfrage selbst auf diese Weise nicht stören können. Aber für solch einfache Abfragen wäre es wahrscheinlich effizienter, so etwas zu tun$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);
quelle
more efficient
als die Verwendung vorbereiteter Anweisungen? (Vorbereitete Anweisungen funktionieren immer, die Bibliothek kann im Falle von Angriffen schnell korrigiert werden, legt keine menschlichen Fehler offen (z. B. falsche Eingabe der vollständigen Ersetzungszeichenfolge) und bietet erhebliche Leistungsvorteile, wenn die Anweisung erneut verwendet wird.)