PHP sessions; hoe het wel moet
09-08-2010,22:36 door
Helaas, door een cross site scripting probleem op je favoriete website heeft een hacker je session cookie bemachtigd, ga niet langs start. De hacker heeft ondertussen toegang tot jouw sessie op de website, en daar gaat al je e-mail, je vriendjes of je matches. Ok, cross site scripting hoort niet voor te komen, maar een meerlaags security model had dit kunnen opvangen. Jammer dat PHP (en andere web programmeertalen) de sessies niet veilig implementeren. Hoogste tijd om het zelf te doen.
Omdat een webpagina eigenlijk stateless is, wordt tegenwoordig altijd een cookie gebruikt met hierin een sessionid. Dit id is uniek voor de sessie die jij met de website hebt, en geeft de server de mogelijkheid om informatie van jouw sessie bij te houden. Na het inloggen met gebruikersnaam Piet weet de server bijvoorbeeld dat de sessie met jouw sessionid is ingelogd als gebruiker Piet.
Hackers kunnen jouw sessionid op twee manieren bemachtigen. De eerste heet session-fixation, waarbij de hacker vooraf een sessionid bepaalt, en jou deze laat gebruiken. De tweede heet session-hijacking, waarbij de hacker je sessionid kaapt met behulp van cross site scripting. Mocht je meer willen weten over deze twee aanvallen, zoek dan op de termen 'session fixation', 'cross site scripting', 'session hijacking', dit artikel gaat juist over de beveiliging van de sessies.
Voor veilige sessies in PHP zul je zelf wat moeten programmeren. Als het goed is heb je al in een initialisatiescript een aanroep van session_start() staan. Er bestaan in PHP een paar functies om wat parameters van het session cookie aan te passen:
Met het lifetime argument kunnen we aangeven hoelang het cookie maximaal mag bestaan, dit kunnen we het beste op 0 zetten, zodat de cookies niet worden opgeslagen in de browser. De timeout van een sessie kunnen we beter zelf bepalen. Omdat er meerder PHP sites op dezelfde webserver kunnen draaien is het belangrijk het path en domein goed in te stellen. Vanwege dit is het belangrijk de session_name functie te gebruiken om een session-id cookie alleen voor jouw applicatie te laten genereren. Het secure argument geeft aan of het cookie alleen via SSL mag worden verzonden, wat natuurlijk essentieel is voor enige veiligheid. De optie httponly is nieuw in 5.2.0, en vertelt de browser dat Javascript geen toegang heeft tot dit cookie. Niet elke browser ondersteunt dit nog, maar het is in ieder geval een mooie extra maatregel. Voor mijn testapplicatie komt dit neer op:
De secure (cookie mag alleen via SSL worden verzonden) optie is leuk om te gebruiken, maar het cookie wordt bij een onbeveiligde verbinding nu wel vrolijk onversleuteld naar de gebruiker verzonden. Om dit tegen te gaan moeten we helemaal aan het begin controleren of er wel gebruik wordt gemaakt van een SSL verbinding, en anders de gebruiker redirecten.
Om beveiliging tegen session fixation te bieden, kunnen we in het sessieobject een waarde initialized toevoegen. Als deze waarde nog niet bestaat, is het een nieuwe sessie, en genereren we een nieuw random sessionid:
Tot slot is het mooi wanneer we het ip-adres van elke sessie bijhouden. Mocht een session-id dan worden gekaapt en gebruikt worden vanaf een ander systeem, dan zien we dit omdat het ip-adres anders is. Ik las dat er wordt geadviseerd om uit te gaan van de versie van de browser bij deze detectie. Het mag duidelijk zijn dat dit geen extra veiligheid biedt. Tegenwoordig is het spoofen van een HTTP header veld (waar de browser versie in wordt meegestuurd) veel eenvoudiger dan het spoofen van een ip-adres.
De complete implementatie in een mooi direct bruikbare functie, inclusief ip-adres is als volgt:
De principes in dit artikel kunnen uiteraard ook worden toegepast voor ASP, JSP en andere web-programmeertalen. Dit laten we liever over als oefening voor de lezers.
Omdat een webpagina eigenlijk stateless is, wordt tegenwoordig altijd een cookie gebruikt met hierin een sessionid. Dit id is uniek voor de sessie die jij met de website hebt, en geeft de server de mogelijkheid om informatie van jouw sessie bij te houden. Na het inloggen met gebruikersnaam Piet weet de server bijvoorbeeld dat de sessie met jouw sessionid is ingelogd als gebruiker Piet.
Hackers kunnen jouw sessionid op twee manieren bemachtigen. De eerste heet session-fixation, waarbij de hacker vooraf een sessionid bepaalt, en jou deze laat gebruiken. De tweede heet session-hijacking, waarbij de hacker je sessionid kaapt met behulp van cross site scripting. Mocht je meer willen weten over deze twee aanvallen, zoek dan op de termen 'session fixation', 'cross site scripting', 'session hijacking', dit artikel gaat juist over de beveiliging van de sessies.
Voor veilige sessies in PHP zul je zelf wat moeten programmeren. Als het goed is heb je al in een initialisatiescript een aanroep van session_start() staan. Er bestaan in PHP een paar functies om wat parameters van het session cookie aan te passen:
void session_set_cookie_params ( int lifetime
[, string path [,string domain [, bool secure
[, bool httponly]]]] )
string session_name ( [string name] )
Met het lifetime argument kunnen we aangeven hoelang het cookie maximaal mag bestaan, dit kunnen we het beste op 0 zetten, zodat de cookies niet worden opgeslagen in de browser. De timeout van een sessie kunnen we beter zelf bepalen. Omdat er meerder PHP sites op dezelfde webserver kunnen draaien is het belangrijk het path en domein goed in te stellen. Vanwege dit is het belangrijk de session_name functie te gebruiken om een session-id cookie alleen voor jouw applicatie te laten genereren. Het secure argument geeft aan of het cookie alleen via SSL mag worden verzonden, wat natuurlijk essentieel is voor enige veiligheid. De optie httponly is nieuw in 5.2.0, en vertelt de browser dat Javascript geen toegang heeft tot dit cookie. Niet elke browser ondersteunt dit nog, maar het is in ieder geval een mooie extra maatregel. Voor mijn testapplicatie komt dit neer op:
session_name($cookiename);
session_set_cookie_params(0,
"/~test/cs/test.php", "127.0.0.1",
true, true);
De secure (cookie mag alleen via SSL worden verzonden) optie is leuk om te gebruiken, maar het cookie wordt bij een onbeveiligde verbinding nu wel vrolijk onversleuteld naar de gebruiker verzonden. Om dit tegen te gaan moeten we helemaal aan het begin controleren of er wel gebruik wordt gemaakt van een SSL verbinding, en anders de gebruiker redirecten.
if (!$_SERVER['SSL_PROTOCOL']) {
header("Location: https://127.0.0.1/~test/cs/test.php");
exit;
}
Om beveiliging tegen session fixation te bieden, kunnen we in het sessieobject een waarde initialized toevoegen. Als deze waarde nog niet bestaat, is het een nieuwe sessie, en genereren we een nieuw random sessionid:
if (!$_SESSION || $_SESSION['initialized'] !== true) {
session_regenerate_id(true);
$_SESSION['initialized'] = true;
}
Tot slot is het mooi wanneer we het ip-adres van elke sessie bijhouden. Mocht een session-id dan worden gekaapt en gebruikt worden vanaf een ander systeem, dan zien we dit omdat het ip-adres anders is. Ik las dat er wordt geadviseerd om uit te gaan van de versie van de browser bij deze detectie. Het mag duidelijk zijn dat dit geen extra veiligheid biedt. Tegenwoordig is het spoofen van een HTTP header veld (waar de browser versie in wordt meegestuurd) veel eenvoudiger dan het spoofen van een ip-adres.
De complete implementatie in een mooi direct bruikbare functie, inclusief ip-adres is als volgt:
function securesession
($path, $domain, $cookiename, $secureurl) {
/* Force the usage of SSL */
if (!$_SERVER['SSL_PROTOCOL']) {
header("Location: $secureurl");
exit;
}
/* Set correct sessioncookie parameters */
session_name($cookiename);
session_set_cookie_params(0, $path, $domain, true, true);
session_start();
/* Test for a proxy */
$forward = "";
if (defined($_SERVER['REMOTE_ADDR'])) {
$forward = $_SERVER['REMOTE_ADDR'];
}
/* Force the creation of a sessionid */
if (!$_SESSION || $_SESSION['initialized'] !== true) {
session_regenerate_id(true);
$_SESSION['initialized'] = true;
$_SESSION['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['X_FORWARDED_FOR'] = $forward;
}
/* Test the user's ip and possible proxy */
if (($_SERVER['REMOTE_ADDR'] != $_SESSION['REMOTE_ADDR']) ||
($_SESSION['X_FORWARDED_FOR'] != $forward)) {;
$_SESSION = array();
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '',
time()-42000, $path,
$domain, false, true);
}
session_destroy();
exit();
}
securesession("/~test/cs",
"127.0.0.1", "TESTID",
"https://127.0.0.1/~test/cs/test.php");
print "Welcome";
?>
De principes in dit artikel kunnen uiteraard ook worden toegepast voor ASP, JSP en andere web-programmeertalen. Dit laten we liever over als oefening voor de lezers.









