Oft scheinen Websites in erster Linie zu existieren, um etwas in eine Datenbank aufzunehmen, um sie später herauszuziehen. Während andere Datenbankmethoden wie NoSQL in den letzten Jahren an Popularität gewonnen haben, befinden sich die Daten vieler Websites immer noch in der traditionellen SQL-Datenbank. Diese Daten bestehen häufig aus wertvollen persönlichen Informationen wie Kreditkartennummern und anderen persönlichen Informationen, die für Identitätsdiebe und Kriminelle von Interesse sind. Hacker suchen daher immer nach diesen Daten. Eines der häufigsten Ziele dieser Angriffe sind die SQL-Datenbanken, die durch einen SQL-Injektionsprozess hinter vielen Webanwendungen stehen.
Ein Injektionsangriff bewirkt, dass die Anwendung nicht vertrauenswürdige Eingaben an den Interpreter weiterleitet. In letzter Zeit sind 40.000 Kundendatensätze, die aus Bell Canada stammen, das Ergebnis eines SQL Injection-Angriffs. Ende 2013 stahlen Hacker von einem in Kalifornien ansässigen ISP über 100.000 US-Dollar mit SQL-Injektion.
Das Open Web Application-Sicherheitsprojekt (OWASP) wählte den Injektionsangriff aufgrund seiner Verbreitung und des Risikos für die angegriffenen Websysteme als das Top-Sicherheitsrisiko für Anwendungen in den Top-Ten der 2013. Leider hatte es auch im letzten Bericht von 2010 die Nummer eins. Was ist also ein SQL Injection-Angriff? In diesem Tutorial werde ich besprechen, wie sie funktionieren und wie Sie Ihre Anwendung vor diesen Angriffen schützen können.
Jedes interpretierte System hinter einem Webserver kann das Ziel eines Injektionsangriffs sein. Die häufigsten Ziele sind die SQL-Datenbankserver hinter vielen Websites. Die SQL-Injektion ist nicht direkt auf Schwachstellen in der Datenbank zurückzuführen, sondern verwendet Öffnungen in der Anwendung, damit der Angreifer Anweisungen des Angreifers auf dem Server ausführen kann. Bei einem SQL-Injection-Angriff kann die Datenbank mehr Informationen freigeben, als die Anwendung bereitstellt. Sehen wir uns einen SQL-Datenbankaufruf an, den Sie in ASP.NET schreiben könnten.
SqlCommand-Befehl = neuer SqlCommand ("SELECT * FROM-Benutzerdaten WHERE UserId =" + id); SqlDataReader reader = command.ExecuteReader ();
Was ist los mit diesem Code? Vielleicht nichts Das Problem ist das Ich würde
String verwenden wir. Woher kommt dieser Wert? Wenn wir ihn intern oder aus einer vertrauenswürdigen Quelle generieren, funktioniert dieser Code möglicherweise problemlos. Wenn wir jedoch den Wert eines Benutzers erhalten und ohne Änderung verwenden, haben wir uns gerade für die SQL-Injection geöffnet.
Nehmen wir einen allgemeinen Fall, in dem wir den Parameter als Teil der URL nachschlagen. Nimm die URL http://www.example.com/user/details?id=123
. In ASP.NET mit C # können wir den übergebenen Wert mit diesem Code abrufen:
string id = Request.QueryString ["id"];
Dieser Code in Verbindung mit dem obigen Aufruf lässt uns für einen Angriff offen. Wenn der Benutzer wie erwartet eine Benutzer-ID wie 123 übergibt, funktioniert alles einwandfrei. Nichts, was wir hier getan haben, stellt jedoch sicher, dass dies der Fall ist. Nehmen wir an, der Angreifer versucht, auf die URL zuzugreifen http://www.example.com/user/details?id=0; SELECT * FROM Benutzerdaten
.
Anstatt nur einen Wert wie erwartet zu übergeben, haben wir einen Wert angegeben und dann ein Semikolon hinzugefügt, das eine SQL-Anweisung abbricht. Anschließend wird eine zweite SQL-Anweisung hinzugefügt, die der Angreifer ausführen möchte. In diesem Fall würden alle Datensätze in der Nutzerdatentabelle zurückgegeben. Abhängig vom Rest des Codes auf unserer Seite wird möglicherweise ein Fehler zurückgegeben oder möglicherweise jeder Datensatz in der Datenbank dem Angreifer angezeigt. Sogar ein Fehler kann mit sorgfältig erstellten Abfragen verwendet werden, um eine Ansicht der Datenbank zu erstellen. Schlimmer noch, stellen Sie sich vor, der Angreifer geht auf eine URL von http://www.example.com/user/details?id=0; DROP TABLE-Benutzerdaten
. Jetzt gehen alle Benutzer verloren.
In diesem Beispiel kann der Angreifer beliebigen Code auf dem Datenbankserver ausführen. Wenn das Konto, unter dem die Datenbankaufrufe ausgeführt werden, vollständige Kontrolle über die Datenbank hat, hat dies ein zu häufig auftretendes Szenario. Durch das Löschen von Tabellen und das Löschen von Datensätzen kann eine Site auf einfache Weise heruntergefahren werden. Selbst wenn der Angreifer nur Daten in der Datenbank lesen und schreiben kann, müssen die Puling-Daten nur Geduld und Sorgfalt sein.
Nehmen Sie eine einfache Datenbank mit dem Namen "Products", die aus drei Spalten besteht. Die erste Spalte enthält die Produkt-ID, die zweite den Produktnamen und die dritte den Produktpreis. Bei unserer normalen Abfrage versuchen wir, jedes Produkt, das ein Widget enthält, im Namen zu finden. Die SQL, die dies in dem gleichen Muster tut, das wir gezeigt haben, würde folgendermaßen aussehen:
string sql = "SELECT * FROM Produkte, in denen der Produktname LIKE '%' + Suchbegriff +"% "" ist;
Eine typische Website für den Zugriff darauf würde aussehen http://www.example.com/product?search=widget
. Hier durchläuft die Webseite einfach jeden zurückgegebenen Datensatz und zeigt ihn auf dem Bildschirm an. In diesem Fall sehen wir unser Widget-Produkt.
| Produkt-ID | Produktname | Preis | | ----------- | 1 | Widget | 100,00 |
Ändern wir unsere Abfrage in http://www.example.com/product?search=widget 'ODER 1 = 1;--
und führen Sie dieselbe Abfrage aus. etwas mehr sehen. In der Tat sehen wir jeden Datensatz in der Tabelle.
| productid | Produktname | Preis | | --- | 1 | Widget | 100,00 | | 2 | Thingy | 50,00 | | 3 | Boxy | 125,00 |
Wir haben den Interpreter dazu gebracht, SQL-Code unserer Wahl auszuführen. Die resultierende SQL wird ausgeführt:
SELECT * FROM Produkte WO Produktname LIKE '% widget' OR 1 = 1; -% '
Das Ergebnis werden zwei Aussagen sein:
SELECT * FROM Produkte WO Produktname LIKE '% widget' OR 1 = 1; -% '
Durch das Hinzufügen der 1 = 1
, Das ist immer wahr, die where-Klausel gilt für jede Zeile in der Tabelle und die resultierende Abfrage gibt jede Zeile zurück. Das --
Am Anfang der zweiten Anweisung wird der Rest der SQL-Anweisung in einen Kommentar umgewandelt, der die Fehlermeldung verhindert, die ansonsten angezeigt wird.
Änderungen an der Anzeige können diese Aussage nicht verhindern. Ein Patienten-Angreifer kann sogar nur die Tatsache verwenden, dass ein Wert zurückgegeben wird oder nicht, und sorgfältig konstruierte Abfragen, um Ihre Datenbank langsam abzubilden und möglicherweise Daten abzurufen, selbst wenn nur eine Fehlermeldung angezeigt wird. Es gibt Tools wie sqlmap, um den Prozess zu automatisieren.
Sie verhindern die SQL-Injektion, indem Sie verhindern, dass nicht vertrauenswürdige Eingaben in die SQL-Datenbank oder einen anderen Interpreter gelangen. Jede Eingabe von außerhalb des Systems sollte als nicht vertrauenswürdig betrachtet werden. Sogar Daten von anderen Partnersystemen müssen als nicht vertrauenswürdig betrachtet werden, da Sie keine Garantie dafür haben, dass das andere System nicht unter Sicherheitsproblemen leidet, durch die beliebige Daten eingefügt werden könnten, die dann an Ihre Anwendung weitergegeben werden.
Wenn wir zu unserem früheren Beispiel zurückkehren, wissen wir, dass der id-Parameter immer eine Ganzzahl sein sollte. Wir können versuchen, ihn in eine Ganzzahl zu konvertieren und einen Fehler anzuzeigen, falls dies fehlschlägt. Eine ASP.NET-MVC-Anwendung führt dies mit folgendem Code aus:
int menge; if (! int.TryParse (Request.QueryString ["qty"], out Quantity)) return RedirectToAction ("Invalid");
Dadurch wird versucht, die Zeichenfolge in eine Ganzzahl zu konvertieren. Wenn die Konvertierung fehlschlägt, wird der Code zu einer Aktion umgeleitet, die eine ungültige Nachricht anzeigt.
Dies kann verhindert werden, wenn wir einen ganzzahligen Parameter suchen. Es würde nicht helfen, wenn wir Text wie in der früheren Produktsuche erwarten. In diesem Fall empfiehlt es sich, reguläre Ausdrücke oder Zeichenfolgenersetzungen zu verwenden, um nur die erforderlichen Zeichen im übergebenen Wert zuzulassen.
Dies kann durch Whitelisting, das Entfernen von anderen Zeichen als einem angegebenen Satz oder durch Blacklisting, durch Entfernen von Mitgliedern eines angegebenen Satzes aus der Zeichenfolge erfolgen. Whitelisting ist zuverlässiger, da Sie nur zulässige Zeichen angeben. Um etwas anderes als Buchstaben und Zahlen in einem String zu entfernen, können wir folgenden Code verwenden:
Regex RegEx = neue Regex ("[^ a-zA-Z0-9 -]"); Zeichenfolge gefiltertString = regEx (originalString, "");
Sie müssen alle Eingaben jeweils auswerten. Für einige Datenbankabfragen ist möglicherweise mehr Aufmerksamkeit erforderlich. Nehmen Sie Zeichen, die in einem SQL-Befehl von Bedeutung sein können, aber auch in einem Datenbankaufruf ein gültiges Zeichen sein können. Zum Beispiel wird das einfache Anführungszeichen 'verwendet, um eine Zeichenfolge in SQL zu starten und zu beenden, es kann jedoch auch Teil des Namens einer Person wie O'Conner sein. In diesem Fall ersetzen Sie das einfache Anführungszeichen '
mit aufeinanderfolgenden einfachen Anführungszeichen "
kann das Problem beseitigen.
Gespeicherte Prozeduren werden häufig als Lösung für dieses Problem angesehen und können Teil der Lösung sein. Eine schlecht geschriebene gespeicherte Prozedur rettet Sie jedoch nicht. Nehmen Sie diese gespeicherte Prozedur mit ähnlichem Code zum Erstellen einer Abfrage:
ALTER PROCEDURE [dbo]. [SearchProducts] @searchterm VARCHAR (50) = "AS BEGIN DECLARE @query VARCHAR (100) SET @query = 'SELECT * FROM Produkte WHERE Produktname LIKE"%' + @searchterm + '% "; EXEC; (@abfrage) ENDE
Die String-Verkettung ist hier das Problem. Versuchen wir einen einfachen Versuch, bei dem wir versuchen, eine immer zutreffende Bedingung zu setzen, um alle Zeilen in der Tabelle anzuzeigen und dasselbe 'Widget' ODER 1 = 1; - "wie die zuvor gesuchte Abfrage zu übergeben. Das Ergebnis ist auch das gleiche :
| productid | Produktname | Preis | | - | 1 | Widget | 100,00 | | 2 | Thingy | 50,00 | | 3 | Boxy | 125,00 |
Wenn nicht vertrauenswürdige Daten übergeben werden, haben wir dasselbe Ergebnis, als ob der Aufruf in unserem Code erstellt worden wäre. Dass die Zeichenfolgenverkettung in einer gespeicherten Prozedur statt in unserem Code erfolgt, bietet keinen Schutz.
Der nächste Teil des Puzzles zum Schutz vor Injektionsangriffen besteht in der Parametrisierung. Das Erstellen von SQL-Abfragen durch Verketten von Zeichenfolgen und anschließendes Übergeben des fertigen Codes vermittelt der Datenbank keine Vorstellung davon, welcher Teil der Zeichenfolge ein Parameter ist und welcher Teil des Befehls ist. Wir können zum Schutz vor Angriffen beitragen, indem wir SQL-Aufrufe so erstellen, dass Anweisungen und Werte unterschiedlich bleiben.
Wir können die zuvor gezeigte gespeicherte Prozedur neu schreiben, um Parameter zu verwenden und einen sichereren Aufruf zu erzeugen. Anstelle der Verkettung %
Zeichen, die Platzhalterzeichen darstellen, erstellen wir eine neue Zeichenfolge, fügen diese Zeichen hinzu und übergeben diese neue Zeichenfolge als Parameter an die SQL-Anweisung. Die neue gespeicherte Prozedur sieht folgendermaßen aus:
ALTER PROCEDURE [dbo]. [SearchProductsFixed] @searchterm NVARCHAR (50) = "AS BEGIN DECLARE @query NVARCHAR (100) DECLARE @msearch NVARCHAR (55) SET @msearch = '%' + @searchterm + '%' SET @query = 'SELECT * FROM Produkte, in denen der Produktname LIKE @search' EXEC sp_executesql @query, N '@ search VARCHAR (55)', @msearch END ist
Die Ausführung dieser gespeicherten Prozedur nur mit dem Wort-Widget funktioniert wie erwartet.
| productid | Produktname | Preis | ---- | 1 | Widget | 100.00
Und wenn wir mit unserem Parameter-Widget übergeben, wird "OR 1 = 1; - nichts zurückgegeben, was zeigt, dass wir für diesen Angriff nicht mehr anfällig sind.
Die Parametrisierung erfordert keine gespeicherten Prozeduren. Sie können dies auch mit im Code integrierten Abfragen nutzen. Hier ist ein kurzes Segment in C #, um eine Verbindung zum Microsoft SQL-Server mithilfe eines Parameters herzustellen und auszuführen.
const string sql = "SELECT * FROM Produkte WO Produktname LIKE @CategoryID"; var connString = WebConfigurationManager.ConnectionStrings ["ProductDatabase"]. ConnectionString; using (var conn = neue SqlConnection (connString)) var command = neuer SqlCommand (sql, conn); command.Parameters.Add ("@ searchterm", SqlDbType.NVarChar) .Value = string.Format ("% 0%", searchTerm); command.Connection.Open (); SqlDataReader reader = command.ExecuteReader (); while (reader.NextResult ()) // In jeder Zeile auszuführender Code wird hier angezeigt
Bis zu diesem Punkt habe ich gezeigt, wie Datenbankangriffe gemindert werden können. Eine weitere wichtige Verteidigungsschicht, die den Schaden minimiert, falls der Angreifer an den anderen Verteidigungen vorbeikommt. Das Konzept des geringsten Privilegs gibt an, dass ein Codemodul, das in diesem Fall von unserer Datenbank aufgerufen wird, nur Zugriff auf die Informationen und Ressourcen hat, die für seine Zwecke benötigt werden.
Wenn ein Datenbankbefehl ausgeführt wird, erfolgt dies unter den Rechten eines Benutzerkontos. Wir erhalten Sicherheit, indem wir dem Konto die Datenbankaufrufe nur mit den Rechten für Dinge ausführen, für die es normalerweise erforderlich ist. Wenn die Aufrufe einer Datenbank nur Daten aus einer Tabelle lesen sollen, geben Sie dem Konto nur die Rechte zum Auswählen der Tabelle und nicht Einfügen oder Löschen. Wenn es bestimmte Tabellen gibt, die aktualisiert werden müssen, beispielsweise eine Auftragstabelle, dann Einfügen und Aktualisieren der Tabelle, nicht jedoch eine andere Tabelle, z. B. eine Tabelle mit Benutzerinformationen.
Das Stornieren von Bestellungen kann über ein separates Konto erfolgen, das nur auf der Seite verwendet wird, die diese Aufgabe ausführt. Dies würde das Löschen einer Bestellung aus der Tabelle an anderer Stelle erschweren. Die Verwendung eines separaten Datenbankkontos für die Verwaltungsfunktionen der Site mit den erforderlichen, größeren Rechten als das, was die Öffentlichkeit verwendet, kann erheblich dazu beitragen, dass ein Benutzer keinen offenen Injektionsangriff findet.
Dies verhindert nicht alle Angriffe. Es würde nichts dagegen tun, zusätzliche Ergebnisse wie das vorherige Beispiel zurückzugeben, das den gesamten Inhalt einer Tabelle zeigt. Dadurch würden Angriffe daran gehindert, Daten zu aktualisieren oder zu löschen.
Die SQL-Injektion ist der gefährlichste Angriff, insbesondere wenn berücksichtigt wird, wie anfällig die Websites für ihn sind und wie viel Potenzial diese Art von Angriff für die Beschädigung von Viren hat. Hier habe ich SQL-Injection-Angriffe beschrieben und gezeigt, welchen Schaden man anrichten kann. Glücklicherweise ist es nicht so schwierig, Ihre Webprojekte durch einige einfache Regeln vor dieser Sicherheitsanfälligkeit zu schützen.
Vertrauen Sie niemals externen Daten, die in Ihre Anwendung eingegeben werden. Es sollte anhand einer Whitelist aus gültigen Eingaben validiert werden, bevor es weiter verarbeitet wird. Dies kann bedeuten, dass ein ganzzahliger Parameter tatsächlich eine ganze Zahl oder ein Datum ein gültiger Datumswert ist. Überprüfen Sie auch den Text, um nur die Zeichen aufzunehmen, die der Parameter benötigen würde. Bei einer Textsuche können Sie häufig nur Buchstaben und Zahlen zulassen und die Interpunktion herausfiltern, die problematisch sein kann, z. B. das Gleichheitszeichen oder ein Semikolon.
Verwenden Sie die Parametrisierung und vermeiden Sie die Verkettung von Zeichenfolgen beim Erstellen von SQL-Aufrufen. Gespeicherte Prozeduren sind kein Allheilmittel, da sie auch verwundbar sein können, wenn einfache String-Verkettung verwendet wird. Durch die Parametrisierung werden viele Probleme der String-Verkettung vermieden.
Der Code, der auf die Datenbank zugreift, sollte mit den geringsten Berechtigungen ausgeführt werden, die zum Ausführen der erforderlichen Aufgaben erforderlich sind. In einigen Fällen müssen die von der Webanwendung verwendeten Datenbankaufrufe Änderungen an der Datenbankstruktur vornehmen, z. B. das Löschen oder das Ändern von Tabellen. Sie können eine zusätzliche Schutzschicht hinzufügen, indem Sie separate Teile der Website unter verschiedenen Konten ausführen. Das für normale Benutzeraktionen verwendete Datenbankkonto hat wahrscheinlich keinen Grund, die Tabelle zu ändern, die die Rollen oder Rechte der Benutzer enthält. Das Ausführen der administrativen Teile der Site unter einem privilegierteren Konto und der Endbenutzerabschnitt unter einem weniger privilegierten Konto können erheblich dazu beitragen, die Gefahr von Code zu reduzieren, durch den andere Probleme verursacht werden.