26. Januar 2010
von Blackbam

Weil ich mich ein Weilchen damit beschäftigt habe, ein kleines Tutorial zum erhöhen der Session-Sicherheit in PHP. Nur um Missverständnissen vorzubeugen, ein kleiner Hinweis: Sichere Sessions machen noch lange kein sicheres PHP-Skript und ein sicheres PHP-Skript gewährt noch lange keine sichere Webanwendung. Außerdem sollte man keinen Code 1:1 aus dem Internet verwenden 😉

Ausgangssituation

Bei PHP wird traditionell bzw. aus Kompatibilitäts- und Bequemlichkeitsgründen mit vielen Sicherheits-relevanten Problemstellungen eher fahrlässig umgegangen. Dabei stellen Sessions keine Ausnahme dar. Eine Session in PHP wird benötigt, um einen Client gegenüber einem Webserver zu authentifizieren. Da HTTP ein zustandsloses Protokoll ist, wird dies in PHP über Session-IDs realisiert. Eine Session-ID wird beim Start einer Session generiert, der Client schleppt sie dann mit sich herum und sendet sie bei Anfragen an den Webserver auf irgendeine Art und Weise mit (in der URL, als verstecktes <input>-Feld oder als Cookie im Header). Der Webserver wiederum schaut nach, ob er die entsprechende Session-ID in seinem Datei-Sortiment gespeichert hat (PHP speichert die Sesson-IDs in Klartext), wenn das nicht der Fall ist, wird schlicht und einfach eine neue Session erzeugt.

Der "Hacker"

Der Hacker hat nun zum Ziel, sich solch Session-ID eines Users zu schnappen und selbst zu verwenden, um sich als dieser ausgeben zu können und entsprechend mit dessen Rechten agieren zu können. Ist es ihm erst einmal gelungen, an eine Session-ID zu kommen, ist das nicht mehr weiter schwer, diese zu verwenden, wie sich z.B. sehr schön mit dem Firefox-Addon Tamper-Data zeigen lässt. Mit diesem lassen sich beliebig POST-Parameter, Header-Daten und somit auch Cookies beliebig modifizieren.
 

 

Demonstration Tamper Data

 


Um an die Session-ID zu kommen gibt es für den Hacker diverse Möglichkeiten und im Prinzip ist alles, was durch das Internet gesendet wird, (mit den entsprechenden Fähigkeiten) abhörbar.
 

Lösungsversuche

Das folgende Skript probiert bestimmten, kritischen Problemen im Session-Handling entgegenzuwirken
 

Problem Gegenmaßnahme
Session-ID als einziges Mittel zur Identifizierung eines Clients Zusätzliche Gegenprüfung der Client-IP und eines evtl. verwendeten Proxies
Nachvollziehbarkeit der Session-ID Erzeugung einer sicheren, nicht rückrechenbaren Session-ID, weitere Schutzmaßnahmen
Lebende Sessions die nicht mehr gültig sein sollten Absolute Zerstörung der Session und des Cookies im Problemfall


 

### wird immer anstatt session_start(); aufgerufen
function startSession() {

	session_start();

	// Falls eine Session existiert, dann muss es auch eine Client_ip geben
	if(filter_var($_SESSION["client_ip"])) {

		// Wenn es bereits eine Session gibt
		if(getSessionClientIP() == $_SESSION["client_ip"]) {
			return;
		} else {
			destroy_session_absolute();
			header("Location: illegal_action.php");
			die;
		}

	} else {

		// falls eine neue Session erzeugt werden muss, dann machen wir das nochmal gscheid
		destroy_session_absolute();
		session_regenerate_save_id();
		session_start();
		setSessionClientIP();
	}
}

### neue, sichere SESSID generieren, MUSS VOR session_start() aufgerufen werden
function session_regenerate_save_id() {
	$hash_time = md5(microtime());
	$hash_ip = md5($_SERVER["REMOTE_ADDR"]);
	$hash_space = sha1(disk_free_space("/"));
	$session_id = sha1($hash_time.$hash_ip.$hash_space);
	session_id($session_id);
}

### speichert die Client-Adresse je nach Typ als md5-Hash in die Session
function setSessionClientIP() {

	if(isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
		$_SESSION["client_ip"] = md5($_SERVER["HTTP_X_FORWARDED_FOR"]);
	} else {
		$_SESSION["client_ip"] = md5($_SERVER["REMOTE_ADDR"]);
	}	

}

### liefert die Client-Adresse entsprechend dem vorher angesprochenen Mechanismus zurück
function getSessionClientIP() {

	if(isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
		return md5($_SERVER["HTTP_X_FORWARDED_FOR"]);
	} else {
		return md5($_SERVER["REMOTE_ADDR"]);
	}	

}

### sichere Zerstörung einer Session
function destroy_session_absolute() {
	if(isset($_COOKIE[session_name()])) {
		setcookie(session_name(),"",time()-42000,"/");
	}
	session_destroy();
}


 

Verwendung des Skripts

  • Jede Session mit startSession(); anstatt session_start(); aufrufen
  • vor jeder kritischen Operation session_regenerate_id(); einsetzen und zwar vor startSession();

Erklärung des Skripts

bzgl. Gegenprüfung der Client-IP bzw. deren Grenzen (Einsatz eines hochanonymen Proxies vom Client)
 

