it-swarm-eu.dev

Sind vorbereitete Anweisungen 100% sicher gegen SQL-Injection?

Sind vorbereitete Anweisungen tatsächlich 100% sicher gegen SQL-Injection, vorausgesetzt, alle vom Benutzer bereitgestellten Parameter werden als abfragegebundene Parameter übergeben?

Immer wenn ich Leute sehe, die das alte mysql_ Funktionen auf StackOverflow (was leider viel zu häufig ist) Ich sage den Leuten im Allgemeinen, dass vorbereitete Anweisungen Chuck Norris (oder Jon Skeet) von SQL-Injection-Sicherheitsmaßnahmen sind.

Ich habe jedoch noch nie eine Dokumentation gesehen, in der kategorisch angegeben ist: "das ist 100% sicher". Ich verstehe sie so, dass sie die Abfragesprache und die Parameter bis zur Eingangstür des Servers trennen, die sie dann als separate Entitäten behandelt.

Bin ich in dieser Annahme richtig?

80
Polynomial

100% ige Garantie vor SQL-Injection? Ich werde es nicht bekommen (von mir).

Im Prinzip könnte Ihre Datenbank (oder Bibliothek in Ihrer Sprache, die mit der Datenbank interagiert) vorbereitete Anweisungen mit gebundenen Parametern auf unsichere Weise implementieren, die für eine Art fortgeschrittenen Angriff anfällig sind, z. B. das Ausnutzen von Pufferüberläufen oder das Beenden von nullterminierenden Zeichen im Benutzer. bereitgestellte Zeichenfolgen usw. (Sie könnten argumentieren, dass diese Arten von Angriffen nicht als SQL-Injection bezeichnet werden sollten, da sie sich grundlegend unterscheiden; aber das ist nur Semantik).

Ich habe noch nie von einem dieser Angriffe auf vorbereitete Anweisungen auf reale Datenbanken im Feld gehört und empfehle dringend, gebundene Parameter zu verwenden, um die SQL-Injection zu verhindern. Ohne gebundene Parameter oder Eingabesanierung ist es trivial, SQL-Injection durchzuführen. Mit nur Eingangshygiene ist es ziemlich oft möglich, eine dunkle Lücke um die Hygiene herum zu finden.

Mit gebundenen Parametern wird Ihr SQL-Abfrageausführungsplan im Voraus ermittelt, ohne auf Benutzereingaben angewiesen zu sein. Dies sollte eine SQL-Injection nicht möglich machen (da eingefügte Anführungszeichen, Kommentarsymbole usw. nur in die bereits kompilierte SQL-Anweisung eingefügt werden).

Das einzige Argument gegen die Verwendung vorbereiteter Anweisungen ist, dass Ihre Datenbank Ihre Ausführungspläne abhängig von der tatsächlichen Abfrage optimieren soll. Die meisten Datenbanken sind bei vollständiger Abfrage intelligent genug, um einen optimalen Ausführungsplan zu erstellen. Wenn die Abfrage beispielsweise einen großen Prozentsatz der Tabelle zurückgibt, möchte sie die gesamte Tabelle durchlaufen, um Übereinstimmungen zu finden. Wenn es nur ein paar Datensätze gibt, können Sie eine indexbasierte Suche durchführen [1] .

BEARBEITEN: Antworten auf zwei Kritikpunkte (die für Kommentare etwas zu lang sind):

Erstens, wie andere angemerkt haben, kompiliert jede relationale Datenbank, die vorbereitete Anweisungen und gebundene Parameter unterstützt, die vorbereitete Anweisung nicht unbedingt vor, ohne den Wert der gebundenen Parameter zu betrachten. Viele Datenbanken tun dies normalerweise, aber es ist auch möglich, dass Datenbanken die Werte der gebundenen Parameter betrachten, wenn sie den Ausführungsplan herausfinden. Dies ist kein Problem, da die Struktur der vorbereiteten Anweisung mit getrennten gebundenen Parametern es der Datenbank erleichtert, die SQL-Anweisung (einschließlich SQL-Schlüsselwörter) sauber von Daten in gebundenen Parametern zu unterscheiden (wobei in einem gebundenen Parameter nichts enthalten ist) als SQL-Schlüsselwort interpretiert). Dies ist nicht möglich, wenn SQL-Anweisungen aus der Verkettung von Zeichenfolgen erstellt werden, bei denen Variablen und SQL-Schlüsselwörter gemischt werden.

Zweitens, wie die andere Antwort hervorhebt, verhindert die Verwendung gebundener Parameter beim Aufrufen einer SQL-Anweisung an einem Punkt in einem Programm sicher die SQL-Injektion, wenn dieser Aufruf der obersten Ebene ausgeführt wird. Wenn Sie jedoch an anderer Stelle in der Anwendung SQL-Injection-Schwachstellen haben (z. B. in benutzerdefinierten Funktionen, die Sie in Ihrer Datenbank gespeichert und ausgeführt haben und die Sie nicht sicher geschrieben haben, um SQL-Abfragen durch Verkettung von Zeichenfolgen zu erstellen).

Wenn Sie beispielsweise in Ihrer Anwendung Pseudocode wie folgt geschrieben haben:

sql_stmt = "SELECT create_new_user(?, ?)"
params = (email_str, hashed_pw_str)
db_conn.execute_with_params(sql_stmt, params)

Beim Ausführen dieser SQL-Anweisung auf Anwendungsebene kann keine SQL-Injection erfolgen. Wenn die benutzerdefinierte Datenbankfunktion jedoch unsicher geschrieben wurde (unter Verwendung der PL/pgSQL-Syntax):

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
DECLARE
   sql_str TEXT;
BEGIN
     sql_str := 'INSERT INTO users VALUES (' || email_str || ', ' || hashed_pw_str || ');'
     EXECUTE sql_str;
