SQL-Injection, die mysql_real_escape_string () umgeht

645

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?

Richard Knop
quelle
34
@ThiefMaster - Ich ziehe es vor, keine ausführlichen Fehler wie ungültigen Benutzer / ungültiges Passwort anzugeben ... es teilt Brute-Force-Händlern mit, dass sie eine gültige Benutzer-ID haben, und es ist nur das Passwort, das sie erraten müssen
Mark Baker
18
Unter dem Gesichtspunkt der Benutzerfreundlichkeit ist dies jedoch schrecklich. Manchmal konnten Sie Ihren Haupt-Spitznamen / Benutzernamen / Ihre E-Mail-Adresse nicht verwenden und dies nach einiger Zeit vergessen, oder die Site hat Ihr Konto wegen Inaktivität gelöscht. Dann ist es äußerst ärgerlich, wenn Sie weiterhin Passwörter versuchen und möglicherweise sogar Ihre IP blockieren, obwohl nur Ihr Benutzername ungültig ist.
ThiefMaster
50
Bitte verwenden Sie keine 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 .
Tereško
13
@machineaddict, seit 5.5 (das kürzlich veröffentlicht wurde) mysql_*erzeugen die Funktionen bereits eine E_DEPRECATEDWarnung. Die ext/mysqlVerlängerung wurde seit mehr als 10 Jahren nicht mehr gewartet. Bist du wirklich so wahnhaft?
Tereško
13
@machineaddict Sie haben gerade diese Erweiterung unter PHP 7.0 entfernt und es ist noch nicht 2050.
GGG

Antworten:

379

Betrachten Sie die folgende Abfrage:

$iId = mysql_real_escape_string("1 OR 1=1");    
$sSql = "SELECT * FROM table WHERE id = $iId";

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:

