Warum geben nicht parametrisierte Abfragen nicht nur einen Fehler zurück?


22

SQL-Injection ist ein sehr schwerwiegendes Sicherheitsproblem, zum großen Teil, weil es so einfach ist, es falsch zu machen: Die offensichtliche, intuitive Art, eine Abfrage mit Benutzereingaben zu erstellen, macht Sie anfällig, und die richtige Art, dies zu verringern, erfordert, dass Sie über Parameter informiert sind Abfragen und SQL-Injection zuerst.

Mir scheint, dass der offensichtliche Weg, dies zu beheben, darin besteht, die offensichtliche (aber falsche) Option auszuschalten: Korrigieren Sie das Datenbankmodul, sodass jede Abfrage, die hartcodierte Werte in der WHERE-Klausel anstelle von Parametern verwendet, eine nette, beschreibende zurückgibt Fehlermeldung, die Sie auffordert, stattdessen Parameter zu verwenden. Dies würde natürlich eine Deaktivierungsoption erfordern, damit Dinge wie Ad-hoc-Abfragen von Verwaltungstools weiterhin problemlos ausgeführt werden können, sie sollte jedoch standardmäßig aktiviert sein.

Wenn dies der Fall wäre, würde die SQL-Injection fast über Nacht heruntergefahren, aber meines Wissens macht dies kein RDBMS. Gibt es einen guten Grund, warum nicht?


22
bad_ideas_sql = 'SELECT title FROM idea WHERE idea.status == "bad" AND idea.user == :mwheeler'Hätte in einer einzigen Abfrage sowohl fest codierte als auch parametrisierte Werte - versuchen Sie das zu fangen! Ich denke, es gibt gültige Anwendungsfälle für solche gemischten Abfragen.
amon

6
Wie wäre es, wenn Sie Datensätze von heute auswählenSELECT * FROM jokes WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY score DESC;
Jaydee

10
@MasonWheeler sorry, ich meinte "versuche das zu erlauben". Beachten Sie, dass es perfekt parametrisiert ist und nicht unter SQL-Injection leidet. Der Datenbanktreiber kann jedoch nicht feststellen, ob das Literal "bad"wirklich ein Literal ist oder aus der Verkettung von Zeichenfolgen resultiert. Die beiden Lösungen, die ich sehe, sind, entweder SQL und andere in Strings eingebettete DSLs zu entfernen (ja, bitte) oder Sprachen zu fördern, bei denen die Verkettung von Strings ärgerlicher ist als die Verwendung parametrisierter Abfragen (umm, nein).
amon

4
und wie würde das RDBMS erkennen, ob dies zu tun ist? Es würde über Nacht unmöglich machen, über eine interaktive SQL-Eingabeaufforderung auf das RDBMS zuzugreifen. Sie könnten mit keinem Tool mehr DDL- oder DML-Befehle eingeben.
Mittwoch,

8
In gewissem Sinne können Sie dies tun: Erstellen Sie SQL-Abfragen überhaupt nicht zur Laufzeit, sondern verwenden Sie stattdessen ein ORM oder eine andere Abstraktionsschicht, mit der Sie keine SQL-Abfragen erstellen müssen. ORM verfügt nicht über die Funktionen, die Sie benötigen? Dann ist SQL eine Sprache, die für Leute gedacht ist, die SQL schreiben möchten, weshalb sie im Großen und Ganzen SQL schreiben können. Das grundlegende Problem ist, dass das dynamische Generieren von Code schwieriger ist als es aussieht, aber die Leute wollen es trotzdem und sind mit Produkten, die sie nicht zulassen, unzufrieden.
Steve Jessop

Antworten:


45

Es gibt zu viele Fälle, in denen ein Literal der richtige Ansatz ist.

Vom Standpunkt der Leistung aus gibt es Zeiten, in denen Sie Literale in Ihren Abfragen benötigen. Stellen Sie sich vor, ich habe einen Bug-Tracker, bei dem 70% der Fehler im System "geschlossen", 20% "offen", 5% "aktiv" und 5% der Fehler "aktiv" sind, sobald sie groß genug sind, um die Leistung zu beeinträchtigen % hat einen anderen Status. Ich möchte vielleicht vernünftigerweise die Abfrage haben, die alle aktiven Bugs zurückgibt

SELECT *
  FROM bug
 WHERE status = 'active'

