Session Sicherheit

Fragen zu allen Themen rund ums Programmieren außerhalb von phpBB können hier gestellt werden - auch zu anderen Programmiersprachen oder Software wie Webservern und Editoren.
Antworten
Benutzeravatar
mgutt
Mitglied
Beiträge: 2999
Registriert: 31.08.2004 16:44
Wohnort: Hennef
Kontaktdaten:

Session Sicherheit

Beitrag von mgutt »

Hallo,

ich möchte gerne ein eigenes Loginsystem für ein Projekt umsetzen und habe mich an den Sicherheitstechniken des phpBB orientiert. Dabei viel mir auf, dass im Endeffekt doch "nur" die IP-Adresse und die nutzbaren Zeichen der Session-ID überprüft wird. Daher entschloss ich mich, ein paar weitere Punkte umzusetzen:

Code: Alles auswählen

session_start();

function encode_ip($dotquad_ip)
{
	$ip_sep = explode('.', $dotquad_ip);
	return sprintf('%02x%02x%02x%02x', $ip_sep[0], $ip_sep[1], $ip_sep[2], $ip_sep[3]);
}

function decode_ip($int_ip)
{
	$hexipbang = explode('.', chunk_split($int_ip, 2, '.'));
	return hexdec($hexipbang[0]). '.' . hexdec($hexipbang[1]) . '.' . hexdec($hexipbang[2]) . '.' . hexdec($hexipbang[3]);
}

$login = false;
$uip = ( !empty($HTTP_SERVER_VARS['REMOTE_ADDR']) ) ? $HTTP_SERVER_VARS['REMOTE_ADDR'] : ( ( !empty($HTTP_ENV_VARS['REMOTE_ADDR']) ) ? $HTTP_ENV_VARS['REMOTE_ADDR'] : getenv('REMOTE_ADDR') );

// Daten vom Formular
if ( isset($HTTP_POST_VARS['email']) && isset($HTTP_POST_VARS['pw']) )
{
	if ( !empty($HTTP_POST_VARS['email']) && !empty($HTTP_POST_VARS['pw']) && !empty($uip) )
	{
		$sql = 'SELECT uid
				FROM users
				WHERE email = \'' . addslashes(trim($HTTP_POST_VARS['email'])) . '\'
				AND pw = \'' . md5(addslashes(trim($HTTP_POST_VARS['pw']))) . '\'
				AND active = 1';
		if ( !($result = mysql_query($sql)) )
		{
			die('Fehler in Datei: ' . __FILE__ . '<br />Zeile: ' . __LINE__ . '<br />Fehlermeldung: ' . mysql_error() . '<br />Abfrage: ' . $sql);
		}
		if ( $row = @mysql_fetch_row($result) )
		{
			$_SESSION['uid'] = intval($row[0]);
			$_SESSION['uip'] = encode_ip($uip);
			$_SESSION['pw'] = md5(addslashes(trim($HTTP_POST_VARS['pw'])));
			$_COOKIE['PHPSESSID'] = session_id();
		}
		@mysql_free_result($result);
	}
}

// ohne Cookie läuft hier nichts!
if ( !empty($_COOKIE) && isset($_COOKIE['PHPSESSID']) && preg_match('/^[A-Za-z0-9]{32}$/', $_COOKIE['PHPSESSID']) )
{
	// ist die Session ID mit der aus dem Cookie identisch?
	if ( session_id() == $_COOKIE['PHPSESSID'] )
	{
		// alle Session-Daten vollständig?
		if ( $_SESSION['uid'] && $_SESSION['uip'] && $_SESSION['pw'] )
		{
			// User ID besteht nur aus Zahlen?
			if ( is_int($_SESSION['uid']) )
			{
				// Passwort ist entsprechend dem md5-Standard konform codiert?
				if ( preg_match('/^[A-Za-z0-9]{32}$/', $_SESSION['pw']) )
				{
					// ohne IP-Adresse läuft hier nichts!
					if ( $uip )
					{
						$uip = encode_ip($uip);
						// Ist die IP-Adresse des Nutzers weitestgehend unverändert? (wegen AOL werden nur die ersten beiden Blöcke verifiziert)
						if ( substr($_SESSION['uip'], 0, 4) == substr($uip, 0, 4) )
						{
							$sql = 'SELECT *
									FROM users
									WHERE uid = ' . $_SESSION['uid'] . '
									AND pw = \'' . $_SESSION['pw'] . '\'
									AND active = 1';
							if ( !($result = mysql_query($sql)) )
							{
								die('Fehler in Datei: ' . __FILE__ . '<br />Zeile: ' . __LINE__ . '<br />Fehlermeldung: ' . mysql_error() . '<br />Abfrage: ' . $sql);
							}
							// Stimmen die User ID und das Passwort mit den Daten in der Datenbank überein und ist das Benutzerkonto aktiv?
							if ( $user = @mysql_fetch_array($result, MYSQL_ASSOC) )
							{
								// Login geht in Ordnung! Die Benutzerdaten wurden erfolgreich ausgelesen.
								$login = true;
							}
							@mysql_free_result($result);
						}
					}
				}
			}
		}
	}
}

