Controleer of post van mijn pagina af komt
Alleen doordat je dan dubbele posts kan krijgen door per ongeluk de pagina te verversen wil ik nu mijn formulier waardes doorsturen naar login2.php en dan laten inloggen.
Alleen hoe weet ik dat die gene van mijn loginpagina komt? Is het slim om een hidden $_POST mee te sturen? Maar dat kan weer onderschept worden lees ik?
Hoe pakken jullie dat aan?
Controleer op de vervolgpagina of deze overeenkomen. Zo niet, dan is er sprake van een 'CSRF-attack', waarbij iemand de pagina oproept via een illegale manier dan er verwacht wordt.
dit.
Stel je hebt form.php
Deze post je naar process.php
succes? > success.php
fail? > form.php?errors=1
Dit zou je ook kunnen reduceren tot één script als je deze netjes in acties opdeelt:
form.php (default actie, toon formulier eventueel met fouten/hints/eerdere invoer als ?errors=1)
form.php?action=process (verwerk-actie)
form.php?action=complete (boodschap tonen na verwerken, of je stuurt iemand gelijk ergens anders naartoe en toon je nog ergens dat alles is opgeslagen met een sessie-flash-message ofzo)
Wat Aar zegt (token) + Stel je hebt form.php
Deze post je naar process.php
succes? > success.php
fail? > form.php?errors=1
Dit zou je ook kunnen reduceren tot één script als je deze netjes in acties opdeelt:
form.php (default actie, toon formulier eventueel met fouten/hints/eerdere invoer als ?errors=1)
form.php?action=process (verwerk-actie)
form.php?action=complete (boodschap tonen na verwerken, of je stuurt iemand gelijk ergens anders naartoe en toon je nog ergens dat alles is opgeslagen met een sessie-flash-message ofzo)
Wat wel jammer is dat ik gebruikers meldingen zoals "geen gebruikersnaam ingevuld" niet automatisch kan checken.
Ik moet dit terug sturen met een GET is dat niet wat slordig?
required atribute kan je toevoegen aan je input.
dan werkt de submit knop niet zolang dat veld niet is ingevuld.
Dat kan je afvangen met HTML5. het dan werkt de submit knop niet zolang dat veld niet is ingevuld.
Gewijzigd op 20/08/2015 14:05:53 door Randy vsf
@Danny zie mijn PB.
Zog altijd voor:
Client-side controles, en bij voorkeur met HTML5, dan met JavaScript en dan nog eens serverside.
Ik zal dus de GET gebruiken voor PHP maar eerst met JQUERY het veld op laten lichten.
Quote:
Alleen doordat je dan dubbele posts kan krijgen door per ongeluk de pagina te verversen
Daar zit eigenlijk je probleem, niet in het doorsturen naar een andere pagina.
Dus als je dat afvangt, is het opgelost.
Dat is ook wel slim Ramon, bedankt.
Klopt, zo vernietig je de POST-request na het correct versturen, maar het voorkomt niet dat anderen de pagina alsnog op een externe wijze kunnen aanroepen.
is dat slim?
Gewijzigd op 20/08/2015 17:49:10 door - Ariën -
- Aar - op 20/08/2015 15:45:21:
Ik zou het niet doen, omdat een IP-adres niet uniek is. Liever uniqid() ofzo.
Maar ik zie op php.net dat deze functie een id maakt op basis van de current-time in microseconds. Dat kan ik op de pagina er na toch niet meer verifieren omdat de microsecondes dan al gewijzigd zijn?
Wel als je dezelfde waarde bewaard in je sessie. Bovendien kun je tevens een timeout maken .
Danny von Gaal op 20/08/2015 16:33:37:
Maar ik zie op php.net dat deze functie een id maakt op basis van de current-time in microseconds. Dat kan ik op de pagina er na toch niet meer verifieren omdat de microsecondes dan al gewijzigd zijn?
- Aar - op 20/08/2015 15:45:21:
Ik zou het niet doen, omdat een IP-adres niet uniek is. Liever uniqid() ofzo.
Maar ik zie op php.net dat deze functie een id maakt op basis van de current-time in microseconds. Dat kan ik op de pagina er na toch niet meer verifieren omdat de microsecondes dan al gewijzigd zijn?
Je slaat die waarde op in een sessie, dus dan kan je het prima later nog vergelijken.
- Aar - op 20/08/2015 15:45:21:
Ik zou het niet doen, omdat een IP-adres niet uniek is.
Een ip-adres is wel uniek.
Wat jij bedoelt is dat je er niet van op aan kunt omdat het kan veranderen. (mobiel b.v.)
Ja, dat bedoelde ik. Maar er kunnen meerdere gebruikers op zitten, en deze kan veelvuldig wijzigen op een mobiel netwerk. Wel weet ik dat dat gebeurt als je dataverbinding langdurig wegvalt.
Bovenaan mijn loginpagina maak ik een md5 hash aan op basis van ip-adres. (Sorry -Aar- maar die uniqueid begreep ik niet helemaal en deze applicatie is alleen maar beschikbaar op kantoor waar iedere pc een unieke ip-adres heeft).
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
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
<form name="login" method="post" action="login2.php">
<table class="login">
<tr>
<td class="label"> </td>
<td class="invoerveld"><p class="login verplicht">Verplichte velden zijn gemarkeerd met een *</p></td>
</tr>
<tr>
<td class="input"><p class="login">Gebruikersnaam *</p></td>
<td class="input"><input type="text" name="gebruikersnaam" id="gebruikersnaam" maxlength="100"/></td>
</tr>
<tr>
<td class="input"><p class="login">Wachtwoord *</p></td>
<td class="input"><input type="password" name="wachtwoord" id="wachtwoord" maxlength="20"/></td>
</tr>
<tr>
<td> </td>
<td>
<?php
// Laat errors zien bij het missen van gebruikersnaam of wachtwoord
if (isset($_GET['error'])) {
if ($_GET['error'] == "1") {
echo "<div class='error'>Er is geen gebruikersnaam ingevuld</div>";
} elseif ($_GET['error'] == "2") {
echo "<div class='error'>Er is geen wachtwoord ingevuld</div>";
}
}
?>
</td>
</tr>
<tr>
<td><input type="hidden" value="<?php echo $_SESSION['hash']; ?>" name="client-hash" /></td>
<td><input type="submit" value="Inloggen"/></td>
</tr>
</table>
</form>
<table class="login">
<tr>
<td class="label"> </td>
<td class="invoerveld"><p class="login verplicht">Verplichte velden zijn gemarkeerd met een *</p></td>
</tr>
<tr>
<td class="input"><p class="login">Gebruikersnaam *</p></td>
<td class="input"><input type="text" name="gebruikersnaam" id="gebruikersnaam" maxlength="100"/></td>
</tr>
<tr>
<td class="input"><p class="login">Wachtwoord *</p></td>
<td class="input"><input type="password" name="wachtwoord" id="wachtwoord" maxlength="20"/></td>
</tr>
<tr>
<td> </td>
<td>
<?php
// Laat errors zien bij het missen van gebruikersnaam of wachtwoord
if (isset($_GET['error'])) {
if ($_GET['error'] == "1") {
echo "<div class='error'>Er is geen gebruikersnaam ingevuld</div>";
} elseif ($_GET['error'] == "2") {
echo "<div class='error'>Er is geen wachtwoord ingevuld</div>";
}
}
?>
</td>
</tr>
<tr>
<td><input type="hidden" value="<?php echo $_SESSION['hash']; ?>" name="client-hash" /></td>
<td><input type="submit" value="Inloggen"/></td>
</tr>
</table>
</form>
Vervolgens handel ik alles af in een nieuw bestand zodat ik geen last heb van dubbele posts:
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
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
<?php
session_start();
// Laat errors zien
error_reporting(E_ALL);
ini_set('display_errors', 1);
// Maak een connectie met de database
include("connect.php");
// Controleer of de gebruiker wel van onze login pagina komt
if (isset($_SESSION['hash'])) {
// Controleer of de sessie overeenkomt met de login pagina
if ($_SESSION['hash'] == md5($_SERVER['REMOTE_ADDR'])) {
// Controleer of gebruikersnaam en wachtwoord is ingevuld
if (isset($_POST['gebruikersnaam'], $_POST['wachtwoord'])) {
// Controleer of gebruikersnaam niet leeg is
if (empty($_POST['gebruikersnaam'])) {
header( "refresh:0;url=login.php?error=1" ); // Error1 = gebruikersnaam is leeg
// Controleer of wachtwoord niet leeg is
} elseif (empty($_POST['wachtwoord'])) {
header( "refresh:0;url=login.php?error=2" ); // Error2 = wachtwoord is leeg
} else {
// Voorkom SQL injection
$gebruikersnaam = mysqli_real_escape_string($conn, $_POST['gebruikersnaam']);
$wachtwoord = mysqli_real_escape_string($conn, $_POST['wachtwoord']);
// Haal gebruiker op
$sql = "SELECT gebruikersnaam, wachtwoord FROM gebruikers WHERE gebruikersnaam = '$gebruikersnaam'";
$result = $conn->query($sql);
// Ga door als er een gebruiker met dezelfde gebruikersnaam aanwezig is
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
if (password_verify($wachtwoord, $row['wachtwoord'])) {
$_SESSION['gebruikersnaam'] = $row['gebruikersnaam'];
header( "refresh:0;url=index.php" );
} else {
echo 'Ongeldige gebruikersnaam/wachtwoord combinatie.';
}
}
} else {
echo "Kan geen gebruiker vinden met deze gebruikersnaam.";
}
}
} else {
// Geen $_POST sessie? Naar login.php!
header( "refresh:0;url=login.php" );
}
} else {
header( "refresh:0;url=login.php" );
}
} else {
// Geen sessie?? Wegwezen!
header( "refresh:0;url=login.php" );
}
?>
session_start();
// Laat errors zien
error_reporting(E_ALL);
ini_set('display_errors', 1);
// Maak een connectie met de database
include("connect.php");
// Controleer of de gebruiker wel van onze login pagina komt
if (isset($_SESSION['hash'])) {
// Controleer of de sessie overeenkomt met de login pagina
if ($_SESSION['hash'] == md5($_SERVER['REMOTE_ADDR'])) {
// Controleer of gebruikersnaam en wachtwoord is ingevuld
if (isset($_POST['gebruikersnaam'], $_POST['wachtwoord'])) {
// Controleer of gebruikersnaam niet leeg is
if (empty($_POST['gebruikersnaam'])) {
header( "refresh:0;url=login.php?error=1" ); // Error1 = gebruikersnaam is leeg
// Controleer of wachtwoord niet leeg is
} elseif (empty($_POST['wachtwoord'])) {
header( "refresh:0;url=login.php?error=2" ); // Error2 = wachtwoord is leeg
} else {
// Voorkom SQL injection
$gebruikersnaam = mysqli_real_escape_string($conn, $_POST['gebruikersnaam']);
$wachtwoord = mysqli_real_escape_string($conn, $_POST['wachtwoord']);
// Haal gebruiker op
$sql = "SELECT gebruikersnaam, wachtwoord FROM gebruikers WHERE gebruikersnaam = '$gebruikersnaam'";
$result = $conn->query($sql);
// Ga door als er een gebruiker met dezelfde gebruikersnaam aanwezig is
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
if (password_verify($wachtwoord, $row['wachtwoord'])) {
$_SESSION['gebruikersnaam'] = $row['gebruikersnaam'];
header( "refresh:0;url=index.php" );
} else {
echo 'Ongeldige gebruikersnaam/wachtwoord combinatie.';
}
}
} else {
echo "Kan geen gebruiker vinden met deze gebruikersnaam.";
}
}
} else {
// Geen $_POST sessie? Naar login.php!
header( "refresh:0;url=login.php" );
}
} else {
header( "refresh:0;url=login.php" );
}
} else {
// Geen sessie?? Wegwezen!
header( "refresh:0;url=login.php" );
}
?>
Gewijzigd op 21/08/2015 14:59:35 door Danny von Gaal
Danny von Gaal op 21/08/2015 14:36:13:
Bovenaan mijn loginpagina maak ik een md5 hash aan op basis van ip-adres. (Sorry -Aar- maar die uniqueid begreep ik niet helemaal en deze applicatie is alleen maar beschikbaar op kantoor waar iedere pc een unieke ip-adres heeft).
Het stelt bar weinig voor:
En de uitwerking:
https://3v4l.org/i9HHT
In andere formulieren zou het handig / gebruiksvriendelijk zijn als de reeds ingevulde velden teruggeplaatst worden bij een foutmelding met eventueel een hint van wat er fout was. Een loginformulier leent zich hier overigens niet echt voor (je zou in dat geval ook geen hints moeten geven over wat er niet klopt).
Je token wordt nergens ongeldig gemaakt en is ook niet random. Dat maakt het token niet erg geschikt voor zijn doel. De opzet met uniqid() is geschikter. En zorg dat deze meteen ongeldig wordt gemaakt nadat je deze hebt gebruikt. Voorbeeld van een "random" token functie:
Dan is een token formulier-invoer waarbij het formulierveld een bepaalde waarde zou moeten hebben waarop gevalideerd moet worden. De eerste controle die je dus zou moeten uitvoeren in login2.php zou eigenlijk een check moeten zijn of je met een POST request van doen hebt. Daarna zou je in eerste instantie kunnen kijken of het token voldoet en de rest van de velden valideren.
Je wachtwoord escapen met mysqli_real_escape_string (waarom niet $conn->real_escape_string()?) lijkt mij in dit geval niet goed, want als er in het wachtwoord karakters zitten die in de MySQL context geescaped zouden worden, dan mislukt je password_verify() aanroep. Je gebruikt $wachtwoord ook helemaal niet in een query...
De syntax header("refresh:0;url=..." ) ben ik niet bekend mee. Waarom gebruik je geen header('Location: ...')? Daarnaast zou na elke header die je redirect een exit moeten staan anders gaat de executie van het script gewoon door. Headers worden pas aan het einde van het script / voor het begin van output verstuurd.
Als je dus zoiets hebt:
Code (php)
En de authenticatie-check mislukt, dan wordt je header geset, vervolgens wordt CODE DIE UITGEVOERD WORDT ALS AUTHENTICATIE SLAAGT uitgevoerd, en dan word je geredirect.
Het toevoegen of weglaten van een exit kan het verschil maken tussen een veilige of een onveilige applicatie.
Of nog beter, maak een functie/methode zodat je dit nooit vergeet:
Je kunt hier ook nog HTTP status codes aan toevoegen als je wilt (bijvoorbeeld HTTP/1.1 303 See Other), en officieel moet $url een volledige URL zijn.
Gewijzigd op 21/08/2015 15:19:49 door Thomas van den Heuvel