Kein Programm ist 100% sicher, aber wir wollen es potenziellen Angreifern auch nicht zu leicht machen, daher folgen ein paar Grundlegende Regeln zur Sicherheit von PHP. Ich gehe hier davon aus, dass die installierte PHP-Version auf dem aktuellen Stand ist und somit weniger Angriffsfläche bietet. -> PHP-Sicherheit erhöhen – Teil 1
1.) “register_globals” sollten deaktiviert sein!!!
Falls diese Option aktiv ist, ist das Überschreiben lokaler Variablen via GET / POST möglich z.B.:
test.php:
———
<?php
if (!isset($test))
{
$test = 1;
}
echo 'Der Wert von test ist: '.$test;
?>
Browser:
——–
…/test.php?test=2
Ab PHP 4.2 ist diese Option (php.ini -> register_globals = Off) jedoch bereits standardmäßig auf “Off” gesetzt.
2.) “Type Casting” verwenden
Casting bedeutet so viel wie Umwandeln einer Variable in einen bestimmten Type z.B. wird aus “123z” -> “123” wenn wir dies zu einer Ganzzahl (int) casten. z.B.
test.php:
———
<?php
$zahl = (int)$_GET['zahl'];
?>
Browser:
——–
…/test.php?zahl=123z
php.net:
——–
Type Casting -> http://de2.php.net/manual/de/language.types.type-juggling.php
3.) Variablen prüfen
Allgemein kann man prüfen, ob die Variablen überhaupt vorhanden/gesetzt sind.
test.php:
———
<?php
if (empty($_GET['zahl']) { [...]
if (!isset(($_GET['zahl']) { [...]
if (!$_GET['zahl']) { [...] // Warnung ("Notice") falls "error_reporting(E_ALL)" und die Variable nicht definiert wurde
php.net:
——–
empty() -> http://php.net/manual/de/function.empty.php
isset() -> http://php.net/manual/de/function.isset.php
4.) Variablen “Type” prüfen
test.php:
———
<?php
if (gettype($_GET['zahl']) == "integer") { $zahl = $_GET['zahl']; }
else { $zahl = (int) $_GET['zahl']; }
?>
Alternative: if (is_int($_GET[‘zahl’])) { […]
5.) “Ctype” Funktionen
Es gibt noch weitere php-Module z.B. “ctype” mit denen sich Variablen überprüfen lassen, ich will jedoch als nächstes einen allgemein gültigen Ansatz via “regulären Ausdrücken” zeigen.
php.net:
——–
http://de2.php.net/manual/de/ref.ctype.php
6.) Variablen prüfen / zuschneiden via “regulären Ausdrücke”
Reguläre Ausdrücke sind Muster, mit denen Zeichenketten beschrieben werden könnten!
Wer die Syntax noch nicht im Kopf hat sollte sich einmal “The Regex Coach” anschauen -> http://weitz.de/regex-coach/
Zusammenfassung:
—————-
[abc] -> a oder b oder c
[^abc] -> nicht a oder b oder c
[a-z] -> von a bis z -> alle Kleinbuchstaben
[A-Z] -> von A bis Z -> alle Großbuchstaben
[a-zA-Z] -> Buchstaben
[0-9] -> von 0 bis 9 -> Ziffern
\d -> von 0 bis 9 -> Ziffern
\D -> keine Ziffer
[a-zA-Z0-9_] -> Buchstaben, Ziffern, Unterstrich
\w -> Buchstaben, Ziffern, Unterstrich
\W -> keine Buchstaben, Ziffern, Unterstrich
[\r\n\t\f]] -> Wagenrücklauf, New Line, Tabulatur, Seitenwechsel -> Leerzeichen
\s -> Leerzeichen
\S -> keine Leerzeichen
\b -> Wortanfang bzw. Wortende z.B. “/\bWort\b/”
\B -> kein Wortanfang bzw. Wortende z.B. “/\BWort\B/”
. -> ein beliebiges Zeichen (Punkt)
x{m} -> “x” muss genau “m”-Mal vorkommen
x{m,} -> “x” muss min. “m”-Mal vorkommen (Ende offen)
x{,n} -> “x” darf max. “n”-Mal vorkommen (Anfang offen)
x{m,n} -> “x” muss zwischen “m”- und “n”-Mal vorkommen
x? -> “x” kann, muss aber nicht an dieser Stelle vorkommen => x{0,1}
x* -> “x” kann kein, ein oder mehrmals an dieser Stelle vorkommen => x{0,}
x+ -> “x” kann ein oder mehrmals an dieser Stelle vorkommen => x{1,}
^ -> markiert den Beginn einer Zeichenkette
$ -> markiert das Ende einer Zeichenkette
| -> Alternative z.B. “/ae|a/”
\ -> Maskierung (Escape-Zeichen) für z.B. einen Punkt “\.”
[] -> Zusammenfassung von Mustern
() -> Zusammenfassung von Mustern + selektieren und an PHP zurückgeben
6.1) ein Beispiel
test.php:
———
<?php
if((!empty($_GET['Text'])) && (preg_match("/^(T|t)est(ing*){0,1}$/",$_GET['Text'])))
{
$variable = "OK";
} else {
$variable = "nicht OK";
}
echo 'Der Wert ist '.$variable;
?>
Browser:
——–
…/test.php?Text=Test -> OK -> (T|t)est
…/test.php?Text=test -> OK -> test(ing){0,1} = test(ing)?
…/test.php?Text=testing -> OK -> test(ing){0,1} = test(ing)?
…/test.php?testinggggg -> OK -> g*
6.2) noch ein Beispiel
Hier sind nur Zahlen zugelassen …
test.php:
———
<?php
if((!empty($_GET['Text'])) && (preg_match("/^\d+([\.,]\d+)?$/",$_GET['Text'])))
{
$variable = "OK";
} else {
$variable = "nicht OK";
}
echo 'Der Wert ist '.$variable;
?>
Info:
—–
/^\d+([\.,]\d+)?$/ -> ist gleich -> /^[0-9]{1,}([\.,][0-9]{1,})?$/
Browser:
——–
…/test.php?Text=1 -> OK -> [0-9]{1,}
…/test.php?Text=123456789 -> OK -> [0-9]{1,}
…/test.php?Text=1.9 -> OK -> [0-9][\.,][0-9]
…/test.php?Text=123.789 -> OK -> [0-9]{1,}[\.,][0-9]{1,}
…/test.php?Text=123,789 -> OK -> [0-9]{1,}[\.,][0-9]{1,}
6.3) und noch ein Beispiel
Hier schneiden wir einen Teil aus einen String aus und geben diesen aus …
test.php:
———
<?php
$imagemap = "<map id=\"imgmap201111415460\" name=\"imgmap201111415460\"><area shape=\"rect\" alt=\"\" title=\"\" coords=\"151,58,221,148\" href=\"\" target=\"\" /></map>";
$imagemap_name = preg_replace(‘/^<map(.+)name=\”(.+)\”><area(.+)/i’, “$2”, $imagemap);
echo ‘Imagemap-Name: ‘.$imagemap_name;
?>
Ausgabe => Imagemap-Name: imgmap201111415460
Info:
—–
(.+) -> ist gleich -> (.{1,})
php.net:
——–
http://de3.php.net/manual/de/function.preg-replace.php
7.) Maskieren von Strings zur Nutzung in Datenbanken
Sobald Daten in einer Datenbank gespeichert werden, sollte man diese via “mysql_real_escape_string” bzw. “mysqli_real_escape_string” maskieren, so dass “SQL Injection Angriffe” verhindert werden.
php.net:
——–
http://php.net/manual/de/function.mysql-real-escape-string.php
8.) Ein-Weg-Verschlüsselung
Bei der Ein-Weg-Verschlüsselung wird nicht die original Eingabe, sondern ein Hash-Wert davon gespeichert z.B. md5
test.php:
———
<?php $passwort = 'mein_Pwd';
echo md5($passwort); ?>
Ausgabe: b4e28e9d7dbf6d41d6e609b3eb46fac4
Das Problem ist jedoch, dass man diese Verschlüsselung via “Brute-Force-Attacke” erraten bzw. in einer “bösen”-Datenbank ;) mit vielen Kombinationen aus md5-hash und original-Wert auslesen kann.
Daher kann man z.B. auch sha1() anstelle von md5() nutzen, was zwar den Zeitaufwand bei “Brute-Force-Attacke” erhöht, jedoch nicht die Schwachstellen der Ein-Weg-Verschlüsselung behebt.
php.net:
——–
http://de.php.net/manual/de/function.md5.php
http://de.php.net/manual/de/function.sha1.php
9.) Zwei-Wege-Verschlüsselung
Bei der Zwei-Wege-Verschlüsselung werden Daten über einen Schlüssel verschlüsselt, so dass diese auch wieder entschlüsselt werden können.
test.php:
———
<?php
function encryptData($value){
$key = "top secret key";
$text = $value;
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $text, MCRYPT_MODE_ECB, $iv);
return $crypttext;
}
function decryptData($value){
$key = “top secret key”;
$crypttext = $value;
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$decrypttext = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $crypttext, MCRYPT_MODE_ECB, $iv);
return trim($decrypttext);
}
$test = encryptData(“Dies ist ein TEST!!!”);
echo $test;
echo “<br><br>”;
echo decryptData($test);
?>
Algorithmus:
————
-> MCRYPT_BLOWFISH (blowfish)
-> MCRYPT_TRIPLEDES (tripledes)
-> MCRYPT_RJINDAEL_256 (rjindael-256)
-> MCRYPT_CAST_256 (cast-256)
Modus:
——
-> MCRYPT_MODE_ECB (electronic codebook): für kurze Zeichenketten
-> MCRYPT_MODE_CBC (cipher block chaining): zur Dateiverschlüsselung
-> MCRYPT_MODE_CFB (cipher feedback): für Byteströme, mit Verschlüsselung einzelner Bytes
-> […]
php.net:
——–
http://php.net/manual/de/function.mcrypt-encrypt.php
Unter MySQL ist die “Zwei-Wege-Verschlüsselung” einfacher zu handhaben, es gibt nur zwei Alternativen
– AES (Advanced Encryption Standard)
– DES: TripleDES
z.B.:
AES_DECRYPT(verschluesselt,schluessel)
AES_ENCRYPT(text,schluessel)
dev.mysql.com:
————–
http://dev.mysql.com/doc/refman/5.5/en/encryption-functions.html
10.) SSL (Secure Socket Layer)
Da das HTTP-Protokoll Daten plain (unverschlüsselt) überträgt, ist eine SSL- bzw. HTTPS-Verschlüsselung immer dann sinnvoll, wenn sensible Daten übertragen werden. So können die Daten beim Transport über das Internet nicht von dritten mitgelesen (Sniffing) werden.
11.) Cross-Site-Scripting (XSS)
Es geht darum Code auszuführen, der von einer fremden Webseite, in die aktuelle Webseite eingeschleust wird. Es ist ein Oberbrgriff für unterschiedliche Arten (serverseitiges & clientseitiges Cross-Site-Scripting) von Angriffen.
wikipedia.org:
————–
http://de.wikipedia.org/wiki/Cross-Site-Scripting
11.1) Serverseitiges Cross-Site-Scripting
Hier wird der “böse” Code direkt auf dem Server der angegriffenen Webseite ausgeführt (z.B.: von PHP) …
… es folgt ein sehr schwerwiegender Programmierfehler, eine include & GET kombination, um serverseitiges XSS zu demonstrieren.
test.php:
———
<?php
inculde($_GET['skript'];
// etc.
...
?>
Info: Clientseitiges Cross-Site-Scripting -> Hier wird der “böse” Code via Browser-Technologien (z.B. via JavaScript, ActiveX …) eingeschleust.
11.2) SQL Injection
wikipedia.org:
————–
http://de.wikipedia.org/wiki/SQL-Injection
Auch dies ist eine Art des serverseitiges Cross-Site-Scriptings und das Ziel des Angreifers ist die Datenbank.
test.php:
———
<?php
[...]
$sql = "SELECT * FROM tabelle WHERE id=" . $id;
[...]
?>
Wenn die Variable die nun per GET, POST oder Cookie überschrieben werden kann, wird eine neue SQL-Query an die Datenbank gesendet. Dabei wird die Tatsache ausgenutzt, dass man mehrere SQL-Befehle mit
Simikolon getrennt, ausführen kann.
Gegenmaßnahmen:
-> Anführungsstriche verwenden
Anführungsstriche werden dazu verwendet um Variablen in SQL-Queries zu benutzen.
test.php:
———
[...] SELECT * FROM xyz WHERE id = '$Test' [...]
Ein Angreifer, der $Test verändert kann nicht aus den Anführungsstrichen ausbrechen, sloange $Test geparst wurde und alle ‘ mit \’ maskiert sind.
Es gibt jedoch noch weitere Möglichkeiten um sich vor solchen Angriffen zu schützen.
-> Keine Strings in numerischen Variablen zulassen
Die Funktion settype wandelt Variablen (var) in einen Integer (int) um:
test.php:
———
[...]
settype($Variable, 'integer');
[...]
php.net:
——–
settype -> http://www.php.net/manual/de/function.settype.php
-> Datenbankverbindungen mit eingeschränkten Rechten
Gehe sparsam mit der Rechtevergabe an die Web (User) um, so dass im Notfall nicht die komplette Datenbank (DROP DATABASE name) betroffen ist. ;)
-> Werte maskieren
Mit der Funktion mysql(i)_real_escape_string bietet PHP eine einfache Möglichkeit SQL-Befehle zu maskieren.
Hinweis: Neben dem Semikolon ist auch ein doppelter Bindestrich (Kommentar in MySQL) dazu geeignet SQL-Kommandos nach dem eingeschleusten Code abzubrechen.
Zusammenfassung: Wenn man generell Anführungszeichen in den SQL-Kommandos benutzt und Variablen immer maskiert, würde dies zirka wie folgt aussehen…
test.php:
———
[...]
if (!isset($_GET['id'])) { $id = 0; }
else { $id = $_GET['id']; }
if (gettype($id) == “integer”) { $zahl = $id; }
else { $zahl = settype($id, ‘integer’); }
$sql = “SELECT * FROM tabelle
WHERE id='” . mysqli_real_escape_string($zahl) . “‘
AND user=’test’
“;
[…]