// aus Sicherheitsgründen wird die Session ID gelöscht, falls auch nur eine der Verifizierungen fehlgeschlagen ist
if ( !$login )
{
	session_destroy();
	header('Location: ' . $host . '?p=login');
	exit;
}

// ab hier geschützte Inhalte anzeigen
Die derzeitige Überprüfung sieht vor:
- ein Cookie muss vorhanden sein
- das Cookie muss eine PHP Session im richtigen Format enthalten (md5 verschlüsselt, aus Buchstaben und Zahlen bestehen und insgesamt 32 Zeichen lang sein)
- die Session ID muss mit der aus dem Cookie übereinstimmen
- die Session muss die Daten "User-ID, User-IP und Passwort" enthalten
- das Passwort muss im richtigen Format vorliegen (md5 verschlüsselt, aus Buchstaben und Zahlen bestehen und insgesamt 32 Zeichen lang sein)
- der Nutzer muss eine IP-Adresse haben
- die IP des Nutzers muss mit der aus der Session weitestgehend übereinstimmen. D.h. die ersten sechs Zahlen von insgesamt neun müssen gleich sein: 255.255.255. Eine härtere Prüfung würde dazu führen, dass AOL Nutzer ständig ausgeloggt werden.
- die Daten aus der Session müssen mit denen aus der Datenbank übereinstimmen (User-ID und Passwort)
- der Nutzer muss ein aktives Benutzerkonto besitzen
- sollte auch nur ein Versuch eine Session zu verifizieren fehlschlagen, so wird diese komplett gelöscht

Das einzige was mich noch "nervt" ist, dass beim ersten Besuch der Seite alle Links mit der Session-ID ausgestattet werden. Ich könnte auf der Zielseite einfach noch eine zusätzliche Weiterleitung einbauen um das zu vermeiden, aber das ist ja keine wirklich sinnvolle Lösung.

Fragen:
- Gibt es eine Option mit der ich die Ausgabe der PHPSESSID gänzlich verhindern kann?
- Welche Sicherheitsabfragen würdet ihr noch hinzufügen oder weglassen?

Gruß
meine Foren: http://www.maxrev.de/communities.htm
Ich kaufe Dein Forum! Angebote bitte an marc at gutt punkt it
Benutzeravatar
larsneo
Mitglied
Beiträge: 2622
Registriert: 07.03.2002 15:23
Wohnort: schwäbisch gmünd
Kontaktdaten:

Beitrag von larsneo »

- Gibt es eine Option mit der ich die Ausgabe der PHPSESSID gänzlich verhindern kann?
bei postnuke verwenden wir eine recht radikale variante um php klar zu machen, dass die sessionwerte nur in den keks gehören:

Code: Alles auswählen

        // PHP configuration variables
        ini_set('session.use_trans_sid', 0);         // Stop adding SID to URLs
        @ini_set('url_rewriter.tags', '');           // some environments dont allow this value to be set causing an error that prevents installation
         ini_set('session.use_cookies', 1);           // Use cookie to store the session ID
- Welche Sicherheitsabfragen würdet ihr noch hinzufügen oder weglassen?
zuerst einmal ein wenig lesestoff als eigenwerbung - im php security guide finden sich zum thema session zwei abschnitte:
4.1 Session Fixation
4.2 Session-Hijacking

