Preventing SQL injections in PHP (and other vulnerabilities)
Wenn Sie schon eine Weile in der Webentwicklung sind, haben Sie mit ziemlicher Sicherheit den Begriff „SQL injection“ und einige schreckliche Geschichten darüber gehört.
PHP ist wie viele andere Sprachen nicht immun gegen diese Art von Bedrohung, die in der Tat sehr gefährlich sein kann. Glücklicherweise können Sie jedoch konkrete Schritte unternehmen, um Ihre Websites vor SQL Injection und anderen ähnlichen Bedrohungen zu schützen.
In diesem Beitrag erfahren Sie, was SQL Injection ist, welche Konsequenzen ein erfolgreicher Angriff hat, wie ein Angreifer anfälligen PHP-Code ausnutzen kann, was Sie tun können, um dies zu verhindern, und mit welchen Tools Sie die Teile Ihres Codes erkennen können, die dieser Art von Bedrohung ausgesetzt sein könnten. Darüber hinaus erfahren Sie mehr über einige andere häufige Schwachstellen, die Sie kennen sollten, um sicherere Anwendungen zu erstellen.
Wie funktioniert eine SQL-Injection?
Das erste, was Sie wissen müssen, um Ihren Code vor SQL-Injection zu schützen, ist zu verstehen, wie er von einem Angreifer ausgenutzt werden kann.
Die Idee hinter dem Exploit ist ziemlich einfach: Ein Angreifer führt bösartigen SQL-Code in Ihrer Datenbank über Ihre App aus.
Wie kann das jemand erreichen? Indem Sie die Eingaben Ihrer Anwendung missbrauchen.
Schauen wir uns ein einfaches Beispiel an. Angenommen, Sie haben eine Anwendung, die eine Liste von Benutzernamen anzeigt, in der jeder Benutzer einen Link zu seinen Details wie folgt hat:
Und dann haben Sie in der user_details.php
-Datei Folgendes:
<?php $sql = "SELECT * FROM user WHERE id = ".$_GET;
Wenn der Benutzer wie erwartet handelt und auf den Namen eines Benutzers klickt, sieht die Anforderungs-URL folgendermaßen aus: http://domain.com/user_details.php?id=8
.
Also, was ist los damit?
Der Wert von $sql
ist die Zeichenfolge "SELECT * FROM user WHERE id = 8"
und die Abfrage wird einwandfrei ausgeführt.
Das Problem liegt im kursiven Teil dieses Satzes: Wenn der Benutzer wie erwartet handelt und auf den Namen eines Benutzers klickt, sieht die Anforderungs-URL folgendermaßen aus:
http://domain.com/user_details.php?id=8
.
Was ist, wenn der Benutzer nicht wie erwartet handelt?
Was wäre, wenn jemand eine URL herstellen würde, die etwas anderes als eine Nummer als ID sendet?
Wenn die URL so etwas wie http://domain.com/user_details.php?id=Peanuts
wäre, wäre die resultierende SQL-Zeichenfolge: "SELECT * FROM user WHERE id = Peanuts"
.
In diesem Fall wäre das Problem für den Benutzer. Die Abfrage würde einfach fehlschlagen, und im schlimmsten Fall würde der Benutzer eine seltsame Nachricht sehen.
Aber was wäre, wenn die URL eher so aussehen würde:
http://domain.com/user_details.php?id=1+OR+1%3B
? (Dies ist übrigens das Ergebnis von urlencode('1 OR 1;')
.)
In diesem Fall wäre das resultierende SQL "SELECT * FROM user WHERE id = 1 OR 1;"
. Das bedeutet, dass das Ergebnis der Abfrage jeder Benutzer in der Datenbank ist und nicht nur der Benutzer, dessen ID gleich 1 ist. AUA.
Aber das ist immer noch nicht so schlimm, oder?
Versuchen wir nun ein extremeres Beispiel.
Nehmen wir an, die URL war so etwas wie
http://domain.com/user_details.php?id=1%27%3B+DROP+TABLE+users%3B--+
, was in id=1; DROP TABLE users;--
übersetzt wird und diesen SQL-Befehl effektiv erzeugt:
"SELECT * FROM users WHERE id = 1; DROP TABLE users;-- "
. Und wenn Sie diese Abfrage ausführen … großer Autsch.
Falls Sie mit der SQL-Syntax nicht zu 100% vertraut sind, würde DROP TABLE
die Tabelle vollständig löschen. Es wäre, als hätte es nie existiert.
Natürlich würde ein aktuelles Backup helfen, den Schaden zu reduzieren, aber immer noch. Das ist schlimm.
Wenn Sie mehr über diese Art von Angriff erfahren möchten, gibt es hier einen großartigen Beitrag.
Aber ich denke, das sind genug schlechte Nachrichten. Gehen wir zu einem fröhlicheren Ort über: Wie Sie diese Art von SQL-Injektionen in Ihren PHP-Apps verhindern können.
Verhindern von SQL-Injektionen in PHP
Wie Sie gesehen haben, besteht das Problem darin, dass ein Angreifer SQL-Code ausführen kann, ohne dass Sie sich dessen bewusst sind.
Diese Situation stellt sich als Folge von Abfragen dar, die im laufenden Betrieb über die Verkettung von Zeichenfolgen erstellt werden und Daten enthalten, die aus externen Eingaben stammen (normalerweise Benutzereingaben, aber auch andere externe Quellen wie Dateien, Antworten auf API-Aufrufe usw.).
Um diese Sicherheitsanfälligkeit in Ihrem Code zu verhindern, müssen Sie die Quellen potenzieller Probleme beheben.
Validieren Sie Ihre Eingaben
Eine einfache Möglichkeit, die Sicherheitsanfälligkeit im obigen Beispiel zu beheben, besteht darin, zu überprüfen, ob der ID-Parameter dem entspricht, was tatsächlich von ihm erwartet wird (z., eine positive ganze Zahl):
Eine andere Möglichkeit, diese Art der Validierung durchzuführen, besteht darin, die in PHP integrierten Filter zu nutzen:
<?php
$id = filter_input( INPUT_GET, 'id', FILTER_VALIDATE_INT);
Indem Sie also Ihre Eingaben validieren, verhindern Sie, dass Angreifer neben Ihrem eigenen bösartigen Code ausführen.
Es gibt keine einfache Möglichkeit, sie daran zu hindern, es zu versuchen, aber solange ihre Versuche nicht erfolgreich sind, können Sie loslegen.
Verwenden Sie vorbereitete Anweisungen
Eine andere Möglichkeit, Ihren Code vor SQL-Injektionen zu schützen, besteht darin, vorbereitete Anweisungen zu verwenden. Vorbereitete Anweisungen sind vorkompilierte SQL-Befehle.
Sie können mit einer bestimmten Datenbankzugriffsbibliothek (wie mysqli) oder mit der allgemeineren Bibliothek PDO verwendet werden.
Schauen wir uns ein Beispiel mit mysqli an:
Wenn Sie diesen Code ausprobieren (natürlich die Datenbankanmeldeinformationen ersetzen), werden Sie feststellen, dass Sie beim Zugriff über eine legale URL wie http://domain.com/user_details.php?id=1
das gleiche Ergebnis erhalten wie jede der oben genannten schädlichen URLs (dh die Informationen über den Benutzer, der mit ID 1 verknüpft ist, und sonst nichts).
Wenn Sie PDO bevorzugen, sieht der Code eher so aus:
Nicht wirklich viel Unterschied, oder?
Zusammenfassend bieten vorbereitete Anweisungen eine großartige Möglichkeit, SQL-Injections zu verhindern. Außerdem sind sie normalerweise besser als On-the-Fly-SQL.
Jetzt, da Sie das Tool kennen, müssen Sie es nur noch verwenden.
So erkennen Sie PHP-Code, der für SQL-Injektionen anfällig ist
Sobald die Sicherheitsanfälligkeit gefunden wurde, ist die Behebung nicht wirklich kompliziert. Aber wie finden Sie diese Art von Schwachstellen in Ihrem Code überhaupt?
Nun, wenn Sie Glück haben und Ihre Codebasis klein genug ist, reicht eine einfache Überprüfung des Codes aus.
Wenn Ihre Anwendung jedoch mittelgroß bis groß ist, benötigen Sie zusätzliche Hilfe.
Ein sehr beliebtes (und Open-Source-) Tool, das Sie für diesen Zweck verwenden können, ist sqlmap , ein einfaches Python-Skript, das versucht, jede URL anzugreifen, die Sie ihm zuführen, und über seine Ergebnisse berichtet.
Hier ist eine Beispielausgabe von einem Lauf gegen eine PHP-basierte Website von mir:
Ein weiteres Open-Source-Tool, das Sie verwenden können, ist Arachni, ein anspruchsvolleres Tool, das eine Web-GUI enthält und in Ruby geschrieben wurde.
Hier ist eine Beispielausgabe von der Ausführung über die CLI:
Neben der Codeüberprüfung und dem Testen sollten Sie eine RASP-Lösung wie Sqreen verwenden, um Ihre Produktionsumgebungen zu überwachen und die Ausführung von Exploits zu stoppen, die die Überprüfungs- und Testphase durchlaufen.
Welche anderen Schwachstellen sollten Sie beachten?
Code injections
SQL Injection ist nur ein Mitglied einer größeren Familie von Schwachstellen: Code Injection.
Die Idee hinter verschiedenen Code-Injection-Techniken ist immer die gleiche. Es geht darum, eine unschuldige Anwendung dazu zu bringen, bösartigen Code auszuführen.
Dies kann auf verschiedene Arten erfolgen.
In PHP gibt es Funktionen, die einfach Befehle direkt in das Betriebssystem ausgeben, z. B. die Funktion exec
.
Hier ist ein einfaches Beispiel, wie dies ausgenutzt werden könnte:
<?php
exec( 'cp uploads/'.$_GET.' /secure/');
Wenn jemand http://domain.com/exec.php?file=a.txt%3B+rm+-Rf+%2A+%23
anfordern würde, wäre das Ergebnis die Ausführung dieses Befehls:
cp uploads/a.txt; rm -Rf * # /secure/
Und dies könnte die gesamte Festplatte auf dem Server auslöschen – die nicht validierte Eingabe schlägt erneut zu!
Cross-Site Scripting
Eine weitere sehr häufige Schwachstelle ist Cross-Site Scripting (XSS). In diesem Fall ist das Opfer der Benutzer, der Ihre Website besucht.
XSS tritt auf, wenn ein Angreifer bösartigen JavaScript-Code in ein reguläres HTML-Formular einfügt, das später vom Browser eines anderen Benutzers gerendert wird.
Schauen Sie sich dieses PHP-Skript an:
<?php
echo "<html><p>Hello {$_GET}!</p></html>";
Offensichtlich ist die Absicht hier, eine einfache Seite zu erstellen, die den Benutzer begrüßt.
Aber wenn Bösewichte dazu kommen, was hindert sie daran, anzufordern
http://domain.com/greet.php?name=Mauro%3Cscript+language%3D%22javascript%22%3Ealert%28%22You+are+hacked%21%22%29%3B%3C%2Fscript%3E
? Diese Anforderung erzeugt den folgenden HTML-Code als Ausgabe:
<html><p>Hello Mauro<script language="javascript">alert("You are hacked!");</script></p></html>
Dies ist eindeutig ein sehr naiver Exploit – was schadet es schließlich, jemandem ein einfaches „Sie werden gehackt!“ nachricht? Aber mit ein wenig Fantasie kann es ziemlich ernst werden. Denken Sie daran, dass jede Anforderung an einen Server das Sitzungscookie enthält, sodass diese Sicherheitsanfälligkeit der Grund dafür sein kann, dass die Sitzungen Ihrer Benutzer entführt werden.
Während die Probleme hässlich aussehen, sind die Korrekturen wirklich einfach: Es geht um Validierung und Bereinigung.
Im Fall von XSS genügt ein einfacher Aufruf von htmlentities
, bevor die tatsächliche Ausgabe erzeugt wird.
Beheben vs. Erkennen von Sicherheitslücken
Wie Sie in diesem Beitrag gesehen haben, ist das Beheben von Sicherheitslücken — sowohl SQL—Injections als auch andere Arten – nicht die größte Herausforderung. Zu erkennen, dass Ihr Code anfällig ist und wo er ausgenutzt werden kann, ist. Sie haben hier ein paar Möglichkeiten:
- Haben Sie eine sehr starke Codierungsdisziplin
- Testen Sie Ihre Anwendungen gründlich auf Sicherheitsprobleme
- Nutzen Sie Überwachungs- und Schutztools, um Exploits zu verhindern und Warnungen rechtzeitig zu erhalten
Welches Sie wählen, liegt ganz bei Ihnen. Das Wichtigste ist, auf diese Probleme zu achten und auf dieses Wissen zu reagieren. Bleib in Sicherheit.
Wenn die URL so etwas wie http://domain.com/user_details.php?id=Peanuts
wäre, wäre die resultierende SQL-Zeichenfolge: "SELECT * FROM user WHERE id = Peanuts"
.
In diesem Fall wäre das Problem für den Benutzer. Die Abfrage würde einfach fehlschlagen, und im schlimmsten Fall würde der Benutzer eine seltsame Nachricht sehen.
Aber was wäre, wenn die URL eher so aussehen würde:
http://domain.com/user_details.php?id=1+OR+1%3B
? (Dies ist übrigens das Ergebnis von urlencode('1 OR 1;')
.)
In diesem Fall wäre das resultierende SQL "SELECT * FROM user WHERE id = 1 OR 1;"
. Das bedeutet, dass das Ergebnis der Abfrage jeder Benutzer in der Datenbank ist und nicht nur der Benutzer, dessen ID gleich 1 ist. AUA.
Aber das ist immer noch nicht so schlimm, oder?
Versuchen wir nun ein extremeres Beispiel.
Nehmen wir an, die URL war so etwas wie
http://domain.com/user_details.php?id=1%27%3B+DROP+TABLE+users%3B--+
, was in id=1; DROP TABLE users;--
übersetzt wird und diesen SQL-Befehl effektiv erzeugt:
"SELECT * FROM users WHERE id = 1; DROP TABLE users;-- "
. Und wenn Sie diese Abfrage ausführen … großer Autsch.
Falls Sie mit der SQL-Syntax nicht zu 100% vertraut sind, würde DROP TABLE
die Tabelle vollständig löschen. Es wäre, als hätte es nie existiert.
Natürlich würde ein aktuelles Backup helfen, den Schaden zu reduzieren, aber immer noch. Das ist schlimm.
Wenn Sie mehr über diese Art von Angriff erfahren möchten, gibt es hier einen großartigen Beitrag.
Aber ich denke, das sind genug schlechte Nachrichten. Gehen wir zu einem fröhlicheren Ort über: Wie Sie diese Art von SQL-Injektionen in Ihren PHP-Apps verhindern können.
Verhindern von SQL-Injektionen in PHP
Wie Sie gesehen haben, besteht das Problem darin, dass ein Angreifer SQL-Code ausführen kann, ohne dass Sie sich dessen bewusst sind.
Diese Situation stellt sich als Folge von Abfragen dar, die im laufenden Betrieb über die Verkettung von Zeichenfolgen erstellt werden und Daten enthalten, die aus externen Eingaben stammen (normalerweise Benutzereingaben, aber auch andere externe Quellen wie Dateien, Antworten auf API-Aufrufe usw.).
Um diese Sicherheitsanfälligkeit in Ihrem Code zu verhindern, müssen Sie die Quellen potenzieller Probleme beheben.
Validieren Sie Ihre Eingaben
Eine einfache Möglichkeit, die Sicherheitsanfälligkeit im obigen Beispiel zu beheben, besteht darin, zu überprüfen, ob der ID-Parameter dem entspricht, was tatsächlich von ihm erwartet wird (z., eine positive ganze Zahl):
Eine andere Möglichkeit, diese Art der Validierung durchzuführen, besteht darin, die in PHP integrierten Filter zu nutzen:
<?php
$id = filter_input( INPUT_GET, 'id', FILTER_VALIDATE_INT);
Indem Sie also Ihre Eingaben validieren, verhindern Sie, dass Angreifer neben Ihrem eigenen bösartigen Code ausführen.
Es gibt keine einfache Möglichkeit, sie daran zu hindern, es zu versuchen, aber solange ihre Versuche nicht erfolgreich sind, können Sie loslegen.
Verwenden Sie vorbereitete Anweisungen
Eine andere Möglichkeit, Ihren Code vor SQL-Injektionen zu schützen, besteht darin, vorbereitete Anweisungen zu verwenden. Vorbereitete Anweisungen sind vorkompilierte SQL-Befehle.
Sie können mit einer bestimmten Datenbankzugriffsbibliothek (wie mysqli) oder mit der allgemeineren Bibliothek PDO verwendet werden.
Schauen wir uns ein Beispiel mit mysqli an:
Wenn Sie diesen Code ausprobieren (natürlich die Datenbankanmeldeinformationen ersetzen), werden Sie feststellen, dass Sie beim Zugriff über eine legale URL wie http://domain.com/user_details.php?id=1
das gleiche Ergebnis erhalten wie jede der oben genannten schädlichen URLs (dh die Informationen über den Benutzer, der mit ID 1 verknüpft ist, und sonst nichts).
Wenn Sie PDO bevorzugen, sieht der Code eher so aus:
Nicht wirklich viel Unterschied, oder?
Zusammenfassend bieten vorbereitete Anweisungen eine großartige Möglichkeit, SQL-Injections zu verhindern. Außerdem sind sie normalerweise besser als On-the-Fly-SQL.
Jetzt, da Sie das Tool kennen, müssen Sie es nur noch verwenden.
So erkennen Sie PHP-Code, der für SQL-Injektionen anfällig ist
Sobald die Sicherheitsanfälligkeit gefunden wurde, ist die Behebung nicht wirklich kompliziert. Aber wie finden Sie diese Art von Schwachstellen in Ihrem Code überhaupt?
Nun, wenn Sie Glück haben und Ihre Codebasis klein genug ist, reicht eine einfache Überprüfung des Codes aus.
Wenn Ihre Anwendung jedoch mittelgroß bis groß ist, benötigen Sie zusätzliche Hilfe.
Ein sehr beliebtes (und Open-Source-) Tool, das Sie für diesen Zweck verwenden können, ist sqlmap , ein einfaches Python-Skript, das versucht, jede URL anzugreifen, die Sie ihm zuführen, und über seine Ergebnisse berichtet.
Hier ist eine Beispielausgabe von einem Lauf gegen eine PHP-basierte Website von mir:
Ein weiteres Open-Source-Tool, das Sie verwenden können, ist Arachni, ein anspruchsvolleres Tool, das eine Web-GUI enthält und in Ruby geschrieben wurde.
Hier ist eine Beispielausgabe von der Ausführung über die CLI:
Neben der Codeüberprüfung und dem Testen sollten Sie eine RASP-Lösung wie Sqreen verwenden, um Ihre Produktionsumgebungen zu überwachen und die Ausführung von Exploits zu stoppen, die die Überprüfungs- und Testphase durchlaufen.
Welche anderen Schwachstellen sollten Sie beachten?
Code injections
SQL Injection ist nur ein Mitglied einer größeren Familie von Schwachstellen: Code Injection.
Die Idee hinter verschiedenen Code-Injection-Techniken ist immer die gleiche. Es geht darum, eine unschuldige Anwendung dazu zu bringen, bösartigen Code auszuführen.
Dies kann auf verschiedene Arten erfolgen.
In PHP gibt es Funktionen, die einfach Befehle direkt in das Betriebssystem ausgeben, z. B. die Funktion exec
.
Hier ist ein einfaches Beispiel, wie dies ausgenutzt werden könnte:
<?php
exec( 'cp uploads/'.$_GET.' /secure/');
Wenn jemand http://domain.com/exec.php?file=a.txt%3B+rm+-Rf+%2A+%23
anfordern würde, wäre das Ergebnis die Ausführung dieses Befehls:
cp uploads/a.txt; rm -Rf * # /secure/
Und dies könnte die gesamte Festplatte auf dem Server auslöschen – die nicht validierte Eingabe schlägt erneut zu!
Cross-Site Scripting
Eine weitere sehr häufige Schwachstelle ist Cross-Site Scripting (XSS). In diesem Fall ist das Opfer der Benutzer, der Ihre Website besucht.
XSS tritt auf, wenn ein Angreifer bösartigen JavaScript-Code in ein reguläres HTML-Formular einfügt, das später vom Browser eines anderen Benutzers gerendert wird.
Schauen Sie sich dieses PHP-Skript an:
<?php
echo "<html><p>Hello {$_GET}!</p></html>";
Offensichtlich ist die Absicht hier, eine einfache Seite zu erstellen, die den Benutzer begrüßt.
Aber wenn Bösewichte dazu kommen, was hindert sie daran, anzufordern
http://domain.com/greet.php?name=Mauro%3Cscript+language%3D%22javascript%22%3Ealert%28%22You+are+hacked%21%22%29%3B%3C%2Fscript%3E
? Diese Anforderung erzeugt den folgenden HTML-Code als Ausgabe:
<html><p>Hello Mauro<script language="javascript">alert("You are hacked!");</script></p></html>
Dies ist eindeutig ein sehr naiver Exploit – was schadet es schließlich, jemandem ein einfaches „Sie werden gehackt!“ nachricht? Aber mit ein wenig Fantasie kann es ziemlich ernst werden. Denken Sie daran, dass jede Anforderung an einen Server das Sitzungscookie enthält, sodass diese Sicherheitsanfälligkeit der Grund dafür sein kann, dass die Sitzungen Ihrer Benutzer entführt werden.
Während die Probleme hässlich aussehen, sind die Korrekturen wirklich einfach: Es geht um Validierung und Bereinigung.
Im Fall von XSS genügt ein einfacher Aufruf von htmlentities
, bevor die tatsächliche Ausgabe erzeugt wird.
Beheben vs. Erkennen von Sicherheitslücken
Wie Sie in diesem Beitrag gesehen haben, ist das Beheben von Sicherheitslücken — sowohl SQL—Injections als auch andere Arten – nicht die größte Herausforderung. Zu erkennen, dass Ihr Code anfällig ist und wo er ausgenutzt werden kann, ist. Sie haben hier ein paar Möglichkeiten:
- Haben Sie eine sehr starke Codierungsdisziplin
- Testen Sie Ihre Anwendungen gründlich auf Sicherheitsprobleme
- Nutzen Sie Überwachungs- und Schutztools, um Exploits zu verhindern und Warnungen rechtzeitig zu erhalten
Welches Sie wählen, liegt ganz bei Ihnen. Das Wichtigste ist, auf diese Probleme zu achten und auf dieses Wissen zu reagieren. Bleib in Sicherheit.