Hallo mensen ben ik weer met een vraagje.

Ik heb een website gemaakt met een loginsysteem dmv $_SESSIONS maar nu klaagde sommige mensen dat ze iedere keer uitgelogd waren. Ik vond het zelf ook wel vervelend dus ik heb nu een $_COOKIE variant verzonnen maar omdat het mijn eerste keer is heb ik daar toch wel twijfels over.

Daarom hoor ik hier graag op of aanmerkingen. (In mijn $_SESSION sla ik de userid van het ingelogde account op waardoor je op iedere pagina bent aangemeld.)

Ten eerste heb ik in mijn database een tabel gemaakt met de kolommen:
- id (PRIMARY,AUTO_INCREMENT)
- userid
- cookiehash (UNIQUE)
- timestamp

Vervolgens heb ik dit in mijn inlogscript verwerkt:

<?php
// Als "Onthoud Mij" is aangeklikt
						if(isset($_POST['onthoudmij'])) {
							
							$cookiehash = md5(uniqid(microtime(), true));
							
							$SQLSelectLoginSessies = "SELECT userid
													  FROM login_sessies
													  WHERE userid = '" . $row['id'] . "'";
							$ResultLoginSessies = $conn->query($SQLSelectLoginSessies);
							if ($ResultLoginSessies->num_rows > 0) {
								
								$sqlUpdateLoginSessies = "UPDATE login_sessies
														  SET cookiehash='" . $cookiehash . "' 
														  WHERE userid='" . $row['id'] . "'";
								if ($conn->query($sqlUpdateLoginSessies) === TRUE) {
									
									setcookie("Onthoudmij", $cookiehash, time() + (86400 * 30), "/", null, true, true); // 30 dagen
									header('Location: ' . $terug . '');
									exit;
									
								} else {
									
									header('Location: ' . $pagina . '&code=0003'); // Kan cookie niet zetten.
									exit;
									
								}
								
							} else {
								
								$SQLInsertLoginSessies = "INSERT INTO login_sessies (userid,cookiehash)
														  VALUES ('" . $row['id'] . "','" . $cookiehash . "')";
								if ($conn->query($SQLInsertLoginSessies) === TRUE) {
									
									setcookie("Onthoudmij", $cookiehash, time() + (86400 * 30), "/", null, true, true); // 30 dagen
									header('Location: ' . $terug . '');
									exit;
									
								} else {
									
									header('Location: ' . $pagina . '&code=0003'); // Kan cookie niet zetten.
									exit;
									
								}
								
							}							
							
							
						} 
?>


En bovenaan mijn index.php pagina dit:

<?php
// Check voor Cookie
if(isset($_COOKIE['Onthoudmij'])) {
	
	$CheckCookie = mysqli_real_escape_string($conn,$_COOKIE['Onthoudmij']);
	
	$SQLCheckCookie = "SELECT userid,cookiehash
					   FROM login_sessies
					   WHERE cookiehash = '" . $CheckCookie . "'";
	$ResultCheckCookie = $conn->query($SQLCheckCookie);
	if ($ResultCheckCookie->num_rows > 0) {
		
		while($RowLoginSessies = $ResultCheckCookie->fetch_assoc()) {
		
			$_SESSION['user'] = $RowLoginSessies['userid'];
			
		}
		
	}
		
}
?>



Dus wanneer er een cookie is gevonden op de clïent met een hash die overeenkomt met een hash in mijn tabel dan wordt dezelfde sessie gemaakt als wanneer je voorheen met sessies inlogde.
Doordat de kolom cookiehash in mijn tabel UNIQUE moet zijn kan je nooit een andere userid krijgen.
Zou je jouw topictitel aan willen passen zodat deze je vraag- of probleemstelling aanduidt?

Alvast bedankt!
Grappig, want sessies gebruiken ook gewoon cookies. In plaats van te proberen een andere manier te verzinnen is het veel verstandiger om de reden van het uitloggen te achterhalen.
Ben van Velzen op 19/01/2016 16:18:38