$iId = (int)"1 OR 1=1";
$sSql = "SELECT * FROM table WHERE id = $iId";
Wesley van Opdorp
quelle
9
Aber das wäre kein echtes Problem, weil mysql_query()nicht mehrere Anweisungen ausgeführt werden, oder?
Pekka
11
@ Pekka, obwohl das übliche Beispiel ist DROP TABLE, ist es in der Praxis wahrscheinlicher, dass der Angreifer SELECT passwd FROM users. Im letzteren Fall wird die zweite Abfrage normalerweise unter Verwendung einer UNIONKlausel ausgeführt.
Jacco
58
(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 produzieren
zerkms
28
Dies ist mehr ein Missbrauch der Funktion als alles andere. Immerhin heißt es mysql_real_escape_stringnicht mysql_real_escape_integer. Es ist nicht dazu gedacht, mit ganzzahligen Feldern verwendet zu werden.
NullUserException
11
@ircmaxell, doch die Antwort ist völlig irreführend. Offensichtlich stellt sich die Frage nach dem Inhalt der Zitate. "Zitate sind nicht da" ist nicht die Antwort auf diese Frage.
Pacerier
629

Die kurze Antwort lautet ja, ja, es gibt einen Weg, um herumzukommenmysql_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 ...

mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Unter bestimmten Umständen wird mehr als eine Zeile zurückgegeben. Lassen Sie uns analysieren, was hier los ist:

  1. Auswählen eines Zeichensatzes

    mysql_query('SET NAMES gbk');

    Für diesen Angriff zu arbeiten, müssen wir die Codierung , dass der Server die auf der Verbindung erwartet sowohl codieren 'als in ASCII dh 0x27 und einige Zeichen zu haben , deren letzte Byte ist ein ASCII \dh 0x5c. Wie sich herausstellt, gibt es 5 solche Codierungen in MySQL 5.6 standardmäßig unterstützt: big5, cp932, gb2312, gbkund sjis. Wir werden gbkhier auswählen .

    Nun ist es sehr wichtig, die Verwendung von SET NAMEShier zu beachten . Dies setzt den Zeichensatz auf dem Server . Wenn wir den Aufruf der C-API-Funktion verwenden würden mysql_set_charset(), wäre dies in Ordnung (bei MySQL-Versionen seit 2006). Aber mehr dazu in einer Minute ...

  2. Die Nutzlast

    Die Nutzlast, die wir für diese Injektion verwenden werden, beginnt mit der Bytesequenz 0xbf27. In gbkist das ein ungültiges Multibyte-Zeichen. in latin1, es ist die Zeichenfolge ¿'. Beachten Sie, dass in latin1 und gbk , 0x27auf 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 \dh 0x5c, vor dem 'Charakter. Also haben wir mit aufzuwickeln würde 0xbf5c27, die in gbkeine zwei Zeichenfolge: 0xbf5cgefolgt von 0x27. Oder mit anderen Worten, ein gültiges Zeichen, gefolgt von einem nicht entflohenen '. Aber wir benutzen nicht addslashes(). Also weiter zum nächsten Schritt ...

  3. mysql_real_escape_string ()

    Der C-API-Aufruf von mysql_real_escape_string()unterscheidet sich addslashes()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 noch latin1für die Verbindung verwenden, da wir es nie anders gesagt haben. Wir haben dem Server mitgeteilt gbk, dass wir ihn verwenden , aber der Client glaubt immer noch, dass dies der Fall ist latin1.

    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 $varden gbkZeichensatz ansehen würden, würden wir tatsächlich sehen:

    縗 'OR 1 = 1 / *

    Welches ist genau das, was der Angriff erfordert.

  4. Die Abfrage

    Dieser Teil ist nur eine Formalität, aber hier ist die gerenderte Abfrage:

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1

Herzlichen Glückwunsch, Sie haben gerade erfolgreich ein Programm mit mysql_real_escape_string()...

Das Schlechte

Es wird schlimmer. PDOStandardmäßig werden vorbereitete Anweisungen mit MySQL emuliert . Das bedeutet, dass auf der Client-Seite im Grunde genommen ein Sprintf mysql_real_escape_string()(in der C-Bibliothek) durchgeführt wird, was bedeutet, dass Folgendes zu einer erfolgreichen Injektion führt:

$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Nun ist es erwähnenswert, dass Sie dies verhindern können, indem Sie emulierte vorbereitete Anweisungen deaktivieren:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

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ätten SET 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 PDOjedoch, dass die C-API mysql_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. utf8mb4ist 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 ist utf8, die ebenfalls nicht anfällig ist und die gesamte mehrsprachige Unicode- Grundebene unterstützen kann .

Alternativ können Sie den NO_BACKSLASH_ESCAPESSQL-Modus aktivieren , der (unter anderem) den Betrieb von ändert mysql_real_escape_string(). Wenn dieser Modus aktiviert ist, 0x27wird er durch 0x2727und nicht ersetzt, 0x5c27und daher kann der Escape-Prozess keine gültigen Zeichen in einer der anfälligen Codierungen erstellen, in denen sie zuvor nicht vorhanden waren (dh 0xbf27immer noch 0xbf27usw.). 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:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Weil der Server erwartet utf8...

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Weil wir den Zeichensatz richtig eingestellt haben, damit Client und Server übereinstimmen.

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Weil wir emulierte vorbereitete Anweisungen deaktiviert haben.

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Weil wir den Zeichensatz richtig eingestellt haben.

$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

Weil MySQLi ständig wirklich vorbereitete Anweisungen ausführt.

Einpacken

Wenn du:

  • Verwenden Sie moderne Versionen von MySQL (Ende 5.1, alle 5.5, 5.6 usw.) UND mysql_set_charset() / $mysqli->set_charset()/ PDOs DSN-Zeichensatzparameter (in PHP ≥ 5.3.6).

ODER

  • Verwenden Sie keinen anfälligen Zeichensatz für die Verbindungscodierung (Sie verwenden nur utf8/ latin1/ ascii/ etc).

Du bist 100% sicher.

Andernfalls sind Sie verwundbar , obwohl Siemysql_real_escape_string() ...

ircmaxell
quelle
3
PDO-Emulation von Prepare-Anweisungen für MySQL, wirklich? Ich sehe keinen Grund, warum das so wäre, da der Treiber es nativ unterstützt. Nein?
Netcoder
16
Es tut. Sie sagen in der Dokumentation, dass dies nicht der Fall ist. Aber im Quellcode ist es deutlich sichtbar und leicht zu beheben. Ich schreibe es auf Inkompetenz der Entwickler.
Theodore R. Smith
5
@ TheodoreR.Smith: Es ist nicht so einfach zu beheben. Ich habe daran gearbeitet, die Standardeinstellung zu ändern, aber beim Umschalten schlägt eine Schiffsladung von Tests fehl. Es ist also eine größere Veränderung als es scheint. Ich hoffe immer noch, dass es bis 5.5 fertig sein wird ...
ircmaxell
14
@shadyyx: Nein, es ging um die Sicherheitslücke, um die es in dem Artikel ging 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 ...
ircmaxell
5
@shadyyx: Was das Übergeben einer solchen Funky in $ _GET ... ?var=%BF%27+OR+1=1+%2F%2Ain der URL, $var = $_GET['var'];im Code und Bobs Onkel angeht .
CHao
183

TL; DR

mysql_real_escape_string()wird bieten keinerlei Schutz (weiterhin und könnte Ihre Daten munge) , wenn:

  • Der NO_BACKSLASH_ESCAPESSQL-Modus von MySQL ist aktiviert (was möglicherweise der Fall ist, es sei denn, Sie wählen bei jeder Verbindung explizit einen anderen SQL-Modus aus ). und

  • Ihre SQL-Zeichenfolgenliterale werden in Anführungszeichen gesetzt ".

Dies wurde als Fehler # 72458 abgelegt und in MySQL v5.7.6 behoben (siehe Abschnitt " The Saving Grace " weiter unten).

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 ...

mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"'); // could already be set
$var = mysql_real_escape_string('" OR 1=1 -- ');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

Dadurch werden alle Datensätze aus der testTabelle zurückgegeben. Eine Dissektion:

  1. Auswählen eines SQL-Modus

    mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');

    Wie unter String Literals dokumentiert :

    Es gibt verschiedene Möglichkeiten, Anführungszeichen in eine Zeichenfolge aufzunehmen:

    • Ein " '" in einem mit " '" zitierten String kann als " ''" geschrieben werden.

    • Ein " "" in einem mit " "" zitierten String kann als " """ geschrieben werden.

    • Vor dem Anführungszeichen steht ein Escape-Zeichen (" \").

    • Ein " '" in einer mit " "" zitierten Zeichenfolge erfordert keine besondere Behandlung und muss nicht verdoppelt oder maskiert werden. Ebenso bedarf „ "“ in einer mit „ '“ zitierten Zeichenfolge keiner besonderen Behandlung.

    Wenn der SQL-Modus des Servers Folgendes enthält NO_BACKSLASH_ESCAPES, ist die dritte dieser Optionen - der übliche Ansatz von mysql_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.

  2. Die Nutzlast

    " OR 1=1 -- 

    Die Nutzlast initiiert diese Injektion buchstäblich mit dem "Charakter. Keine besondere Kodierung. Keine Sonderzeichen. Keine seltsamen Bytes.

  3. mysql_real_escape_string ()

    $var = mysql_real_escape_string('" OR 1=1 -- ');

    Zum Glück wird mysql_real_escape_string()der SQL-Modus überprüft und sein Verhalten entsprechend angepasst. Siehe libmysql.c:

    ulong STDCALL
    mysql_real_escape_string(MYSQL *mysql, char *to,const char *from,
                 ulong length)
    {
      if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES)
        return escape_quotes_for_mysql(mysql->charset, to, 0, from, length);
      return escape_string_for_mysql(mysql->charset, to, 0, from, length);
    }

    Daher wird eine andere zugrunde liegende Funktion escape_quotes_for_mysql()aufgerufen, wenn der NO_BACKSLASH_ESCAPESSQL-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. Siehe charset.c:

    /*
      Escape apostrophes by doubling them up
    
    // [ deletia 839-845 ]
    
      DESCRIPTION
        This escapes the contents of a string by doubling up any apostrophes that
        it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
        effect on the server.
    
    // [ deletia 852-858 ]
    */
    
    size_t escape_quotes_for_mysql(CHARSET_INFO *charset_info,
                                   char *to, size_t to_length,
                                   const char *from, size_t length)
    {
    // [ deletia 865-892 ]
    
        if (*from == '\'')
        {
          if (to + 2 > to_end)
          {
            overflow= TRUE;
            break;
          }
          *to++= '\'';
          *to++= '\'';
        }

    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 $varbleibt genau das gleiche wie das Argument, die zur Verfügung gestellt wurde mysql_real_escape_string()-es ist , als ob kein Entkommen stattgefunden hat überhaupt .

  4. Die Abfrage

    mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

    Die gerenderte Abfrage ist eine Art Formalität:

    SELECT * FROM test WHERE name = "" OR 1=1 -- " LIMIT 1

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 nicht mysqli::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. Im NO_BACKSLASH_ESCAPESModus 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_ESCAPESIn 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 z addslashes().

Außerdem wird der SQL-Modus einer neuen Verbindung vom Server entsprechend seiner Konfiguration festgelegt (welche aSUPER 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_ESCAPESauch 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 :

Funktionalität hinzugefügt oder geändert

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). ::

mysql_set_charset($charset);
mysql_query("SET SQL_MODE=''");
$var = mysql_real_escape_string('" OR 1=1 /*');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

... weil wir explizit einen SQL-Modus ausgewählt haben, der nicht enthalten ist NO_BACKSLASH_ESCAPES.

mysql_set_charset($charset);
$var = mysql_real_escape_string("' OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

... weil wir unser String-Literal in einfache Anführungszeichen setzen.

$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(["' OR 1=1 /*"]);

... 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). .

$var  = $pdo->quote("' OR 1=1 /*");
$stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");

... 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 .

$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "' OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

... weil von MySQLi vorbereitete Anweisungen sicher sind.

Einpacken

Also, wenn Sie:

  • Verwenden Sie native vorbereitete Anweisungen

ODER

  • Verwenden Sie MySQL v5.7.6 oder höher

ODER

  • in zusätzlich eine der Lösungen in ircmaxell Zusammenfassung, die Verwendung mindestens eines zu beschäftigen:

    • PDO;
    • String-Literale in einfachen Anführungszeichen; oder
    • Ein explizit festgelegter SQL-Modus, der nicht enthalten ist NO_BACKSLASH_ESCAPES

... dann sollten Sie absolut sicher sein (Schwachstellen außerhalb des Bereichs, in dem Zeichenfolgen ausgeblendet werden).

eggyal
quelle
10
TL; DR wäre also wie folgt: "Es gibt einen MySQL-Servermodus NO_BACKSLASH_ESCAPES, der eine Injektion verursachen kann, wenn Sie keine einfachen Anführungszeichen verwenden.
Ihr gesunder Menschenverstand
Ich kann nicht auf bugs.mysql.com/bug.php?id=72458 zugreifen . Ich bekomme nur eine Seite mit Zugriffsverweigerung. Ist es aus Sicherheitsgründen vor der Öffentlichkeit verborgen? Verstehe ich aus dieser Antwort auch richtig, dass Sie die Sicherheitslücke entdecken? Wenn ja, herzlichen Glückwunsch.
Mark Amery
1
Leute sollten überhaupt nicht "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 Sie ANSI_QUOTESin den Modus aufnehmen, um die
Zerbrochenheit des Zitats
2
@DanAllen: Meine Antwort war etwas breiter, da Sie diesen speziellen Fehler durch die Funktion von PDO vermeiden können - aber vorbereitete Anweisungen sind eine viel sicherere und angemessenere Möglichkeit, eine Injektion im Allgemeinen zu vermeiden. Wenn Sie nicht entkoppelte Variablen direkt in Ihr SQL verkettet haben, sind Sie natürlich anfällig für Injektionen, unabhängig davon, welche Methoden Sie danach verwenden. quote()
Eggyal
1
@eggyall: Unser System basiert auf dem zweiten sicheren Beispiel oben. Es gibt Fehler, bei denen mysql_real_escape_string weggelassen wurde. Das Beheben dieser Probleme im Notfallmodus scheint der umsichtige Weg zu sein, in der Hoffnung, dass wir vor den Korrekturen nicht mit Atomwaffen behandelt werden. Meine Begründung ist, dass die Umstellung auf vorbereitete Aussagen ein viel längerer Prozess sein wird, der danach folgen muss. Ist der Grund, warum vorbereitete Aussagen sicherer sind, die Tatsache, dass Fehler keine Schwachstellen verursachen? Mit anderen Worten, ist das obige zweite Beispiel korrekt implementiert? Ist das genauso sicher wie vorbereitete Aussagen?
DanAllen
18

Nun, es gibt nichts, was das wirklich passieren kann, außer %Platzhalter. Es könnte gefährlich sein, wenn Sie eine LIKEAnweisung 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);

Slava
quelle
2
+1, aber die Platzhalter sind für die LIKE-Klausel, nicht für einfache Gleichheit.
Dor
7
Inwiefern betrachten Sie einen einfachen Ersatz more efficientals 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.)
MatBailie
7
@Slava: Sie beschränken Benutzernamen und Passwörter effektiv nur auf Wortzeichen. Die meisten Leute, die etwas über Sicherheit wissen, würden dies als schlechte Idee betrachten, da dies den Suchraum erheblich verkleinert. Natürlich würden sie es auch für eine schlechte Idee halten, Klartext-Passwörter in der Datenbank zu speichern, aber wir müssen das Problem nicht verschärfen. :)
CHao
2
@cHao, mein Vorschlag betrifft nur Logins. Natürlich müssen Sie keine Passwörter filtern. Leider ist dies in meiner Antwort nicht eindeutig angegeben. Aber eigentlich könnte das eine gute Idee sein. Die Verwendung von "Stein ignoranter Baumraum" anstelle von schwer zu merkendem und zu tippendem "a4üua3! @V \" ä90; 8f "wäre viel schwieriger zu erzwingen. Selbst wenn Sie ein Wörterbuch mit beispielsweise 3000 Wörtern verwenden, um Ihnen zu helfen, wissen Sie Sie haben genau 4 Wörter verwendet - das wären immer noch ungefähr 3,3 * 10 ^ 12 Kombinationen. :)
Slava
2
@ Slava: Ich habe diese Idee schon einmal gesehen. siehe xkcd.com/936 . Das Problem ist, dass die Mathematik es nicht ganz bestätigt. Ihr Beispielkennwort mit 17 Zeichen hätte 96 ^ 17 Möglichkeiten, und das ist, wenn Sie die Umlaute vergessen und sich auf druckbares ASCII beschränkt haben. Das ist ungefähr 4.5x10 ^ 33. Wir sprechen buchstäblich von einer Milliarde Billionen Mal mehr Arbeit für rohe Gewalt. Sogar ein 8-stelliges ASCII-Passwort hätte 7,2x10 ^ 15 Möglichkeiten - dreitausendmal mehr.
CHao