PHP-Server-Variable Standard Normaler Proxy Anonymer Proxy Hochanonymer Proxy Erklärung
$_SERVER["HTTP_VIA"] nein ja, proxy ip ja, proxy ip nein Weiterleitungs-IP
$_SERVER["REMOTE_ADDR"] ja, echte ja, proxy ip ja, proxy ip ja,proxy ip Offizielle-IP
$_SERVER["HTTP_X_FORWARDED_FOR"] nein ja, richtige ja, proxy/random nein Ursprungs-IP

 

Erweiterungsmöglichkeiten für das Skript

Entsprechend dem Schema "Gegenprüfung der Client-IP", welches in diesem Script verwendet wird, können weitere Mittel eingesetzt werden, um einen Client möglichst eindeutig identifizieren zu können. So beispielsweise der Vorschlag von phpforum.de:

// zuverlässig
$_SERVER["DOCUMENT_ROOT"]
$_SERVER["HTTP_USER_AGENT"] // kann leer sein, dann aber zuverlässig leer
// eine eindeutige Kennung vielfältig als "salt" einsetzbar
define("APPLICATION_ID","w3463-dfgsdgd-564564")

// weniger zuverlässig
$_SERVER["REMOTE_ADDR"] // oder nur Teile davon
$_SERVER["HTTP_ACCEPT_LANGUAGE"]
$_SERVER["HTTP_ACCEPT_CHARSET"]
$_SERVER["HTTP_ACCEPT_ENCODING"]
$_SERVER["HTTP_ACCEPT"]

(aus Session-Sicherheit auf phpforum.de)

 

Hat man dann vermeintlich genug identifizierendes Material über den Benutzer gesammelt (natürlich sollte man sich hierbei nur auf Daten verlassen, die während der gesamten Session eindeutig sein sollten, damit man nicht seine Benutzer durch ständigen Logout verärgert ), speichert man es in gehashten Variablen (mit möglichst unauffälligen Namen), die bei startSession(); gegengeprüft werden. Nützlich ist möglicherweise auch ein Überblick über alle $_SERVER-Variablen im PHP-Manual.

 

Man kann dies auch beispielsweise auch über den sogenannten "User-Fingerprint-Array" realisieren. Dazu sammelt man in einem Array erstmal Daten über den Benutzer und bildet darüber einen Hash-Wert.

$fingerprintArray = array($_SERVER['HTTP_USER_AGENT'],substr($_SERVER["REMOTE_ADDR"],0,7),...);
$fingerprint = md5(serialize($fingerprintArray)); .

Dazu und ebenfalls zu dem Thema gibt es hier ebenfalls eine Diskussion.

Weitere Sicherheitsmaßnahmen

  • Nur Cookies zur Übertragung der SessionID erlauben
ini_set("session.use_cookies" ,1); // sicherlich!!
ini_set("session.use_only_cookies",1); // JA! Ohne Cookies geht hier nix!!
ini_set("session.use_trans_sid" ,0); // bloss nicht auf 1 setzen session_start();
  • sensitive Daten nur direkt zum Server übertragen und niemals in einer Session speichern
  • Verwendung von SSL
  • wenn es die Sicherheit erfordert, sei zusätzlich zu einem TAN-System geraten
  • Sicherstellen, dass bei Shared-Hosting kein "Server-Nachbar" auf den Ordner mit den Session-IDs zugreifen kann

 

Ich hoffe das dieses Tutorial den Leser ein Stück weitergebracht hat. Es ist, wie gesagt, nur ein kleines Stück Sicherheit und keinesfalls sollte man sich damit in Sicherheit wiegen.


Als weiterführende Literatur ist das Buch "Sichere Webanwendungen in PHP" von Tobias Wassermann zu empfehlen, aus welchem ein Großteil meines Wissens für dieses Tutorial stammt. Ebenfalls "PHP-Sicherheit" von Christopher Kunz und Stefan Esser ist den ein oder anderen Blick wert.

 

Weiterführende Informationen: Beschwerden, Bugs, Fehler oder gar Lob bitte unbedingt als Kommentar hinterlassen 🙂

Share

Warning: Undefined variable $time_since in /home/.sites/609/site1266/web/blackbams-blog/wp-content/themes/SilentWoodsByBlackbam/single.php on line 42 Dieser Eintrag wurde am 26. Januar 2010 um 18:18 in der Kategorie PHP, Security veröffentlicht. Du kannst die Kommentare zu diesem Artikel mit RSS 2.0 abonnieren. Feedback, Diskussion, Lob und Kritik sind erwünscht: Kommentar schreiben oder Trackback anlegen.


Tags: , , , , , , , , , , ,

Fatal error: Uncaught Error: Undefined constant "Ext_related_links" in /home/.sites/609/site1266/web/blackbams-blog/wp-content/themes/SilentWoodsByBlackbam/single.php:75 Stack trace: #0 /home/.sites/609/site1266/web/blackbams-blog/wp-includes/template-loader.php(106): include() #1 /home/.sites/609/site1266/web/blackbams-blog/wp-blog-header.php(19): require_once('/home/.sites/60...') #2 /home/.sites/609/site1266/web/blackbams-blog/index.php(17): require('/home/.sites/60...') #3 {main} thrown in /home/.sites/609/site1266/web/blackbams-blog/wp-content/themes/SilentWoodsByBlackbam/single.php on line 75 internal_server_error <![CDATA[WordPress &rsaquo; Fehler]]> 500