Wie können vorbereitete Anweisungen vor SQL-Injection-Angriffen schützen?

172

Wie helfen uns vorbereitete Anweisungen , SQL-Injection- Angriffe zu verhindern?

Wikipedia sagt:

Vorbereitete Anweisungen sind widerstandsfähig gegen SQL-Injection, da Parameterwerte, die später mit einem anderen Protokoll übertragen werden, nicht korrekt maskiert werden müssen. Wenn die ursprüngliche Anweisungsvorlage nicht von externen Eingaben abgeleitet ist, kann keine SQL-Injection durchgeführt werden.

Ich kann den Grund nicht gut sehen. Was wäre eine einfache Erklärung in einfachem Englisch und einigen Beispielen?

Aan
quelle

Antworten:

290

Die Idee ist sehr einfach: Die Abfrage und die Daten werden separat an den Datenbankserver gesendet .
Das ist alles.

Die Wurzel des SQL-Injection-Problems liegt in der Mischung von Code und Daten.

Tatsächlich ist unsere SQL-Abfrage ein legitimes Programm . Und wir erstellen ein solches Programm dynamisch und fügen einige Daten im laufenden Betrieb hinzu. Daher können die Daten den Programmcode stören und sogar ändern, wie jedes SQL-Injection-Beispiel zeigt (alle Beispiele in PHP / MySQL):

$expected_data = 1;
$query = "SELECT * FROM users where id=$expected_data";

erzeugt eine regelmäßige Abfrage

SELECT * FROM users where id=1

während dieser Code

$spoiled_data = "1; DROP TABLE users;"
$query        = "SELECT * FROM users where id=$spoiled_data";

erzeugt eine böswillige Sequenz

SELECT * FROM users where id=1; DROP TABLE users;

Dies funktioniert, weil wir die Daten direkt zum Programmkörper hinzufügen und sie Teil des Programms werden, sodass die Daten das Programm ändern können. Abhängig von den übergebenen Daten wird entweder eine reguläre Ausgabe oder eine Tabelle usersgelöscht.

Während wir bei vorbereiteten Anweisungen unser Programm nicht ändern, bleibt es intakt.
Das ist der Punkt.

Wir senden zuerst ein Programm an den Server

$db->prepare("SELECT * FROM users where id=?");

Dabei werden die Daten durch eine Variable ersetzt, die als Parameter oder Platzhalter bezeichnet wird.

Beachten Sie, dass genau dieselbe Abfrage ohne Daten an den Server gesendet wird! Und dann senden wir die Daten mit der zweiten Anforderung, die im Wesentlichen von der Abfrage selbst getrennt ist:

$db->execute($data);

Es kann also unser Programm nicht ändern und keinen Schaden anrichten.
Ganz einfach - nicht wahr?

Das einzige, was ich hinzufügen muss, das in jedem Handbuch immer weggelassen wurde:

Vorbereitete Anweisungen können nur Datenliterale schützen , können jedoch nicht mit anderen Abfrageteilen verwendet werden.
Wenn wir also beispielsweise einen dynamischen Bezeichner hinzufügen müssen - beispielsweise einen Feldnamen -, können uns vorbereitete Anweisungen nicht weiterhelfen. Ich habe die Angelegenheit kürzlich erklärt , damit ich mich nicht wiederhole.

Ihr gesunder Menschenverstand
quelle
2
"Zum Beispiel verwenden PDO standardmäßig keine vorbereiteten Anweisungen" - dies ist nicht genau richtig, da PDO vorbereitete Anweisungen nur für Treiber emuliert, die diese Funktion nicht unterstützen.
Pinepain
3
@ zaq178miami: "PDO emuliert vorbereitete Anweisungen nur für Treiber, die die Funktion nicht unterstützen" - ist nicht genau richtig. MySQL unterstützt seit einiger Zeit vorbereitete Anweisungen. Der PDO-Treiber hat auch. Trotzdem wurden MySQL-Abfragen standardmäßig von PDO vorbereitet, als ich das letzte Mal nachgesehen habe.
CHao
9
Was ist anders zwischen $spoiled_data = "1; DROP TABLE users;"-> $query = "SELECT * FROM users where id=$spoiled_data";im Vergleich zu: $db->prepare("SELECT * FROM users where id=?");-> $data = "1; DROP TABLE users;"-> $db->execute($data);. Werden sie nicht dasselbe tun?
Juha Untinen
14
@ Juha Untinen Die Daten können alles sein. Die Daten werden nicht analysiert. Das ist DATA nicht der Befehl. Selbst wenn die $ data SQL-Befehle enthalten, werden sie nicht ausgeführt. Wenn die ID eine Zahl ist, generiert der Zeichenfolgeninhalt einen Bericht oder den Wert Null.
Soley
21