Grappig, want sessies gebruiken ook gewoon cookies. In plaats van te proberen een andere manier te verzinnen is het veel verstandiger om de reden van het uitloggen te achterhalen.


Hallo Ben,

Ik weet dat je de session lifetime kan verlengen maar dat leek mij zo onveilig??

@Danny, zou je het niet omdraaien? Controleer eerst of $_SESSION['user'] bestaat, en dan (anders) of $_COOKIE['Onthoudmij'] bestaat om een sessie eventueel "door te starten"? EDIT anders is dit niet nodig, je sessie bestaat nog.

Ook zou ik, als je zo'n doorstart maakt, de cookiehash verversen. EDIT: of misschien nog beter, ververs deze zolang je sessie bestaat.

Daarnaast zou je kunnen overwegen om een langere hash te pakken zodat deze moeilijker te raden is.

Zo te zien maak je al gebruik van HTTPS? Volgens mij is deze opzet dan redelijk veilig (dataverkeer kan niet ontcijferd worden en cookies kunnen alleen via HTTP ingesteld worden), but don't take my word for it.

Op dit moment is de hash het enige identificerende attribuut. Afhankelijk van de situatie zou je ook een IP of user agent kunnen verwerken in/bij de hash.
@Thomas: Cool om te horen dat de methode die ik heb verzonnen redelijk veilig is.