END;
$$
LANGUAGE plpgsql;

dann wären Sie anfällig für SQL-Injection-Angriffe, da eine SQL-Anweisung ausgeführt wird, die über eine Zeichenfolgenverkettung erstellt wird und die SQL-Anweisung mit Zeichenfolgen mischt, die die Werte benutzerdefinierter Variablen enthalten.

Das heißt, wenn Sie nicht versuchen, unsicher zu sein (Erstellen von SQL-Anweisungen über Verkettung von Zeichenfolgen), ist es natürlicher, die benutzerdefinierten Anweisungen auf sichere Weise zu schreiben:

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
BEGIN
     INSERT INTO users VALUES (email_str, hashed_pw_str);
END;
$$
LANGUAGE plpgsql;

Wenn Sie wirklich das Bedürfnis hatten, eine SQL-Anweisung aus einer Zeichenfolge in einer benutzerdefinierten Funktion zu erstellen, können Sie Datenvariablen auch innerhalb einer benutzerdefinierten Funktion auf dieselbe Weise wie gespeicherte_Prozeduren/gebundene Parameter von der SQL-Anweisung trennen. Zum Beispiel in PL/pgSQL :

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
DECLARE
   sql_str TEXT;
BEGIN
     sql_str := 'INSERT INTO users VALUES($1, $2)'
     EXECUTE sql_str USING email_str, hashed_pw_str;
END;
$$
LANGUAGE plpgsql;

Die Verwendung vorbereiteter Anweisungen ist also vor SQL-Injection sicher, solange Sie nicht nur unsichere Dinge an anderer Stelle ausführen (dh SQL-Anweisungen durch Verkettung von Zeichenfolgen erstellen).

54
dr jimbob

100% sicher? Nicht einmal annähernd. Gebundene Parameter (in Bezug auf Anweisungen oder auf andere Weise vorbereitet) können eine Klasse der SQL-Injection-Sicherheitsanfälligkeit (vorausgesetzt, es werden keine Datenbankfehler und eine vernünftige Implementierung vorausgesetzt) ​​zu 100% wirksam verhindern ). In keiner Weise verhindern sie andere Klassen. Beachten Sie, dass PostgreSQL (meine Datenbank der Wahl) die Option hat, Parameter an Ad-hoc-Anweisungen zu binden, wodurch ein Roundtrip in Bezug auf vorbereitete Anweisungen erspart wird, wenn Sie bestimmte Funktionen dieser Anweisungen nicht benötigen.

Sie müssen sich vorstellen, dass viele große, komplexe Datenbanken Programme für sich sind. Die Komplexität dieser Programme ist sehr unterschiedlich, und die SQL-Injection muss in den Programmierroutinen beachtet werden. Solche Routinen umfassen Trigger, benutzerdefinierte Funktionen, gespeicherte Prozeduren und dergleichen. Es ist nicht immer offensichtlich, wie diese Dinge von einer Anwendungsebene aus interagieren, da viele gute Datenbankprogramme einen gewissen Grad an Abstraktion zwischen der Anwendungszugriffsebene und der Speicherebene bieten.

Bei gebundenen Parametern wird der Abfragebaum analysiert, und zumindest in PostgreSQL werden die Daten zum Planen betrachtet. Der Plan wird ausgeführt. Mit vorbereiteten Anweisungen wird der Plan gespeichert, sodass Sie denselben Plan immer wieder mit unterschiedlichen Daten ausführen können (dies kann das sein, was Sie möchten oder nicht). Der Punkt ist jedoch, dass ein Parameter mit gebundenen Parametern nichts in den Analysebaum einfügen kann. Daher wird dieses Problem der SQL-Injection-Klasse ordnungsgemäß behoben.

Jetzt müssen wir protokollieren, wer was in eine Tabelle schreibt. Daher fügen wir Trigger und benutzerdefinierte Funktionen hinzu, um die Logik dieser Trigger zu kapseln. Diese werfen neue Probleme auf. Wenn Sie über dynamisches SQL verfügen, müssen Sie sich dort um die SQL-Injection kümmern. Die Tabellen, in die sie schreiben, haben möglicherweise eigene Auslöser und so weiter. In ähnlicher Weise kann ein Funktionsaufruf eine andere Abfrage aufrufen, die einen anderen Funktionsaufruf usw. aufruft. Jedes davon ist unabhängig vom Hauptbaum geplant.

Dies bedeutet, dass wenn ich eine Abfrage mit einem gebundenen Parameter wie foo'; drop user postgres; -- dann kann es den Abfragebaum der obersten Ebene nicht direkt implizieren und einen weiteren Befehl zum Löschen des Postgres-Benutzers hinzufügen. Wenn diese Abfrage jedoch eine andere Funktion direkt aufruft oder nicht, ist es möglich, dass irgendwo auf der ganzen Linie eine Funktion anfällig ist und der Postgres-Benutzer gelöscht wird. Die gebundenen Parameter boten keinen Schutz für sekundäre Abfragen. Diese sekundären Abfragen müssen sicherstellen, dass sie so weit wie möglich auch gebundene Parameter verwenden, und wo nicht, müssen geeignete Anführungsroutinen verwendet werden.

Das Kaninchenloch geht tief.

Übrigens für eine Frage zum Stapelüberlauf, bei der dieses Problem offensichtlich ist, siehe https://stackoverflow.com/questions/37878426/conditional-where-expression-in-dynamic-query/37878574#37878574

Eine problematischere Version (aufgrund der Einschränkung der Dienstprogrammanweisungen) unter https://stackoverflow.com/questions/38016764/perform-create-index-in-plpgsql-doesnt-run/38021245#38021245

25
Chris Travers