Opmerkingen op PDO script
Ik heb een beetje wat geprobeerd met een tutorial over PDO (dit keer inloggen).
Het begint een beetje duidelijk te worden, alleen snap ik sommige dingen nog niet helemaal.
Een paar vragen die ik niet zo snel op internet heb kunnen vinden:
Waar staat het FILTER_SANITIZE_STRING voor? Is dat een soort mysqli_real_escape ofzo?
Hoe werkt het try/exception principe precies? Is daar een duidelijke tekst voor te vinden ergens? Of kunnen jullie dat uitleggen?
En zijn er verder nog opmerkingen op de veiligheid van het script of verbeteringen?
Gr
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?php
//begin session
session_start();
if(isset($_SESSION['user_id']))
{
$message = 'Gebruiker is al ingelogd';
}
//check that both username and password have been submitted
if(!isset($_POST['phpro_username'], $_POST['phpro_password']))
{
$message = 'Voer een geldig gebruikersnaam en wachtwoord in.';
}
//check if the username is the correct length
elseif (strlen( $_POST['phpro_username']) > 20 || strlen($_POST['phpro_username']) < 4)
{
$message = 'Gebruikersnaam mag minimaal 4 tekens lang zijn en maximaal 20 tekens.';
}
//check if the password is the correct length
elseif(strlen($_POST['phpro_password']) > 20 || strlen($_POST['phpro_password']) < 4)
{
$message = 'Wachtwoord mag minimaal 4 tekens lang zijn en maximaal 20 tekens.';
}
//check the username has only alpha numeric characters
elseif (ctype_alnum($_POST['phpro_username']) != true)
{
$message = 'Gebruikersnaam kan alleen getallen en letters bevatten.';
}
//check the password has only alpha numeric characters
elseif (ctype_alnum($_POST['phpro_password']) != true)
{
$message = 'Wachtwoord kan alleen getallen en letters bevatten.';
}
else
{
// if we are here the data is valid and we can insert into database
$phpro_username = filter_var($_POST['phpro_username'], FILTER_SANITIZE_STRING);
$phpro_password = filter_var($_POST['phpro_password'], FILTER_SANITIZE_STRING);
//password encryption
$phpro_password = sha1($phpro_password);
include_once ('connection.php');
try{
$dbh = new PDO("mysql:host=$mysql_hostname;dbname=$mysql_dbname", $mysql_username, $mysql_password);
//send message when connected
//set the error mode to exceptions
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//prepare the select statement
$stmt = $dbh->prepare("SELECT phpro_user_id, phpro_username, phpro_password
FROM phpro_users
WHERE phpro_username = :phpro_username
AND phpro_password = :phpro_password");
//bind the parameters
$stmt->bindParam(':phpro_username', $phpro_username, PDO::PARAM_STR);
$stmt->bindParam(':phpro_password', $phpro_password, PDO::PARAM_STR, 40);
//execute the prepared statement
$stmt->execute();
//check for a result
$user_id = $stmt->fetchColumn();
//if there is no result then show failure message
if($user_id == false)
{
$message = 'Inloggen mislukt';
}
//else everything is fine
else
{
$_SESSION['user_id'] = $user_id;
$message = 'Je bent ingelogd!';
}
} catch (Exception $ex) {
//if we are here something went wrong with the database connection
$message = 'Helaas is er wat mis gegaan tijdens het inloggen.';
}
}
?>
//begin session
session_start();
if(isset($_SESSION['user_id']))
{
$message = 'Gebruiker is al ingelogd';
}
//check that both username and password have been submitted
if(!isset($_POST['phpro_username'], $_POST['phpro_password']))
{
$message = 'Voer een geldig gebruikersnaam en wachtwoord in.';
}
//check if the username is the correct length
elseif (strlen( $_POST['phpro_username']) > 20 || strlen($_POST['phpro_username']) < 4)
{
$message = 'Gebruikersnaam mag minimaal 4 tekens lang zijn en maximaal 20 tekens.';
}
//check if the password is the correct length
elseif(strlen($_POST['phpro_password']) > 20 || strlen($_POST['phpro_password']) < 4)
{
$message = 'Wachtwoord mag minimaal 4 tekens lang zijn en maximaal 20 tekens.';
}
//check the username has only alpha numeric characters
elseif (ctype_alnum($_POST['phpro_username']) != true)
{
$message = 'Gebruikersnaam kan alleen getallen en letters bevatten.';
}
//check the password has only alpha numeric characters
elseif (ctype_alnum($_POST['phpro_password']) != true)
{
$message = 'Wachtwoord kan alleen getallen en letters bevatten.';
}
else
{
// if we are here the data is valid and we can insert into database
$phpro_username = filter_var($_POST['phpro_username'], FILTER_SANITIZE_STRING);
$phpro_password = filter_var($_POST['phpro_password'], FILTER_SANITIZE_STRING);
//password encryption
$phpro_password = sha1($phpro_password);
include_once ('connection.php');
try{
$dbh = new PDO("mysql:host=$mysql_hostname;dbname=$mysql_dbname", $mysql_username, $mysql_password);
//send message when connected
//set the error mode to exceptions
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//prepare the select statement
$stmt = $dbh->prepare("SELECT phpro_user_id, phpro_username, phpro_password
FROM phpro_users
WHERE phpro_username = :phpro_username
AND phpro_password = :phpro_password");
//bind the parameters
$stmt->bindParam(':phpro_username', $phpro_username, PDO::PARAM_STR);
$stmt->bindParam(':phpro_password', $phpro_password, PDO::PARAM_STR, 40);
//execute the prepared statement
$stmt->execute();
//check for a result
$user_id = $stmt->fetchColumn();
//if there is no result then show failure message
if($user_id == false)
{
$message = 'Inloggen mislukt';
}
//else everything is fine
else
{
$_SESSION['user_id'] = $user_id;
$message = 'Je bent ingelogd!';
}
} catch (Exception $ex) {
//if we are here something went wrong with the database connection
$message = 'Helaas is er wat mis gegaan tijdens het inloggen.';
}
}
?>
Toevoeging op 13/03/2014 11:52:47:
En Mathieu, als deze is 'goedgekeurd' heb je hier misschien wat aan?
PHP FILTER_SANITIZE_STRING Filter
Een soort van combi van strip_tags en htmlspecialchars.
Volgens mij krijg je de melding op regel 13 nu standaard als je de pagina opent?
Je controleert namelijk niet of er ge-post is (if($_SERVER['REQUEST_METHOD'] == 'POST'))
Verder ziet het er wel netjes uit.
Toevoeging op 13/03/2014 12:09:10:
en de try catch, moet je simpel zien als .. Proberen en opvangen... in Try wordt de code uitgevoerd, maar als dit fout gaat krijg je een 'exception'. Met catch kun je die exception opvangen en een eigen foutmelding weergeven.
Quote:
The FILTER_SANITIZE_STRING filter strips or encodes unwanted characters.
This filter removes data that is potentially harmful for your application. It is used to strip tags and remove or encode unwanted characters.
This filter removes data that is potentially harmful for your application. It is used to strip tags and remove or encode unwanted characters.
Een soort van combi van strip_tags en htmlspecialchars.
Volgens mij krijg je de melding op regel 13 nu standaard als je de pagina opent?
Je controleert namelijk niet of er ge-post is (if($_SERVER['REQUEST_METHOD'] == 'POST'))
Verder ziet het er wel netjes uit.
Toevoeging op 13/03/2014 12:09:10:
en de try catch, moet je simpel zien als .. Proberen en opvangen... in Try wordt de code uitgevoerd, maar als dit fout gaat krijg je een 'exception'. Met catch kun je die exception opvangen en een eigen foutmelding weergeven.
>>Volgens mij krijg je de melding op regel 13 nu standaard als je de pagina opent?
Je controleert namelijk niet of er ge-post is (if($_SERVER['REQUEST_METHOD'] == 'POST'))
Klopt inderdaad, hij checkt niet of er gepost is, dat kan ik nog wel toevoegen.
>>en de try catch, moet je simpel zien als .. Proberen en opvangen... in Try wordt de code uitgevoerd, maar als dit fout gaat krijg je een 'exception'. Met catch kun je die exception opvangen en een eigen foutmelding weergeven.
Wat is dan het voordeel om try catch te gebruiken? Het klinkt een klein beetje als if else, if code is goed = voer uit else = foutmelding.
Gewijzigd op 13/03/2014 12:16:44 door Snelle Jaap
Ja soort beveiliging. Je voorkomt hiermee dat bepaalde data wordt opgeslagen.
Hij telt niet de lengte van de string. Daar is inderdaad strlen() voor.
Wat je misschien bedoelt zijn de FLAGS
Quote:
Possible options and flags:
FILTER_FLAG_NO_ENCODE_QUOTES - This flag does not encode quotes
FILTER_FLAG_STRIP_LOW - Strip characters with ASCII value below 32
FILTER_FLAG_STRIP_HIGH - Strip characters with ASCII value above 127
FILTER_FLAG_ENCODE_LOW - Encode characters with ASCII value below 32
FILTER_FLAG_ENCODE_HIGH - Encode characters with ASCII value above 127
FILTER_FLAG_ENCODE_AMP - Encode the & character to &
FILTER_FLAG_NO_ENCODE_QUOTES - This flag does not encode quotes
FILTER_FLAG_STRIP_LOW - Strip characters with ASCII value below 32
FILTER_FLAG_STRIP_HIGH - Strip characters with ASCII value above 127
FILTER_FLAG_ENCODE_LOW - Encode characters with ASCII value below 32
FILTER_FLAG_ENCODE_HIGH - Encode characters with ASCII value above 127
FILTER_FLAG_ENCODE_AMP - Encode the & character to &
Deze FLAG kun je meegeven als je bijv geen quotes wilt encoden (ging idee waarom je dat zou willen)
Of karakters onder een bepaald ASCII getal weg laten. Kijk hiervoor in een ASCII tabel. Daar zie je dat 32 een spatie is en alles daaronder zijn speciale karakters. met STRIP_LOW worden deze karakters dus gestript.
>>> Klopt inderdaad, hij checkt niet of er gepost is, dat kan ik nog wel toevoegen.
Lijkt me wel handig ;)
>>> Wat is dan het voordeel om try catch te gebruiken? Het klinkt een klein beetje als if else, if code is goed = voer uit else = foutmelding.
Dat je een fout opvangt en niet zomaar een vreemde error op je scherm komt. Voor de bezoeker natuurlijk sowieso niet mooi. Jij geeft dan een mooie melding 'Helaas is er wat mis gegaan tijdens het inloggen.' i.p.v. iets wat de bezoeker waarschijnlijk niet begrijpt.
Gewijzigd op 13/03/2014 12:26:10 door Michael -
if( $ex->getCode() == 23000)
{
$message = 'Username already exists';
}
else
{
/*** if we are here, something has gone wrong with the database ***/
$message = 'We are unable to process your request. Please try again later"';
}
Dan zal dat (23000) wel een error code zijn?
Haha het is wel vet dat je dit doet maar echt ik snap er helemaal niks van:P
Gewoon goed de comments lezen, en als je iets niet doorhebt, kijk dan even op internet wat iets betekend en bekijk wat voorbeelden. Zo heb ik het gedaan, nog lang niet alles is duidelijk, maar ik kan nu wel een redelijk veilig (?) login script bouwen. Alleen wordt er nog niet gecheckt of er ook echt iets gesubmit/gepost is zoals Michael al zei.
Aha naja ik zal er wel naar kijken als ik weer op school zit. Dus over een weekje
Het gaat er vooral om dat je weet wat dingen doen, bij mij op school werden er veel dingen geleerd zonder dat je doorhebt wat je aan het doen bent, zo komt het nooit binnen. Op php.net kun je echt bijna alles vinden wat je zoekt.
Jaap dan kan ik beter kijken op phphulp.nl of w3schools.com want dat is voor meer duidelijker ik vind php.net echt niet duidelijk (Voor mij he!)
Gewijzigd op 13/03/2014 13:55:12 door Snelle Jaap
ja das waar
Dat zeg je ook in de comments op regel 41,
maar dan ga je ineens inloggen.
Het is natuurlijk niet handig om de hacker al direct te vertellen hoe lang een username moet zijn, en welke tekens daarin mogen zitten.
Er is trouwens geen zinnige reden te bedenken waarom een password een max-stringlengte zou moeten hebben en waarom er alleen alnum-tekens in zouden mogen zitten.
Je slaat het toch gehasht op....
Daarnaast moet je dergelijke lengte controles ook uitvoeren op de ge-sanitizede versie
username aap is te kort, maar '<html tags worden straksverwijderd/>aap<nog meer html>'
is lang genoeg als strlen op de invoer, maar na de filter functie blijft "aap" over.
in de query op regel 58 is het niet zinnig om de username en password op te vragen: die heb je niet nodig en staat bovendien al letterlijk in de WHERE regel opgenomen...
>>Er is trouwens geen zinnige reden te bedenken waarom een password een max-stringlengte zou moeten hebben en waarom er alleen alnum-tekens in zouden mogen zitten.
Je slaat het toch gehasht op....
Dat is voor het wachtwoord zelf, in een sha1 encrypted password komen die gewoon voor, dus dat zou toch geen probleem moeten zijn?
En met die max string length doe ik hier geloof ik wat mee: $stmt->bindParam(':phpro_password', $phpro_password, PDO::PARAM_STR, 40); Aangezien een sha1 encryptie 40 tekens is.
Toevoeging op 14/03/2014 11:23:22:
Nog een vraag, ik wil graag die messages weergeven in een bericht op de site zonder refresh, moet ik dan heel m'n script weer omgooien? of is dat best simpel te doen? Kan iemand me daarmee op weg helpen?
Gewijzigd op 13/03/2014 14:43:58 door Snelle Jaap
Zonder refresh is AJAX. Dan zou het POST gedeelte in een apart bestand moeten doen en met jQuery kun je $.post de juiste data naar de pagina sturen zonder een refresh en de data die je terug krijgt tonen als melding.
Ja ik weet wel een beetje hoe dat werkt, maar heb dat nog nooit in combinatie met PDO gedaan. Als ik het POST gedeelte weghaal kan mijn script het toch niet meer vinden?
Die POST wordt dan dus afgehandeld door een andere pagina dan waar je het formulier hebt staan.
Als je weet hoe het werkt, probeer het gewoon ;)
Toevoeging op 14/03/2014 11:48:30:
Aan het script hoef ik niks meer aan te passen? Het gaat namelijk alleen om validatie, en als alles correct is wil ik het script laten uitvoeren (natuurlijk ook nog even server side valideren)
Toevoeging op 14/03/2014 11:56:33:
Ik maak anders wel een nieuw topic met de code die ik nu heb, is wat overzichtelijker.
alnum voor een wachtwoord is wat vreemd. Je zou juist graag vreemde karakters willen die je nou niet toestaat. een max lengte (PARAM_STR, 40) op het wachtwoord is niet nodig. en de andere punten van Ivo.
Toevoeging op 14/03/2014 12:08:42:
Ik heb trouwens een nieuw topic aangemaakt in het Javascript gedeelte.