anstatt die statusals Bindevariable zu übergeben. Ich möchte einen anderen Abfrageplan in Abhängigkeit vom übergebenen Wert für status- Ich möchte einen Tabellenscan durchführen, um die geschlossenen Bugs und einen Indexscan für die zurückzusendenstatusSpalte, um die aktiven Kredite zurückzugeben. Unterschiedliche Datenbanken und unterschiedliche Versionen haben unterschiedliche Ansätze, um (mehr oder weniger erfolgreich) derselben Abfrage die Verwendung eines unterschiedlichen Abfrageplans abhängig vom Wert der Bindungsvariablen zu ermöglichen. Dies führt jedoch in der Regel zu einer erheblichen Komplexität, die verwaltet werden muss, um die Entscheidung auszugleichen, ob ein erneutes Parsen einer Abfrage oder die Wiederverwendung eines vorhandenen Plans für einen neuen Bindevariablenwert erforderlich ist. Für einen Entwickler kann es sinnvoll sein, mit dieser Komplexität umzugehen. Oder es kann sinnvoll sein, einen anderen Pfad zu erzwingen, wenn ich mehr Informationen darüber habe, wie meine Daten aussehen werden, als der Optimierer.

Unter dem Gesichtspunkt der Codekomplexität ist es auch häufig sinnvoll, Literale in SQL-Anweisungen zu haben. Wenn Sie zum Beispiel eine zip_codeSpalte mit einer 5-stelligen Postleitzahl und manchmal weiteren 4 Stellen haben, ist es vollkommen sinnvoll, so etwas zu tun

SELECT substr( zip_code, 1, 5 ) zip,
       substr( zip_code, 7, 4 ) plus_four

anstatt 4 separate Parameter für die numerischen Werte einzugeben. Dies sind keine Dinge, die sich jemals ändern werden, sodass das Binden von Variablen nur dazu dient, den Code potenziell schwerer lesbar zu machen und das Potenzial zu schaffen, dass jemand Parameter in der falschen Reihenfolge bindet und einen Fehler verursacht.


12

SQL Injection tritt auf, wenn eine Abfrage erstellt wird, indem Text aus einer nicht vertrauenswürdigen und nicht validierten Quelle mit anderen Teilen einer Abfrage verknüpft wird. Während so etwas am häufigsten bei String-Literalen vorkommt, ist dies nicht der einzige Weg, wie es vorkommen könnte. Eine Abfrage für numerische Werte könnte eine vom Benutzer eingegebene Zeichenkette übernehmen (das sollte nur Ziffern enthalten) und verketten mit anderem Material eine Abfrage ohne die Anführungszeichen normalerweise mit Stringliterale assoziiert zu bilden; Code, der der clientseitigen Validierung zu sehr vertraut, enthält möglicherweise Feldnamen, die aus einer HTML-Abfragezeichenfolge stammen. Code, der eine SQL-Abfragezeichenfolge betrachtet, kann nicht erkennen, wie sie zusammengestellt wurde.

Wichtig ist nicht, ob eine SQL-Anweisung Zeichenfolgenliterale enthält, sondern ob eine Zeichenfolge Zeichenfolgen aus nicht vertrauenswürdigen Quellen enthält . Die Validierung hierfür sollte am besten in der Bibliothek erfolgen, die Abfragen erstellt. In C # gibt es im Allgemeinen keine Möglichkeit, Code zu schreiben, der ein Zeichenfolgenliteral zulässt, andere Arten von Zeichenfolgenausdrücken jedoch nicht zulässt. Es kann jedoch eine Codierungsregel geben, die erfordert, dass Abfragen mithilfe einer abfrageerstellenden Klasse erstellt werden Die Verkettung von Zeichenfolgen und die Übergabe einer nicht-wörtlichen Zeichenfolge an den Abfrage-Generator müssen eine solche Aktion rechtfertigen.


1
Als Näherung für "Ist es ein Literal?" Können Sie überprüfen, ob die Zeichenfolge interniert ist.
CodesInChaos

1
@CodesInChaos: Richtig, und ein solcher Test könnte für diesen Zweck genau genug sein, vorausgesetzt, jeder, der einen Grund für die Generierung einer Zeichenfolge zur Laufzeit hatte, verwendete eine Methode, die eine nicht-wörtliche Zeichenfolge akzeptierte, anstatt die zur Laufzeit generierte Zeichenfolge zu internieren und zu verwenden das (die Nicht-Literal-Zeichenfolge-Methode einen anderen Namen zu geben, würde es den Code-Überprüfern leicht machen, alle Verwendungen davon zu überprüfen).
Superkatze

Beachten Sie, dass es in C # zwar keine Möglichkeit gibt, dies jedoch in einigen anderen Sprachen möglich ist (z. B. Perls beflecktes Zeichenfolgenmodul).
Jules

Kurz gesagt, dies ist ein Client- Problem, kein Server-Problem.
Blrfl

7
SELECT count(ID)
FROM posts
WHERE deleted = false

Wenn Sie die Ergebnisse in die Fußzeile Ihres Forums einfügen möchten, müssen Sie einen Dummy-Parameter hinzufügen, um jedes Mal false zu sagen. Oder der naive Web-Programmierer sucht nach Informationen zum Deaktivieren dieser Warnung und fährt dann fort.

