inlog systeem veilig met $_SESSIONS?
Ik ben een systeem aan het maken voor een bedrijf waarbij ik zoveel mogelijk wil voorkomen dat hackers het systeem in kunnen komen. Ik weet dat dit onmogelijk is als iemand het echt op je gemunt heeft, maar alle beetjes helpen denk ik dan maar.
Ik ben van plan om op de volgende manieren te werk te gaan:
1. gebruiker logt in waarbij bcrypt hashes van het ingevoerde wachtwoord en de hash die in de db staat worden vergeleken.
2. als de hashes kloppen worden er session variabelen aangemaakt met gegevens van de gebruiker. Zie:
[script]
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
//controleer wachtwoord
$password = $database->prepare('SELECT wachtwoord, gebruikers_id, email_adres FROM gebruikers WHERE gebruikers_naam = :gebruikers_naam');
$password->execute(array('gebruikers_naam' => $_POST['username']));
$hash = $password->fetch();
if (password_verify($_POST['password'], $hash['wachtwoord'])) {
$_SESSION['gebruikers_naam'] = $_POST['username'];
$_SESSION['gebruikers_id'] = $hash['gebruikers_id'];
$_SESSION['email_adres'] = $hash['email_adres'];
$_SESSION['rememberMe'] = 1;
setcookie('Remember',$_SESSION['rememberMe']);
} else {
$err[]='Verkeerde gebruikersnaam en/of wachtwoord!';
}
?>
//controleer wachtwoord
$password = $database->prepare('SELECT wachtwoord, gebruikers_id, email_adres FROM gebruikers WHERE gebruikers_naam = :gebruikers_naam');
$password->execute(array('gebruikers_naam' => $_POST['username']));
$hash = $password->fetch();
if (password_verify($_POST['password'], $hash['wachtwoord'])) {
$_SESSION['gebruikers_naam'] = $_POST['username'];
$_SESSION['gebruikers_id'] = $hash['gebruikers_id'];
$_SESSION['email_adres'] = $hash['email_adres'];
$_SESSION['rememberMe'] = 1;
setcookie('Remember',$_SESSION['rememberMe']);
} else {
$err[]='Verkeerde gebruikersnaam en/of wachtwoord!';
}
?>
[/script]
3. om te controleren of een gebruiker is ingelogd voordat de pagina wordt getoond gebruik ik:
[script]
Code (php)
[/script]
Naast deze 3 punten wil ik de user agent opslaan in een sessie variabele als de gebruiker inlogt. Op elke volgende pagina controleer ik of de user agent hetzelfde is gebleven anders vernietig ik de sessie.
Denken jullie dat ik zo goed op weg ben, of ben ik hier verkeerd bezig en/of hebben jullie nog tips?
Met vriendelijke groet,
Davey Mat
Dankzij jouw zou je op een slimme manier dus al een naam, id en emailadres in handen kunnen krijgen.
Haal deze gewoon uit de database wanneer je ze nodig hebt!
Je zou een hash kunnen maken van de useragent en deze controleren. De useragent is vrij uniek, maar geef deze dan niet gewoon weg, maar gebruik hiervoor bijvoorbeeld ook bcrypt.
Sla deze useragent op in een aparte tabel en ruim deze op na een bepaalde tijd inactief.
Daarnaast zou je nog bij elke pagina die je opent een unieke string kunnen opslaan in je database en session en deze met elkaar vergelijken. Als iemand de string in handen krijgt is deze maar beperkt geldig.
>> Onthoud dat SESSION eigenlijk gewoon COOKIES zijn (maar dan server side) maar evengoed uit te lezen door de gebruiker
Wat bedoel je hiermee? Het lijkt nu alsof je zegt dat een gebruiker het sessiebestand kan inzien en dat is natuurlijk niet zo.
Michael - op 25/04/2014 11:11:52:
Waarom zou je zoveel willen opslaan in SESSIONS? Onthoud dat SESSION eigenlijk gewoon COOKIES zijn (maar dan server side) maar evengoed uit te lezen door de gebruiker.
Dankzij jouw zou je op een slimme manier dus al een naam, id en emailadres in handen kunnen krijgen.
Haal deze gewoon uit de database wanneer je ze nodig hebt!
Dankzij jouw zou je op een slimme manier dus al een naam, id en emailadres in handen kunnen krijgen.
Haal deze gewoon uit de database wanneer je ze nodig hebt!
Een $_SESSION['email_adres'] wordt gewoon op de server opgeslagen hoor. Die ziet de client dus niet. Het enige dat de client ziet, is de hash van de sessie-ID.
Wat wél mogelijk is, is bijvoorbeeld session hijacking: wie de cookie met de sessie-ID in handen krijgt, kan de sessie kapen.
Wat je daarom om te beginnen moet doen, is HTTPS met SSL gebruiken. Dat maakt het aftappen van het HTTP-verkeer bijna onmogelijk, waardoor ook de cookie met de sessie-ID niet zo makkelijk in verkeerde handen kan vallen.
Wat je daarnaast kunt doen, is het raden van een geldige sessie-ID moeilijker maken. Standaard is het namelijk slechts een MD5-hash. Daarvan kun je om te beginnen een langere en dus sterkere SHA-1-hash maken:
Code (php)
1
2
3
4
5
6
2
3
4
5
6
<?php
// MD5 (128 bits) vervangen door SHA-1 (160 bits)
ini_set('session.hash_function', '1');
// PHP-sessie starten of hervatten
session_start();
?>
// MD5 (128 bits) vervangen door SHA-1 (160 bits)
ini_set('session.hash_function', '1');
// PHP-sessie starten of hervatten
session_start();
?>
Wil je er voor eens en altijd van af zijn, dan doe je dat in php.ini:
Code (php)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
; Select a hash function for use in generating session ids.
; Possible Values
; 0 (MD5 128 bits)
; 1 (SHA-1 160 bits)
; This option may also be set to the name of any hash function supported
; by the hash extension. A list of available hashes is returned by the
; hash_algos() function.
; http://php.net/session.hash-function
session.hash_function = 1
; Possible Values
; 0 (MD5 128 bits)
; 1 (SHA-1 160 bits)
; This option may also be set to the name of any hash function supported
; by the hash extension. A list of available hashes is returned by the
; hash_algos() function.
; http://php.net/session.hash-function
session.hash_function = 1
In PHP >= 5.3.0 kun je er nog een schepje bovenop gooien door MD5 of SHA-1 te vervangen door een variant van SHA-2, bijvoorbeeld met een 512-bits hash:
Code (php)
1
2
3
4
5
6
2
3
4
5
6
<?php
// MD5 (128 bits) vervangen door SHA-2 (512 bits)
ini_set('session.hash_function', 'sha512');
// PHP-sessie starten of hervatten
session_start();
?>
// MD5 (128 bits) vervangen door SHA-2 (512 bits)
ini_set('session.hash_function', 'sha512');
// PHP-sessie starten of hervatten
session_start();
?>
Ik heb even snel iets gemaakt:
functions.php:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
//sla user agent encrypted op in db
//plaats deze functie nadat $_SESSION['gebruikers_id'] is aangemaakt
function save_ua() {
$encrypted_ua = password_hash($_SERVER['HTTP_USER_AGENT'], PASSWORD_BCRYPT);
$save_ua = $database->prepare('UPDATE gebruikers SET user_agent=:user_agent WHERE gebruikers_id = :gebruikers_id');
$save_ua->execute(array('user_agent' => $encrypted_ua, 'gebruikers_id' => $_SESSION['gebruikers_id']));
}
//controleer of user agent zelfde is als in db, anders sessie destroyen
function check_ua() {
$encrypted_ua = password_hash($_SERVER['HTTP_USER_AGENT'], PASSWORD_BCRYPT);
$check_ua = $database->prepare('SELECT user_agent FROM gebruikers WHERE gebruikers_id = :gebruikers_id');
$check_ua->execute(array('gebruikers_id' => $_SESSION['gebruikers_id']));
$ua = $check_ua->fetch();
if (!(password_verify($encrypted_ua, $ua['user_agent']))) {
$_SESSION = array();
destroy_session();
header('Location: index.php');
exit;
}
}
//genereer sessie id en plaats deze in sessie variabele + database
//plaats deze functie nadat $_SESSION['gebruikers_id'] is aangemaakt
function set_session_id() {
$session_id = bin2hex(openssl_random_pseudo_bytes(16));
$set_session_id = $database->prepare('UPDATE gebruikers SET session_id=:session_id WHERE gebruikers_id = :gebruikers_id');
$set_session_id->execute(array('session_id' => $session_id, 'gebruikers_id' => $_SESSION['gebruikers_id']));
$_SESSION['session_id']=$session_id;
}
//controleer of sessie id variabele en databse sessie id overeenkomen en maak daarna een nieuwe aan
function check_session_id() {
$session_id=$_SESSION['session_id'];
$check_session_id = $database->prepare('SELECT session_id FROM gebruikers WHERE gebruikers_id = :gebruikers_id');
$check_session_id->execute(array('gebruikers_id' => $_SESSION['gebruikers_id']));
$session_id_db = $check_session_id->fetch();
if ($session_id!=$session_id_db['session_id']) {
$_SESSION = array();
destroy_session();
header('Location: index.php');
exit;
} else {
$session_id = bin2hex(openssl_random_pseudo_bytes(16));
$set_session_id = $database->prepare('UPDATE gebruikers SET session_id=:session_id WHERE gebruikers_id = :gebruikers_id');
$set_session_id->execute(array('session_id' => $session_id, 'gebruikers_id' => $_SESSION['gebruikers_id']));
$_SESSION['session_id']=$session_id;
}
}
?>
//sla user agent encrypted op in db
//plaats deze functie nadat $_SESSION['gebruikers_id'] is aangemaakt
function save_ua() {
$encrypted_ua = password_hash($_SERVER['HTTP_USER_AGENT'], PASSWORD_BCRYPT);
$save_ua = $database->prepare('UPDATE gebruikers SET user_agent=:user_agent WHERE gebruikers_id = :gebruikers_id');
$save_ua->execute(array('user_agent' => $encrypted_ua, 'gebruikers_id' => $_SESSION['gebruikers_id']));
}
//controleer of user agent zelfde is als in db, anders sessie destroyen
function check_ua() {
$encrypted_ua = password_hash($_SERVER['HTTP_USER_AGENT'], PASSWORD_BCRYPT);
$check_ua = $database->prepare('SELECT user_agent FROM gebruikers WHERE gebruikers_id = :gebruikers_id');
$check_ua->execute(array('gebruikers_id' => $_SESSION['gebruikers_id']));
$ua = $check_ua->fetch();
if (!(password_verify($encrypted_ua, $ua['user_agent']))) {
$_SESSION = array();
destroy_session();
header('Location: index.php');
exit;
}
}
//genereer sessie id en plaats deze in sessie variabele + database
//plaats deze functie nadat $_SESSION['gebruikers_id'] is aangemaakt
function set_session_id() {
$session_id = bin2hex(openssl_random_pseudo_bytes(16));
$set_session_id = $database->prepare('UPDATE gebruikers SET session_id=:session_id WHERE gebruikers_id = :gebruikers_id');
$set_session_id->execute(array('session_id' => $session_id, 'gebruikers_id' => $_SESSION['gebruikers_id']));
$_SESSION['session_id']=$session_id;
}
//controleer of sessie id variabele en databse sessie id overeenkomen en maak daarna een nieuwe aan
function check_session_id() {
$session_id=$_SESSION['session_id'];
$check_session_id = $database->prepare('SELECT session_id FROM gebruikers WHERE gebruikers_id = :gebruikers_id');
$check_session_id->execute(array('gebruikers_id' => $_SESSION['gebruikers_id']));
$session_id_db = $check_session_id->fetch();
if ($session_id!=$session_id_db['session_id']) {
$_SESSION = array();
destroy_session();
header('Location: index.php');
exit;
} else {
$session_id = bin2hex(openssl_random_pseudo_bytes(16));
$set_session_id = $database->prepare('UPDATE gebruikers SET session_id=:session_id WHERE gebruikers_id = :gebruikers_id');
$set_session_id->execute(array('session_id' => $session_id, 'gebruikers_id' => $_SESSION['gebruikers_id']));
$_SESSION['session_id']=$session_id;
}
}
?>
De set functies roep ik aan nadat de gebruiker succesvol inlogt. De check functies zet ik boven iedere pagina waar een gebruiker daarna naar toe kan.
Daarnaast heb ik als de gebruiker uitlogt:
Code (php)
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['logout'])) {
$save_ua = $database->prepare('UPDATE gebruikers SET user_agent=NULL, session_id=null WHERE gebruikers_id = :gebruikers_id');
$save_ua->execute(array('gebruikers_id' => $_SESSION['gebruikers_id']));
$_SESSION = array();
session_destroy();
header("Location: index.php");
exit;
}
?>
if(isset($_GET['logout'])) {
$save_ua = $database->prepare('UPDATE gebruikers SET user_agent=NULL, session_id=null WHERE gebruikers_id = :gebruikers_id');
$save_ua->execute(array('gebruikers_id' => $_SESSION['gebruikers_id']));
$_SESSION = array();
session_destroy();
header("Location: index.php");
exit;
}
?>
Denken jullie dat ik deze code zo goed heb gemaakt en dat dit op deze manier zinvol is?
---TOEVOEGING:
Ik heb overigens de tip van Ward gebruikt door dit bovenaan de inlog pagina te zetten:
Code (php)
1
2
3
4
5
6
2
3
4
5
6
session_name('Login');
// Cookie voor 2 weken
session_set_cookie_params(2*7*24*60*60);
// MD5 (128 bits) vervangen door SHA-2 (512 bits)
ini_set('session.hash_function', 'sha512');
session_start();
// Cookie voor 2 weken
session_set_cookie_params(2*7*24*60*60);
// MD5 (128 bits) vervangen door SHA-2 (512 bits)
ini_set('session.hash_function', 'sha512');
session_start();
Gewijzigd op 25/04/2014 13:35:02 door Davey Mat
Tja... ik weet niet hoor, maar als je iemand password in een sessie gaat zetten, ben je niet helemaal slim bezig lijkt mij. Een naam e.d. kun je gewoon in sessie zetten. Waarom zou je die iedere keer opnieuw gaan ophalen?
Maareh... hoe weet jij eigenlijk mijn wachtwoord?! ;)
Daarnaast begrijp ik Micheal- dat sessie variabelen niet als veilig moeten worden beschouwd, dus zal ik enkel de gebruikers_id uit de database in een sessie variabele opslaan. De rest vraag ik iedere keer met een query op.
Ik durf ook te wedden dat wel eens het gebruikers id wordt gebruikt als inlog controle (Davey?).
Ik vind dat je met sessies het zelfde om moet gaan als cookies (betreft veiligheid).
Sorry dat ik je wachtwoord hier poste :P
Ja, niet meer doen hoor! Nu moet ik overal al m'n wachtwoorden aanpassen. Ik heb besloten om nu meer tekens te gebruiken. Eerst waren het er 6, nu doe ik er voortaan 10. Het zijn allemaal 4'en, maar uiteraard ben ik niet zo dom om te vertellen in welke volgorde ze staan!
@Ozzie ha ha :) Flauw hoor (Nee ik hoef geen SALT).
Verder heeft encryptie van de user agent in de database niet veel zin, want het is slechts een extern gegeven waarvan je alleen maar wilt weten of het gewijzigd is. Daarvoor voldoet een asymmetrische hash.
Je kunt hier zowel strengere als sneller code gebruiken, bijvoorbeeld inclusief een IP-adres:
Code (php)
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
<?php
function save_ua()
{
$hashed_ua = sha1($_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR']);
$stmt = $database->prepare('UPDATE gebruikers SET user_agent=:user_agent WHERE gebruikers_id = :gebruikers_id');
$stmt->execute(array('user_agent' => $hashed_ua, 'gebruikers_id' => $_SESSION['gebruikers_id']));
}
?>
function save_ua()
{
$hashed_ua = sha1($_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR']);
$stmt = $database->prepare('UPDATE gebruikers SET user_agent=:user_agent WHERE gebruikers_id = :gebruikers_id');
$stmt->execute(array('user_agent' => $hashed_ua, 'gebruikers_id' => $_SESSION['gebruikers_id']));
}
?>
Davey Mat op 25/04/2014 13:29:06:
Daarnaast begrijp ik Micheal- dat sessie variabelen niet als veilig moeten worden beschouwd, dus zal ik enkel de gebruikers_id uit de database in een sessie variabele opslaan. De rest vraag ik iedere keer met een query op.
Nee, dat is zonde. De sessievariabelen zijn veilig opgeslagen in (meestal) een file cache in een server-directory buiten de root, waar helemaal niemand bij kan. Je verliest een van de voordelen van sessies als je dat vervangt door databaseverkeer. Je hebt dan bijvoorbeeld meer databaseverbindingen nodig en je hebt ze langer nodig.
Wat ik wel nog mis, is het loggen van fouten en het blokkeren van een account of een IP-adres. Je vernietigt nu de sessie, maar na de redirect kan iemand gewoon een nieuwe poging wagen. Miljoenen keren zelfs...
Ip-adres kan tijdens een sessie veranderen. Lijkt me geen goed plan.
Bovendien hoef je de sessie bij een gewijzigde user agent of een ander IP-adres natuurlijk niet te vernietigen. Wel kun je constateren dat er ondertussen iets bij de client structureel anders is. Dat kán een reden zijn om ergens extra controles in te bouwen.
Bijvoorbeeld: normaliter mag je je accountgegevens na inloggen wijzigen, maar nu staat er een vlaggetje op rood en moet je ter bevestiging nog even opnieuw je wachtwoord invullen.
Er is meer tussen hemel en aarde dan true/false.
Ward van der Put op 25/04/2014 14:03:57:
Wat ik wel nog mis, is het loggen van fouten en het blokkeren van een account of een IP-adres. Je vernietigt nu de sessie, maar na de redirect kan iemand gewoon een nieuwe poging wagen. Miljoenen keren zelfs...
Precies en deze is echt heel belangrijk want dit is de manier op een password te raden.
dus na laten we zeggen een keer of 20 mislukte login direct het ip blokkeren.
Vervolgens denk ik altijd maar aan het afsluiten van je woonhuis:
De achterdeur draai je drie keer in het slot en als je thuis komt is je hele huis overhoop gehaald: blijkt het slaapkamerraam nog open te staan..
Dus:
- goede sterke wachtwoorden gebruiken voor de login maar ook voor de database en de ftp server.
- zo weinig mogelijk informatie prijsgeven bij foutieve inlogpogingen. enkel een melding 'Geen toegang' is wat mij betreft genoeg.
- Zoals al gezegd: gebruik een beveiligde internet-verbinding.
- Sla de wachtwoorden gecodeerd op in de database.
- Desgewenst log je de inlogpogingen. Indien er vele pogingen in korte tijd bijkomen kun je nog een email laten versturen naar de beheerder. Deze kan vervolgens beslissen of de site misschien tijdelijk offline moet.
- Overweeg het gebruik waar filters. Bijvoorbeeld: inloggen enkel vanuit nederland of enkel bepaalde ip-adressen
>> User agent kan tijdens een sessie ook veranderen...
Hoe kan een user agent tijdens een sessie veranderen?
Dit zal ik deze week ook allemaal gaan doen. Op het lijstje:
-user agent + ip adres controle waarna de gebruiker bij verandering nog wel content kan zien, maar niet kan aanpassen, verwijderen etc.
-loggen van fouten en ip adressen die aanmelden en/of brute force proberen waarna ik na bv 10-20x foutief inloggen binnen een uur het ip adres blokkeer(blokkeren is gewoon te vertrouwen als ik in php hiermee werk? Dus een database tabel met geblokkeerde ipadressen en dan elke keer als iemand een pagina opent kijken of dit van een geblokkeerd ip adres komt, dan een fout weergeven. Of is er een betere manier?)
-mail naar systeembeheer bij veel aanmeldingen op 1 gebruiker o.i.d. binnen een tijdsbestek van 1 uur.
-inloggen enkel via de ip reeksen van landen waar gebruikers vandaan komen (Nl/BE)
@Ozzie: Ik denk dat Ward bijvoorbeeld bedoelt dat als iemand de sessie van een gebruiker kan "stelen". De hacker kan daarna met deze sessie verbinding maken met de website, maar als hij/zij een andere user agent heeft dan merken we dat dus op en blokkeren we bepaalde functies. Daarnaast kan iedereen zijn user agent veranderen. Zie bijvoorbeeld: http://www.howtogeek.com/113439/how-to-change-your-browsers-user-agent-without-installing-any-extensions/