Tutorials
Een veilig login systeem
Uitleg over hoe je een veilig login syteem kan maken
Pagina 1
Inleiding
Ik heb al best veel rond lopen zoeken over hoe je nou eigenlijk een goed login syteem maakt, en als je alles bij elkaar optelt kan je er ook wel uitkomen maar...
Ik dacht dus bij mezelf: kom laat ik is alles bij elkaar voegen en ff duidelijk uitleggen hoe je het nou moet maken. Wat ik hier nou niet neerzet is een volledig login syteem, maar alleen uitleg over hoe je het (veilig) zou kunnen maken.
Ik dacht dus bij mezelf: kom laat ik is alles bij elkaar voegen en ff duidelijk uitleggen hoe je het nou moet maken. Wat ik hier nou niet neerzet is een volledig login syteem, maar alleen uitleg over hoe je het (veilig) zou kunnen maken.
Pagina 2
Het formulier
Natuurlijk begint alles met een formuliertje dat je invult:
De ervaren mensen zullen het meteen zien: hier ontbreekt nogal wat aan.
Ten eerste, er staat geen method in het formulier.
Daardoor zal er dus automatisch de GET methode gebruikt worden, oftewel: iedereen die achter je staat zal in de browser je wachtwoord/gebruikersnaam kunnen lezen!
Dus; toevoegen:
Ten tweede, veel mensen hebben de slechte gewoonte om wachtwoorden op te slaan.
Dit valt (voor sommige browsers, IE zeker) te voorkomen. Dit door het name atribut te verwijderen en daarvoor in de plaats alleen een id velt te gebruiken.
Het goede formulier wordt dus:
Als je nu het vooraf gaande login formulier gebruikt en je drukt op verzenden zullen je gegevens verstuurt worden naar de server. Niks mis mee zal je vast denken, niet helemaal.
Als je zou inloggen zou de hacker de verzonden data kunnen onderscheppen en alles ligt dan zo voor z'n neus. Hoe te voorkomen?
Simpel, client-side het wachtwoord hashen, dus zeg maar een soort javascript sha1() functie gebruiken.
Download hier of zie laatste pagina van deze tutorial voor als hij een dode link aangeeft.
De nieuwe code wordt dan:
<form action="login.php">
Gebruikersnaam:<input type="text" name="username" />
Wachtwoord:<input type="password" name="password" />
<input type="submit" value="Login" />
</form>
De ervaren mensen zullen het meteen zien: hier ontbreekt nogal wat aan.
Ten eerste, er staat geen method in het formulier.
Daardoor zal er dus automatisch de GET methode gebruikt worden, oftewel: iedereen die achter je staat zal in de browser je wachtwoord/gebruikersnaam kunnen lezen!
Dus; toevoegen:
method="post"Ten tweede, veel mensen hebben de slechte gewoonte om wachtwoorden op te slaan.
Dit valt (voor sommige browsers, IE zeker) te voorkomen. Dit door het name atribut te verwijderen en daarvoor in de plaats alleen een id velt te gebruiken.
Het goede formulier wordt dus:
<form action="login.php" method="post">
Gebruikersnaam:<input type="text" name="username" />
Wachtwoord:<input type="password"id="password" />
<input type="submit" value="Login" />
</form>
Als je nu het vooraf gaande login formulier gebruikt en je drukt op verzenden zullen je gegevens verstuurt worden naar de server. Niks mis mee zal je vast denken, niet helemaal.
Als je zou inloggen zou de hacker de verzonden data kunnen onderscheppen en alles ligt dan zo voor z'n neus. Hoe te voorkomen?
Simpel, client-side het wachtwoord hashen, dus zeg maar een soort javascript sha1() functie gebruiken.
Download hier of zie laatste pagina van deze tutorial voor als hij een dode link aangeeft.
De nieuwe code wordt dan:
<script type="text/javascript" src="sha1.js"></script>
<script type="text/javascript">
function _submit() {
document.getElementById('password_encrypted').value = hex_sha1(document.getElementById('password').value);
document.getElementById('password').value = '';
}
</script>
<form action="login.php" method="post" onsubmit="return _submit();">
Gebruikersnaam:<input type="text" name="username" />
Wachtwoord:<input type="password" id="password" />
<input type="hidden" name="password_encrypted" value="" />
<input type="submit" value="Login" />
</form>
Pagina 3
Formulier verwerken
Nu het formulier in orde is zullen we het ook nog moeten verwerken, daar gaat dit stuk over.
We gaan gebruik maken van sessies, en een MySQL database.
De code begint dus zo:
<?php
session_start();
// maak verbinding met database
mysql_connect(DB_HOST, DB_USER, DB_PASS) or die ('Kan niet verbinden met database!');
// selecteer de juiste db
mysql_select_db(DB_NAME) or die ('Kan database niet vinden');
// functie voor random key
function make_rand($length) {
$chars = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ1234567890";
$rand = '';
for ($i = 1; $i <= $length; $i++) {
$num = rand(0, strlen($chars));
$rand .= substr($chars, $num, 1);
}
return $rand;
}
?>
Dan zullen we moeten kijken of het formulier is verzonden:
<?php
// kijk of formulier is verzonden
if ($_SERVER['REQUEST_METHOD'] == "POST") { // indien verzonden
?>
Vervolgens gaan we zoeken of de gebruiker voorkomt en het wachtwoord controleren in de db, houd rekening met MySQL injection (in dit geval via addslashes):
<?php
$query= mysql_query("SELECT * FROM users WHERE username = '" . addslashes($_POST['username']) . "'");
if (mysql_num_rows($query) > 0) { // als er een gebruiker is gevonden
$user = mysql_fetch_object($query);
if ($user->pass == sha1($_POST['password'])) { // als het wachtwoord klopt
?>
Nu zijn we ingelogd, wat rest nog: er moet onthouden worden dat we ingelogd zijn, daarvoor gaan we dus de sessies gebruiken:
<?php
$_SESSION['user_id'] = $user->user_id;
$_SESSION['session_id'] = make_rand(50); // maak een random string voor sessie session_id mbv van functie
// zet de sessie id in de db zodat we hem later kunnen controleren
mysql_query("UPDATE users SET session_id = '" . $_SESSION['session_id'] . "' WHERE user_id = '" . $_SESSION['user_id'] . "'");
echo 'Inloggen is gelukt!';
} else { // ongeldig wachtwoord
echo 'Ongeldig wachtwoord/gebruikersnaam!';
}
} else { // ongeldige gebruikersnaam
echo 'Ongeldig wachtwoord/gebruikersnaam!';
}
}
?>
Je zou vast denken: mooi, werkt. maar dat is helaas niet helemaal zo. Er bestaat namelijk zoiets als session hijacking. Fijn, maar hoe lossen we dit op; simpel ip noteren:
<?php
// dit komt nadat je succesvol bent ingelogd
$_SESSION['user_ip'] = $_SERVER['REMOTE_ADRESS'];
?>
Mooi, het uiteindelijke script komt er dus zo uit te zien:
<?php
session_start();
// maak verbinding met database
mysql_connect(DB_HOST, DB_USER, DB_PASS) or die ('Kan niet verbinden met database!');
// selecteer de juiste db
mysql_select_db(DB_NAME) or die ('Kan database niet vinden');
// functie voor random key
function make_rand($length) {
$chars = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ1234567890";
$rand = '';
for ($i = 1; $i <= $length; $i++) {
$num = rand(0, strlen($chars));
$rand .= substr($chars, $num, 1);
}
return $rand;
}
// kijk of formulier is verzonden
if ($_SERVER['REQUEST_METHOD'] == "POST") { // indien verzonden
$query= mysql_query("SELECT * FROM users WHERE username = '" . addslashes($_POST['username']) . "'");
if (mysql_num_rows($query) > 0) { // als er een gebruiker is gevonden
$user = mysql_fetch_object($query);
if ($user->pass == sha1($_POST['password'])) { // als het wachtwoord klopt
$_SESSION['user_id'] = $user->user_id;
$rand_key = make_rand(50); // maak een random string voor sessie session_id mbv van functie
$_SESSION['session_id'] = $rand_key;
// zet de sessie id in de db zodat we hem later kunnen controleren
mysql_query("UPDATE users SET session_id = '" . $rand_key . "' WHERE user_id = '" . $_SESSION['user_id'] . "'");
$_SESSION['user_ip'] = $_SERVER['REMOTE_ADRESS'];
echo 'Inloggen is gelukt!';
} else { // ongeldig wachtwoord
echo 'Ongeldig wachtwoord/gebruikersnaam!';
}
} else { // ongeldige gebruikersnaam
echo 'Ongeldig wachtwoord/gebruikersnaam!';
}
} /* else { hier zou je het formulier kunnen zetten } */
?>
Fijn dat dat werkt, nu nog zorgen dat we op alle beveiligde pagina's kunnen kijken of er ingelogd is:
<?php
if (isset($_SESSION['user_id'], $_SESSION['user_ip'], $_SESSION['session_id']) && $_SESSION['user_ip'] == $_SERVER['REMOTE_ADRESS']) {
// sessies bestaan en ip klopt, dus gaan we verder met controleren van sessie session_id
$session_id = mysql_fetch_object(mysql_query("SELECT session_id FROM users WHERE user_id = '" . $_SESSION['user_id'] . "'"));
if ($_SESSION['session_id'] == $session_id->session_id) {
/*
alles klopt, dus we zijn hier klaar
eventueel zou je hier mooi iets kunnen zetten wat bijhoud wie waar is geweest
*/
} else { // session_id klopt niet
header('location: login.php'); // stuur door naar login pagina
}
} else { // sessies bestaan niet, of ip is onjuist
header('location: login.php'); // stuur door naar login pagina (alweer :-) )
}
?>
Zet bovenstaande in check.php of weet ik veel hoe je t noemen wil en include het in elke pagina die beveiligd is.
Nou dit was het dan alweer, hopelijk steek je er iets van op en als je iets op of aan te merken hebt dan hoor ik het wel denk ik.
We gaan gebruik maken van sessies, en een MySQL database.
De code begint dus zo:
<?php
session_start();
// maak verbinding met database
mysql_connect(DB_HOST, DB_USER, DB_PASS) or die ('Kan niet verbinden met database!');
// selecteer de juiste db
mysql_select_db(DB_NAME) or die ('Kan database niet vinden');
// functie voor random key
function make_rand($length) {
$chars = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ1234567890";
$rand = '';
for ($i = 1; $i <= $length; $i++) {
$num = rand(0, strlen($chars));
$rand .= substr($chars, $num, 1);
}
return $rand;
}
?>
Dan zullen we moeten kijken of het formulier is verzonden:
<?php
// kijk of formulier is verzonden
if ($_SERVER['REQUEST_METHOD'] == "POST") { // indien verzonden
?>
Vervolgens gaan we zoeken of de gebruiker voorkomt en het wachtwoord controleren in de db, houd rekening met MySQL injection (in dit geval via addslashes):
<?php
$query= mysql_query("SELECT * FROM users WHERE username = '" . addslashes($_POST['username']) . "'");
if (mysql_num_rows($query) > 0) { // als er een gebruiker is gevonden
$user = mysql_fetch_object($query);
if ($user->pass == sha1($_POST['password'])) { // als het wachtwoord klopt
?>
Nu zijn we ingelogd, wat rest nog: er moet onthouden worden dat we ingelogd zijn, daarvoor gaan we dus de sessies gebruiken:
<?php
$_SESSION['user_id'] = $user->user_id;
$_SESSION['session_id'] = make_rand(50); // maak een random string voor sessie session_id mbv van functie
// zet de sessie id in de db zodat we hem later kunnen controleren
mysql_query("UPDATE users SET session_id = '" . $_SESSION['session_id'] . "' WHERE user_id = '" . $_SESSION['user_id'] . "'");
echo 'Inloggen is gelukt!';
} else { // ongeldig wachtwoord
echo 'Ongeldig wachtwoord/gebruikersnaam!';
}
} else { // ongeldige gebruikersnaam
echo 'Ongeldig wachtwoord/gebruikersnaam!';
}
}
?>
Je zou vast denken: mooi, werkt. maar dat is helaas niet helemaal zo. Er bestaat namelijk zoiets als session hijacking. Fijn, maar hoe lossen we dit op; simpel ip noteren:
<?php
// dit komt nadat je succesvol bent ingelogd
$_SESSION['user_ip'] = $_SERVER['REMOTE_ADRESS'];
?>
Mooi, het uiteindelijke script komt er dus zo uit te zien:
<?php
session_start();
// maak verbinding met database
mysql_connect(DB_HOST, DB_USER, DB_PASS) or die ('Kan niet verbinden met database!');
// selecteer de juiste db
mysql_select_db(DB_NAME) or die ('Kan database niet vinden');
// functie voor random key
function make_rand($length) {
$chars = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ1234567890";
$rand = '';
for ($i = 1; $i <= $length; $i++) {
$num = rand(0, strlen($chars));
$rand .= substr($chars, $num, 1);
}
return $rand;
}
// kijk of formulier is verzonden
if ($_SERVER['REQUEST_METHOD'] == "POST") { // indien verzonden
$query= mysql_query("SELECT * FROM users WHERE username = '" . addslashes($_POST['username']) . "'");
if (mysql_num_rows($query) > 0) { // als er een gebruiker is gevonden
$user = mysql_fetch_object($query);
if ($user->pass == sha1($_POST['password'])) { // als het wachtwoord klopt
$_SESSION['user_id'] = $user->user_id;
$rand_key = make_rand(50); // maak een random string voor sessie session_id mbv van functie
$_SESSION['session_id'] = $rand_key;
// zet de sessie id in de db zodat we hem later kunnen controleren
mysql_query("UPDATE users SET session_id = '" . $rand_key . "' WHERE user_id = '" . $_SESSION['user_id'] . "'");
$_SESSION['user_ip'] = $_SERVER['REMOTE_ADRESS'];
echo 'Inloggen is gelukt!';
} else { // ongeldig wachtwoord
echo 'Ongeldig wachtwoord/gebruikersnaam!';
}
} else { // ongeldige gebruikersnaam
echo 'Ongeldig wachtwoord/gebruikersnaam!';
}
} /* else { hier zou je het formulier kunnen zetten } */
?>
Fijn dat dat werkt, nu nog zorgen dat we op alle beveiligde pagina's kunnen kijken of er ingelogd is:
<?php
if (isset($_SESSION['user_id'], $_SESSION['user_ip'], $_SESSION['session_id']) && $_SESSION['user_ip'] == $_SERVER['REMOTE_ADRESS']) {
// sessies bestaan en ip klopt, dus gaan we verder met controleren van sessie session_id
$session_id = mysql_fetch_object(mysql_query("SELECT session_id FROM users WHERE user_id = '" . $_SESSION['user_id'] . "'"));
if ($_SESSION['session_id'] == $session_id->session_id) {
/*
alles klopt, dus we zijn hier klaar
eventueel zou je hier mooi iets kunnen zetten wat bijhoud wie waar is geweest
*/
} else { // session_id klopt niet
header('location: login.php'); // stuur door naar login pagina
}
} else { // sessies bestaan niet, of ip is onjuist
header('location: login.php'); // stuur door naar login pagina (alweer :-) )
}
?>
Zet bovenstaande in check.php of weet ik veel hoe je t noemen wil en include het in elke pagina die beveiligd is.
Nou dit was het dan alweer, hopelijk steek je er iets van op en als je iets op of aan te merken hebt dan hoor ik het wel denk ik.
Pagina 4
JavaScript Secure Hash Algorithm, SHA1
/*
* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
* in FIPS PUB 180-1
* Version 2.1a Copyright Paul Johnston 2000 - 2002.
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for details.
*/
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
/*
* Perform a simple self-test to see if the VM is working
*/
function sha1_vm_test()
{
return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
}
/*
* Calculate the SHA-1 of an array of big-endian words, and a bit length
*/
function core_sha1(x, len)
{
/* append padding */
x[len >> 5] |= 0x80 << (24 - len % 32);
x[((len + 64 >> 9) << 4) + 15] = len;
var w = Array(80);
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
var e = -1009589776;
for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
var olde = e;
for(var j = 0; j < 80; j++)
{
if(j < 16) w[j] = x[i + j];
else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
safe_add(safe_add(e, w[j]), sha1_kt(j)));
e = d;
d = c;
c = rol(b, 30);
b = a;
a = t;
}
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
e = safe_add(e, olde);
}
return Array(a, b, c, d, e);
}
/*
* Perform the appropriate triplet combination function for the current
* iteration
*/
function sha1_ft(t, b, c, d)
{
if(t < 20) return (b & c) | ((~b) & d);
if(t < 40) return b ^ c ^ d;
if(t < 60) return (b & c) | (b & d) | (c & d);
return b ^ c ^ d;
}
/*
* Determine the appropriate additive constant for the current iteration
*/
function sha1_kt(t)
{
return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
(t < 60) ? -1894007588 : -899497514;
}
/*
* Calculate the HMAC-SHA1 of a key and some data
*/
function core_hmac_sha1(key, data)
{
var bkey = str2binb(key);
if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
var ipad = Array(16), opad = Array(16);
for(var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
return core_sha1(opad.concat(hash), 512 + 160);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
/*
* Convert an 8-bit or 16-bit string to an array of big-endian words
* In 8-bit function, characters >255 have their hi-byte silently ignored.
*/
function str2binb(str)
{
var bin = Array();
var mask = (1 << chrsz) - 1;
for(var i = 0; i < str.length * chrsz; i += chrsz)
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
return bin;
}
/*
* Convert an array of big-endian words to a string
*/
function binb2str(bin)
{
var str = "";
var mask = (1 << chrsz) - 1;
for(var i = 0; i < bin.length * 32; i += chrsz)
str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
return str;
}
/*
* Convert an array of big-endian words to a hex string.
*/
function binb2hex(binarray)
{
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for(var i = 0; i < binarray.length * 4; i++)
{
str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
}
return str;
}
/*
* Convert an array of big-endian words to a base-64 string
*/
function binb2b64(binarray)
{
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var str = "";
for(var i = 0; i < binarray.length * 4; i += 3)
{
var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
| (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
| ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
for(var j = 0; j < 4; j++)
{
if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
}
}
return str;
}
Reacties
0