Jetzt können Sie sagen, dass Sie eine Ausnahme für Aufzählungen hinzufügen würden, die jedoch das Loch wieder öffnet (obwohl es kleiner ist). Ganz zu schweigen von Menschen, die erst erzogen werden müssen, um sie nicht zu benutzen varchars.

Das eigentliche Problem bei der Injektion ist die programmgesteuerte Erstellung der Abfragezeichenfolge. Die Lösung hierfür ist ein Mechanismus für gespeicherte Prozeduren und die Durchsetzung seiner Verwendung oder eine Whitelist zulässiger Abfragen.


2
Wenn Ihre Lösung für "Es ist allzu leicht, parametrisierte Abfragen zu vergessen - oder gar nicht zu wissen -," lautet "Alle daran erinnern - und an erster Stelle wissen -, gespeicherte Prozesse zu verwenden", dann Sie fehlt der ganze sinn der frage.
Maurer Wheeler

5
Ich habe bei meiner Arbeit SQL-Injection über gespeicherte Prozeduren gesehen. Es stellt sich heraus, dass gespeicherte Prozeduren für alles BAD sind. Es gibt immer 0,5%, die echte dynamische Abfragen sind (Sie können nicht eine ganze where-Klausel parametrisieren, geschweige denn einen Tabellen-Join).
Joshua

Im Beispiel in dieser Antwort , die Sie ersetzen könnten deleted = falsemit NOT deleted, die die wörtliche vermeidet. Aber der Punkt ist im Allgemeinen gültig.
Psmears

5

TL; DR : Sie müssten alle Literale einschränken , nicht nur die in WHEREKlauseln. Aus Gründen, aus denen sie dies nicht tun, kann die Datenbank von anderen Systemen entkoppelt bleiben.

Erstens ist Ihre Prämisse fehlerhaft. Sie möchten nur WHEREKlauseln einschränken , aber dies ist nicht der einzige Ort, an dem Benutzereingaben möglich sind. Beispielsweise,

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Dies ist gleichermaßen anfällig für SQL-Injection:

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) FROM item; DROP TABLE user_info; SELECT CASE(WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Sie können also nicht nur Literale in der Liste einschränken WHERE Klausel . Sie müssen alle Literale einschränken .

Jetzt bleibt die Frage: "Warum überhaupt Literale zulassen?" Beachten Sie Folgendes: Während relationale Datenbanken unter einer Anwendung verwendet werden, die in einer anderen Sprache geschrieben ist, ist es nicht erforderlich, dass Sie Anwendungscode verwenden, um die Datenbank zu verwenden. Und hier haben wir eine Antwort: Sie brauchen Literale, um Code zu schreiben. Die einzige Alternative wäre, den gesamten Code in einer von der Datenbank unabhängigen Sprache zu schreiben. Wenn Sie sie haben, können Sie "Code" (SQL) direkt in die Datenbank schreiben. Dies ist eine wertvolle Entkopplung und ohne Literale nicht möglich. (Versuchen Sie, irgendwann ohne Literale in Ihrer Lieblingssprache zu schreiben. Ich bin sicher, Sie können sich vorstellen, wie schwierig das sein würde.)

In der Grundgesamtheit von Wertelisten- / Nachschlagetabellen werden häufig Literale verwendet:

CREATE TABLE user_roles (role_id INTEGER, role_name VARCHAR(50));
INSERT INTO user_roles (1, 'normal');
INSERT INTO user_roles (2, 'admin');
INSERT INTO user_roles (3, 'banned');

Ohne sie müssten Sie Code in einer anderen Programmiersprache schreiben , um diese Tabelle zu füllen. Die Möglichkeit, dies direkt in SQL zu tun, ist wertvoll .

Dann bleibt uns noch eine Frage: Warum machen Programmiersprachen-Client-Bibliotheken das dann nicht? Und hier haben wir eine sehr einfache Antwort: Sie hätten den gesamten Datenbankparser für jede unterstützte Version der Datenbank erneut implementiert . Warum? Weil es keinen anderen Weg gibt, um zu garantieren, dass Sie jedes Wort gefunden haben. Reguläre Ausdrücke sind nicht genug. Zum Beispiel: Dies enthält 4 separate Literale in PostgreSQL:

SELECT $lit1$I'm a literal$lit1$||$lit2$I'm another literal $$ with nested string delimiters$$ $lit2$||'I''m ANOTHER literal'||$$I'm the last literal$$;

Dies zu versuchen, wäre ein Alptraum für die Wartung, zumal sich die gültige Syntax häufig zwischen den Hauptversionen von Datenbanken ändert.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.