PHP-Logo

PHP-Sicherheit erhöhen – Teil 2

closeDieser Beitrag wurde vor mehr als drei Monaten veröffentlicht. Bedenke bitte, dass die hier angebotene Information nicht mehr aktuell und gültig sein könnte. Informiere dich daher bitte auch an anderer Stelle über dieses Thema. Sollten sich neue Informationen ergeben haben, so kannst du mich auch gerne auf diese über einen Kommentar hinweisen. Vielen Dank!

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/

The Regex Coach
The 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’
“;
[...]

1 Star2 Stars3 Stars4 Stars5 Stars (2 votes, average: 3.00 out of 5)
  • http://blog.bmarwell.de/ Ben

    “8) Ein-Weg-Verschlüsselung”…
    Hashwerte sind in keinem Fall irgend eine Art von Verschlüsselung. Im Deutschen sagt man auch “Streuwert”. Eingaben werden bei kleinster Änderung in eine völlig andere Zeichenkette umgewandelt.

    Umstieg auf SHA1 bringt nicht wirklich viel, besser ist es, den Hash zu salzen (Salted Hash). Für ungesalzene (unbearbeitete) Hashwerte liegen schon lange sogenannte “Rainbowtabellen” vor, mit denen z.B. alle Passwörter bis ca. 8 Stellen zurückgewandelt werden können.

    Die genauen Begriffsdefinitionen und -Abgrenzungen hatte ich hier bereits einmal aufgeschrieben:
    http://blog.bmarwell.de/2009/04/05/grundlagen-kryptographie/

    Ansonsten stimmen die Beispiele und sind schön zusammengefasst! :)

    Aufpassen muss man bei RegEx etwa noch bei “[^abc]“. Es findet nicht (wie man denken Könnte) Zeichenketten, die “nicht ‘abc’” entsprechen, sondern alle Zeichen, die nicht in der Menge [abc] sind. Es ist ein Unterschied zu: “[^(abc)]“, wo die aufeinanderfolgenden Buchstaben “abc” genau so in der Reihenfolge nicht vorkommen dürfen.

    Gruß,
    Ben

  • http://blog.murawski.ch/ Stefan M.

    Kleine bemerkung zu “if (is_int($_GET['zahl'])) { [...]”

    $_GET können nie is_int() sein, höchstens is_numeric().
    Hat irgendwas mit Browser übermittlung zu tun, bin mehrfach auf das Problem gestossen ;)

    • http://www.reincom.de Andreas

      Formularvariablen werden grundsätzlich immer als Strings übermittelt! Daher, wenn man einen Integer erwartet, dann die Zahl mit (int) casten und den Wert überprüfen. Anmerkung: war es keine Zahl oder eine 0, dann ist das Ergebnis auch eine 0.
      Grundsätzlich gilt: “ALLES WAS VON AUSSEN KOMMT ->IMMER PRÜFEN!!! und ALLES WAS RAUS GEHT -> IMMER ESCAPEN!!!
      Soll heißen von AUSSEN: Formular Daten ($_GET, $_POST), Session Variablen ($_SESSION) und Cookie Variablen ($_COOKIE).
      WAS RAUS GEHT: jede Ausgabe, die von außen gekommen ist (Variable), mit htmlspecialchars() oder htmlentities() ausgeben und bei der Speicherung in z.B. einer Datenbank mit mysql_real_escape_string() oder mysqli_real_escape_string, je nach Adapter bzw. lieber Prepare Statement einsetzen. Anmerkung: in die Datenbank kommen nur saubere Daten!!! Beispielsweise schöner Tag und nicht schöner Tag.

  • Thomas

    SQL Abfragen lasse ich nur noch über die Prepared Statements laufen, egal ob Select, Update oder Insert. Das mit den Rechten habe ich schon vor Jahren in Onlineshops umgesetzt und sogar die Produktdaten und Benutzerdaten in zwei Datenbanken getrennt. Ist eben alles eine Sache der Faulheit mit den PHP Fehlern :-)

  • http://www.akcp-sicherheit.de Nico

    Vielen Dank für diese ausführliche Anleitung. Man sollte die Sicherheit nie aus den Augen lassen.