als weiteren anhaltspunkt vielleicht auch einmal das postnuke session management *klick* - dort haben wir versucht, über einen eigenen session handler das maximum an sicherheit zu erreichen. insbesondere die bindung von session an den useragent, der schutz vor pollution sowie das session_regenerate sind vielleicht ganz interessante punkte.

btw: zum thema addslashes in mysql-queries hat chris shiflett mal was interessantes geschrieben addslashes() Versus mysql_real_escape_string()
gruesse aus dem wilden sueden
larsneo
..::[krapohl.net]::..
Benutzeravatar
S2B
Ehemaliges Teammitglied
Beiträge: 3258
Registriert: 10.08.2004 22:48
Wohnort: Aachen
Kontaktdaten:

Beitrag von S2B »

Ich würde nicht sagen, dass die von dir hinzugefügten Überprüfungen im phpBB-Sessionsystem fehlen. Viele der Überprüfungen werden schon allein dadurch vorgenommen, dass man die Session aus der Datenbank ausliest. Wenn dabei eine Session-ID z.B. nur 10 Zeichen haben sollte, wird nie eine Session gefunden, da nun mal alle Session-IDs in der Datenbank 32 Zeichen lang sind. Genauso muss man nicht unbedingt überprüfen, ob die ID des Benutzers eine Zahl ist, wenn man sie - mit intval() präpariert - in einem SQL-Query verwendet. Wenn die ID keine Zahl sein sollte, dann wird eben auf die Benutzer-ID 0 bzw. 1 überprüft, und da die beide nicht vorhanden sind, wird keine Session eröffnet.

Wirklich sicherheitsrelevante Änderungen wären meiner Meinung nach nur die Deaktivierung der als GET-Parameter übermittelten Session-ID oder die Überprüfung des UserAgents.
Gruß, S2B
Keinen Support per ICQ/PM!
Hier kann man meine PHP-Skripte und meine MODs für phpBB runterladen.
Benutzeravatar
mgutt
Mitglied
Beiträge: 2999
Registriert: 31.08.2004 16:44
Wohnort: Hennef
Kontaktdaten:

Beitrag von mgutt »

S2B hat geschrieben:Genauso muss man nicht unbedingt überprüfen, ob die ID des Benutzers eine Zahl ist, wenn man sie - mit intval() präpariert - in einem SQL-Query verwendet.
Es ist aber eine minimale Sicherheitsabfrage. Denn wenn die Zahl ein String ist, dann wird sie abgelehnt. Klar könnte ich sie über intval() wandeln, aber dann weiß ich nur im Falle einer 0 erst bei der Datenbankabfrage, ob die ID richtig war.

So spare ich mir also die Datenbankabfrage gänzlich. Klar könnte ich auf empty() prüfen, aber so ist es nur eine Bedingung is_int() und nicht intval() und empty() zusammen plus die neue Zuweisung des Variableninhaltes. Denn wenn man "$_SESSION['uid'] = intval($_SESSION['uid'])" setzt schreibt php die Daten neu in die Sessionsdatei.

@ larsneo
Diesen Part verstehe ich nicht:

Code: Alles auswählen

<?php session_start(); if (!isset($_SESSION['initiated'])) { session_regenerate_id(); $_SESSION['initiated'] = true; } ?>
Wenn session_start() aufgerufen wird, passieren doch zwei Sachen. Entweder die SID wurde in dem Cookie erkannt und die Session wird demnach fortgeführt oder es wurde keine SID erkannt und eine SID wird generiert.

Warum muss dann zusätzlich eine Variable gesetzt werden? Das würde doch heißen, wenn session_start() das erste Mal aufgerufen wird, dass die SID einfach nur geändert wird. Also im Endeffekt 2x session_start() aufgerufen wird. Im Fall einer bekannten SID aus einem Cookie ändert sich dagegen gar nichts.

Wenn ein Nutzer eine nicht vorhandene SID nutzt, dann passiert doch gar nichts oder wie? Wo ist das der Trick, den ich nicht verstehe?
meine Foren: http://www.maxrev.de/communities.htm
Ich kaufe Dein Forum! Angebote bitte an marc at gutt punkt it
Antworten

Zurück zu „Coding & Technik“