Hier ist SQL zum Einrichten eines Beispiels:

CREATE TABLE employee(name varchar, paymentType varchar, amount bigint);

INSERT INTO employee VALUES('Aaron', 'salary', 100);
INSERT INTO employee VALUES('Aaron', 'bonus', 50);
INSERT INTO employee VALUES('Bob', 'salary', 50);
INSERT INTO employee VALUES('Bob', 'bonus', 0);

Die Inject-Klasse ist anfällig für SQL-Injection. Die Abfrage wird dynamisch zusammen mit Benutzereingaben eingefügt. Die Abfrage sollte Informationen über Bob anzeigen. Entweder Gehalt oder Bonus, basierend auf Benutzereingaben. Der böswillige Benutzer manipuliert jedoch die Eingabe, die die Abfrage beschädigt, indem er das Äquivalent eines 'oder true' zur where-Klausel anheftet, sodass alles zurückgegeben wird, einschließlich der Informationen über Aaron, die ausgeblendet werden sollten.

import java.sql.*;

public class Inject {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=user&password=pwd";
        Connection conn = DriverManager.getConnection(url);

        Statement stmt = conn.createStatement();
        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='" + args[0] + "'";
        System.out.println(sql);
        ResultSet rs = stmt.executeQuery(sql);

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

Wenn Sie dies ausführen, ist der erste Fall bei normaler Verwendung und der zweite bei böswilliger Injektion:

c:\temp>java Inject salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary'
salary 50

c:\temp>java Inject "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary' OR 'a'!='b'
salary 100
bonus 50
salary 50
bonus 0

Sie sollten Ihre SQL-Anweisungen nicht mit einer Zeichenfolgenverkettung von Benutzereingaben erstellen. Es ist nicht nur anfällig für Injection, sondern hat auch Auswirkungen auf das Caching auf dem Server (die Anweisung ändert sich, sodass es weniger wahrscheinlich ist, dass ein SQL-Anweisungscache getroffen wird, während im Bindungsbeispiel immer dieselbe Anweisung ausgeführt wird).

Hier ist ein Beispiel für die Bindung, um diese Art der Injektion zu vermeiden:

import java.sql.*;

public class Bind {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=postgres&password=postgres";
        Connection conn = DriverManager.getConnection(url);

        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?";
        System.out.println(sql);

        PreparedStatement stmt = conn.prepareStatement(sql);
        stmt.setString(1, args[0]);