Ik zal inderdaad boven de isset($_COOKIE eerst een check op mijn user session maken. En ik gebruik inderdaad HTTPS:// en via php.net zag ik dat je cookies op HTTPS kan afdwingen. Ik weet echter niet of dit nog nadelige gevolgen heeft.

En ik zat er ook aan te denken om de hash bij iedere opvraag van een pagina te verversen maar ik was bang dat de load naar de database dan te groot zal worden. Of kan die wel veel hebben?

Momenteel gebruik ik
<?phpmd5(uniqid(microtime(), true))?>
voor de hash maar welke encryptie methode stel jij voor? Het lijkt mij nu al niet dat iemand dit zal raden aangezien het op niets gebasseerd is (een naam bijvoorbeeld).

Danny von Gaal op 19/01/2016 20:57:56

Momenteel gebruik ik
<?phpmd5(uniqid(microtime(), true))?>
voor de hash maar welke encryptie methode stel jij voor? Het lijkt mij nu al niet dat iemand dit zal raden aangezien het op niets gebasseerd is (een naam bijvoorbeeld).

De functie uniqid() genereert een pseudo-aselecte ID op basis van de systeemklok. Genereer maar eens 100 ID's in een for-loop en je zult zien dat het eerste gedeelte van de string oploopt met de tijd.

Vandaar deze waarschuwing:

Warning This function does not create random nor unpredictable strings. This function must not be used for security purposes. Use a cryptographically secure random function/generator and cryptographically secure hash functions to create unpredictable secure IDs.

Aangezien uniqid() al een string gebaseerd op de systeemklok geeft, heeft het niet zoveel zin om de $prefix in de eerste parameter met microtime() op de systeemtijd te zetten. Je doet dan namelijk iets dat de functie zelf al doet:


<?php
$cookiehash = md5(uniqid(microtime(), true));
?>


Je kunt hier beter met bijvoorbeeld mt_rand() een aselect getal genereren:


<?php
$cookiehash = md5(uniqid(mt_rand(), true));
?>


Je gebruikt al true voor de tweede parameter $more_entropy van uniqid() om de ID minder voorspelbaar te maken. Je kunt daar zelf een flinke schep bovenop doen door een aselect getal toe te voegen:


<?php
$cookiehash = md5(uniqid(mt_rand(), true) . mt_rand());
?>


Aangezien mt_rand() al is gebruikt voor de prefix, zou ik echter een andere functie dan mt_rand() gebruiken voor deze postfix, bijvoorbeeld:


<?php
$cookiehash = md5(uniqid(mt_rand(), true) . bin2hex(openssl_random_pseudo_bytes(128)));
?>


Tot slot zit de hashfunctie md5() nog in de weg. Die kun je wel gebruiken voor interne hashes, maar beter niet meer voor beveiligingsdoeleinden. Op zijn minst gebruik je SHA-1:


<?php
$cookiehash = sha1(uniqid(mt_rand(), true) . bin2hex(openssl_random_pseudo_bytes(128)));
?>


Nog beter is een langere en sterkere variant uit de SHA-familie:


<?php
$cookiehash = hash('sha512', uniqid(mt_rand(), true) . bin2hex(openssl_random_pseudo_bytes(128)));
?>
Mja, elk systeem heeft zijn zwakste schakel.

Ook mt_rand() is niet cryptographically secure.

In dat artikel wordt het concept nonce aangehaald, wat wellicht een idee is om te gebruiken, maar heeft misschien ook haken en ogen.

In dit artikel staat wel een aardig idee voor een IP-loze oplossing met net iets meer "identificatie" dan simpelweg een hash, en ook in de user comments staan goede aanvullingen, met name een veilig(er) alternatief voor mt_rand():
lezen van /dev/urandom (unix).

Op PHP.net worden ook enkele alternatieven genoemd: random_int(), random_bytes() of openssl_random_pseudo_bytes() (die hierboven al gebruikt wordt) en ook daar wordt in de comments /dev/urandom aangehaald.
Eens hoor Thomas, maar in dit specifieke geval moeten we dit er eigenlijk nog bij betrekken:
Danny von Gaal op 19/01/2016 16:05:27

Ten eerste heb ik in mijn database een tabel gemaakt met de kolommen:
- id (PRIMARY,AUTO_INCREMENT)
- userid
- cookiehash (UNIQUE)
- timestamp
[...]
Doordat de kolom cookiehash in mijn tabel UNIQUE moet zijn kan je nooit een andere userid krijgen.

En dat in combinatie met de query:

<?php
$SQLCheckCookie = "SELECT userid,cookiehash
                   FROM login_sessies
                   WHERE cookiehash = '" . $CheckCookie . "'";
?>

Dat is namelijk niet zomaar een random ID, maar een GUID of UUID. Ik begrijp wel waarom Danny uniqid() heeft gebruikt voor het genereren van die Global/Universal Unique ID, maar ik denk dat de functie UUID() van MySQL in een CHAR(36)-kolom daarvoor een beter alternatief is.

Nou is UUID() wel uniek, maar helaas ook voorspelbaar en daardoor te raden. Als je daar echter nog een (pseudo)random ID aan toevoegt — of naast zet, voor mijn part in een aparte kolom tot het maximum CHAR(255) — dan heb je een oplossing die zowel uniek als onvoorspelbaar is.
Fair enough. Op welke moment(en) zou dit cookie (hash + levensduur) ververst moeten worden? Enkel als deze gebruikt wordt om een sessie door te starten? En is het bijvoorbeeld (ook) verstandig om elk request session_regenerate_id(true) te gebruiken?

Ik vind de discussie zeker interessant, maar wil tegelijkertijd eigenlijk ook wel naar een soort van concrete aanbeveling toe (en Danny waarschijnlijk ook :D).
Ik heb de hash nu vervangen voor

<?php
$cookiehash = hash('sha512', uniqid(mt_rand(), true) . bin2hex(openssl_random_pseudo_bytes(128)));
?>


Maar om nog even terug te komen op dat de hash steeds wordt vernieuwd? Dan moet ik bij iedere pagina opvraag dus mijn database updaten en een nieuwe cookie setten? Zal dat uiteindelijk met veel gebruikers niet teveel verkeer generen en mijn database belasten? Of doen alle sites dat?

Ik zou het wel fijn vinden want dan kan ik laten zien wanneer de gebruiker voor het laatst online was. :)

Reageren