        ResultSet rs = stmt.executeQuery();

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

Wenn Sie dies mit derselben Eingabe wie im vorherigen Beispiel ausführen, funktioniert der Schadcode nicht, da kein Zahlungstyp vorhanden ist, der dieser Zeichenfolge entspricht:

c:\temp>java Bind salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?
salary 50

c:\temp>java Bind "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?
Glenn
quelle
Hat die Verwendung einer vorbereiteten Anweisung aus dem Programm, das eine Verbindung zur Datenbank herstellt, den gleichen Effekt wie die Verwendung einer vorbereiteten Anweisung, die Teil der Datenbank ist? Zum Beispiel hat Postgres eine eigene vorbereitete Anweisung und würde die Verwendung dieser Anweisung die SQL-Injection verhindern? postgresql.org/docs/9.2/static/sql-prepare.html
Celeritas
@Celeritas Ich habe keine endgültige Antwort für Postgresql. Wenn Sie sich die Dokumente ansehen, scheint der Effekt der gleiche zu sein. PREPAREErstellt eine feste benannte Anweisung, die bereits analysiert wurde (dh die Anweisung wird sich unabhängig von der Eingabe nicht mehr ändern), während EXECUTEdie benannte Anweisung ausgeführt wird, die die Parameter bindet. Da es PREPAREnur eine Sitzungsdauer hat, sieht es wirklich so aus, als ob es aus Leistungsgründen gedacht ist, nicht um die Injektion über psql-Skripte zu verhindern. Für den psql-Zugriff können Berechtigungen für gespeicherte Prozeduren erteilt und die Parameter in den Prozessen gebunden werden.
Glenn
@Celeritas Ich habe den obigen Code mit PostgreSQL 11.1 auf x86_64 ausprobiert und das obige SQLi-Beispiel hat funktioniert.
Krishna Pandey
15

Grundsätzlich werden bei vorbereiteten Anweisungen die von einem potenziellen Hacker eingehenden Daten als Daten behandelt - und es gibt keine Möglichkeit, sie mit Ihrer Anwendungs-SQL zu vermischen und / oder als SQL zu interpretieren (was passieren kann, wenn übergebene Daten direkt in Ihre Daten eingegeben werden) Anwendung SQL).

Dies liegt daran, dass vorbereitete Anweisungen die SQL-Abfrage zuerst "vorbereiten", um einen effizienten Abfrageplan zu finden, und die tatsächlichen Werte senden, die vermutlich später aus einem Formular eingehen - zu diesem Zeitpunkt wird die Abfrage tatsächlich ausgeführt.

Weitere tolle Infos hier:

Vorbereitete Anweisungen und SQL Injection

Jose
quelle
6

Ich las die Antworten durch und hatte immer noch das Bedürfnis, den entscheidenden Punkt hervorzuheben, der die Essenz vorbereiteter Aussagen beleuchtet. Betrachten Sie zwei Möglichkeiten, um Ihre Datenbank abzufragen, wenn Benutzereingaben betroffen sind:

Naiver Ansatz

Man verkettet Benutzereingaben mit einer partiellen SQL-Zeichenfolge, um eine SQL-Anweisung zu generieren. In diesem Fall kann der Benutzer schädliche SQL-Befehle einbetten, die dann zur Ausführung an die Datenbank gesendet werden.

String SQLString = "SELECT * FROM CUSTOMERS WHERE NAME='"+userInput+"'"

Zum Beispiel können böswillige Benutzereingaben dazu führen, SQLStringdass sie gleich sind"SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'

SQLStringEnthält aufgrund des böswilligen Benutzers 2 Anweisungen, wobei die 2. ( "DROP TABLE CUSTOMERS") Schaden anrichtet.

Vorbereitete Aussagen

In diesem Fall wird die Benutzereingabe aufgrund der Trennung von Abfrage und Daten niemals als SQL-Anweisung behandelt und daher niemals ausgeführt . Aus diesem Grund würde jeder injizierte schädliche SQL-Code keinen Schaden anrichten. Das "DROP TABLE CUSTOMERS"würde also im obigen Fall niemals ausgeführt werden.

Kurz gesagt, mit vorbereiteten Anweisungen wird bösartiger Code, der über Benutzereingaben eingeführt wird, nicht ausgeführt!

N.Vegeta
quelle
"Ja wirklich?" Die akzeptierte Antwort sagt nicht genau das?
Ihr gesunder Menschenverstand
@Ihr gesunder Menschenverstand Die akzeptierte Antwort enthält viele wertvolle Informationen, aber ich habe mich gefragt, was die Implementierungsdetails für die Trennung von Daten und Abfrage bedeuten. Während die Konzentration auf den Punkt, dass die böswillig injizierten Daten (falls vorhanden) niemals ausgeführt werden würden, den Nagel auf den Kopf trifft.
N.Vegeta
Und welche "Implementierungsdetails" sind in Ihrer Antwort enthalten, die dort nicht vorhanden sind?
Ihr gesunder Menschenverstand
Wenn Sie versuchen zu sehen, woher ich komme, werden Sie feststellen, dass mein Punkt wie folgt lautet: Der kurze Wunsch, die Implementierungsdetails zu sehen, ergab sich aus der Notwendigkeit, den expliziten Grund zu verstehen, warum die böswilligen Benutzereingaben keine verursachen Schaden. Es ist nicht so sehr notwendig, die Implementierungsdetails zu sehen. Aus diesem Grund wird die Nachricht nach Hause gesendet, wenn erkannt wird, dass die Implementierungsdetails so waren, dass zu keinem Zeitpunkt böswillig eingegebenes SQL ausgeführt wird. Ihre Antwort beantwortet die Frage, wie (wie gewünscht)?, Aber ich würde mir vorstellen, dass andere Leute (wie ich) mit einer prägnanten Antwort auf das Warum zufrieden wären.
N.Vegeta
Betrachten Sie dies als eine Bereicherung, die den Kern verdeutlicht, und nicht als implizite Kritik (in letzter Zeit wurde klar, wer der Autor der akzeptierten Antwort war).
N.Vegeta
5

Wenn Sie eine vorbereitete Anweisung erstellen und an das DBMS senden, wird sie als SQL-Abfrage zur Ausführung gespeichert.

Sie binden Ihre Daten später so an die Abfrage, dass das DBMS diese Daten als Abfrageparameter für die Ausführung (Parametrisierung) verwendet. Das DBMS verwendet die von Ihnen gebundenen Daten nicht als Ergänzung zur bereits kompilierten SQL-Abfrage. Es sind einfach die Daten.

Dies bedeutet, dass es grundsätzlich unmöglich ist, eine SQL-Injection mit vorbereiteten Anweisungen durchzuführen. Die Art der vorbereiteten Anweisungen und ihre Beziehung zum DBMS verhindert dies.

wulfgarpro
quelle
4

In SQL Server ist die Verwendung einer vorbereiteten Anweisung definitiv injektionssicher, da die Eingabeparameter keine Abfrage bilden. Dies bedeutet, dass die ausgeführte Abfrage keine dynamische Abfrage ist. Beispiel für eine anfällige SQL-Injection-Anweisung.

string sqlquery = "select * from table where username='" + inputusername +"' and password='" + pass + "'";

Wenn der Wert in der Variablen inoutusername so etwas wie a 'oder 1 = 1 - ist, lautet diese Abfrage jetzt:

select * from table where username='a' or 1=1 -- and password=asda

Und der Rest wird danach kommentiert --, sodass er niemals ausgeführt und umgangen wird, wenn das vorbereitete Anweisungsbeispiel wie folgt verwendet wird.

Sqlcommand command = new sqlcommand("select * from table where username = @userinput and password=@pass");
command.Parameters.Add(new SqlParameter("@userinput", 100));
command.Parameters.Add(new SqlParameter("@pass", 100));
command.prepare();

Tatsächlich können Sie also keinen weiteren Parameter einsenden, um eine SQL-Injection zu vermeiden ...

Loydom
quelle
3

Der Schlüsselbegriff lautet need not be correctly escaped. Das bedeutet, dass Sie sich keine Sorgen machen müssen, wenn Leute versuchen, Bindestriche, Apostrophe, Zitate usw. einzufügen.

Es wird alles für Sie erledigt.

Feisty Mango
quelle
2
ResultSet rs = statement.executeQuery("select * from foo where value = " + httpRequest.getParameter("filter");

Nehmen wir an, Sie haben das in einem Servlet, das Sie richtig haben. Wenn eine böswillige Person einen schlechten Wert für "Filter" übergeben hat, können Sie Ihre Datenbank hacken.

MeBigFatGuy
quelle
0

Grundursache Nr. 1 - Das Trennzeichenproblem

SQL-Injection ist möglich, weil wir Anführungszeichen verwenden, um Zeichenfolgen abzugrenzen und auch Teile von Zeichenfolgen zu sein, was es manchmal unmöglich macht, sie zu interpretieren. Wenn wir Trennzeichen hätten, die nicht in Zeichenfolgendaten verwendet werden könnten, wäre eine SQL-Injection niemals aufgetreten. Durch das Lösen des Trennzeichenproblems wird das SQL-Injection-Problem beseitigt. Strukturabfragen machen das.

Grundursache Nr. 2 - Menschliche Natur, Menschen sind schlau und einige schlaue Menschen sind bösartig und alle Menschen machen Fehler

Die andere Grundursache für die SQL-Injektion ist die menschliche Natur. Menschen, einschließlich Programmierer, machen Fehler. Wenn Sie bei einer strukturierten Abfrage einen Fehler machen, ist Ihr System nicht anfällig für SQL-Injection. Wenn Sie keine strukturierten Abfragen verwenden, können Fehler zu einer Sicherheitsanfälligkeit bezüglich SQL-Injection führen.

Wie strukturierte Abfragen die Hauptursachen von SQL Injection beheben

Strukturierte Abfragen Lösen Sie das Trennzeichenproblem, indem Sie SQL-Befehle in eine Anweisung und die Daten in eine separate Programmieranweisung einfügen. Programmieranweisungen erzeugen die erforderliche Trennung.

Strukturierte Abfragen verhindern, dass durch menschliches Versagen kritische Sicherheitslücken entstehen. In Bezug auf Menschen, die Fehler machen, kann eine SQL-Injektion nicht erfolgen, wenn Strukturabfragen verwendet werden. Es gibt Möglichkeiten, die SQL-Injection zu verhindern, bei denen keine strukturierten Abfragen erforderlich sind. Ein normaler menschlicher Fehler bei diesen Ansätzen führt jedoch normalerweise zu einer zumindest gewissen Exposition gegenüber der SQL-Injection. Strukturierte Abfragen sind vor SQL-Injection ausfallsicher. Sie können fast alle Fehler der Welt mit strukturierten Abfragen machen, wie bei jeder anderen Programmierung, aber keine, die Sie machen können, kann in ein System umgewandelt werden, das von SQL Injection übernommen wird. Deshalb sagen die Leute gerne, dass dies der richtige Weg ist, um eine SQL-Injektion zu verhindern.

Da haben Sie es also, die Ursachen der SQL-Injektion und die naturstrukturierten Abfragen, die sie unmöglich machen, wenn sie verwendet werden.

DanAllen
quelle