07. december 2005 - 21:17Der er
21 kommentarer og 1 løsning
Brugersystem med sessions..
Hejsa,
Jeg har udfra en masse spørgsmål her på eksperten prøvet at bikse mit eget brugersystem sammen. Det er helt sikkert ikke tip-top, så derfor ville jeg gerne have lidt kommentarer og inputs med på vejen, specielt når det gælder sikkerheden.
Derudover mangler du en meget vigtigt ting i forbindelse med dine database kald. For at sikre dig imod SQL-injektion skal du sikre dig at det som kommer ind med dine POST-data'er ikke laver ulykker i din database.
Hvad sker der f.eks. hvis jeg skriver noget i stil med dette i dit password felt :
' DROP TABLE users '
Muligt at lige denne syntaks ikke virker, men det er et faktum at du er sårbar overfor denne type af ulykker. Du tænker måske at nåh ja, men der er jo ikke nogen der ved at min tabel hedder users, men for det første er det jo ikke svært st gætte :) og for det andet så findes der andre SQL-injektion metoder til at finde ud af både dine tabellers navne OG struktur så det skal vi have sat en stopper for.
Først introducerer vi mysql_real_escape_string()
Du kan læse detaljer om denne på php.net men det den gør er bl.a. at escape f.eks. ' og " således at de istedetfor at opdele din query istedet bliver behandlet som EN lang tekst streng.
Så : SELECT * FROM `users` WHERE username = '$postuser' AND password = '$postpass'"
bør være :
SELECT * FROM `users` WHERE username = '".mysql_real_escape_string($postuser)."' AND password = '".mysql_real_escape_string($postpass)."'"
Så er der lukket for SQL-injektion, men derudover kunne du jo også checke på indholdet af f.eks. $_POST['username'] før du overhovdet godkender det til brug. Her introduceres regular ekspressions og det kan godt se meget kompliceret ud men med lidt hårdt arbejde og læse læse læse så viser det sig at være et meget stærkt værktøj.
Lad os sige at dine brugernavne kun må bestå af bogstaverne a-z og tal fra 0-9 plus mellemrum og .
Så kan et reg_exp se således ud
"/ og /" definere blot starten og slutningen på vores reg_exp. ^ og $ definere starten (^) og slutningen ($) på vores streng. [a-z0-9. ] indeholder alle de tegn strengen må indeholde. + betyder skal forekomme 1 eller flere gange.
Så ovenstående betyder altså at der fra starten af strengen (^) skal forekomme 1 eller flere tegn af typen [a-z0-9. ] indtil slutningen ($) af strengen og der må ikke være andre tegn end dem vi har skrevet imellem [ og ]
Hvis du vil tillade både store og små bogstaver kan du enten skrive [a-zA-Z0-9. ] eller sætte et 'i' i enden af udtrykket således :
"/^[a-z0-9. ]+$/i"
i flaget som man kalder det betyder 'accepter både store og små bogstaver'
Så hvis vi vil checke $_POST['username'] kunne det se således ud :
Mht opdelingen af login.php har jeg lige et par spørgsmål. Du skriver, at du ville dele det op på følgende måde (Mine spørgsmål fremhævet med **):
Først registreres om der er modtaget POST-data : ***** med nedenstående if, logger den jo ind ligemeget om, username og password er korrekt, og ligemeget hvor jeg "kommer" fra. F.eks. har min adduser-side også de to variabler og derfor vil man automatisk blive logget ind, derfra også ******
--> herinde laver jeg så 'behandlingen af POST-data fra formen, og logger brugeren ind ved at 'sætte' $_SESSION variablerne. <--
}
Derefter ville jeg checke om en bruger er logget ind : ***** Alle brugere har fra start en level, 0 for brugere, 1 for redaktører, og 2 for admin. Derfor skal der være en menu for dem der er logget ind som bruger, dem der er logget ind som redaktør, og for dem der er admin. Denne viser jo blot menuen for alle ****** if(isset($_SESSION['loggedIn']) && isset($_SESSION['level'])) {
Det er jo altid svært at få det fulde overblik over andres kode men prøv at se på dette jeg har foreløbig udeladt alle sikrings 'tingene' :
if(isset($_POST['username']) && isset($_POST['password'])) { $q=mysql_query("SELECT * FROM users WHERE username='".$_POST['username']."' AND password='".md5($_POST['password'])."'"); if(mysql_num_rows($q)==1) { $r=mysql_fetch_assoc($q); $_SESSION['username']=$_POST['username']; $_SESSION['level']=$r['level']; } }
if(isset($_SESSION['username']) && isset($_SESSION['level'])) { if($_SESSION['level']==0) { echo "Du er en alm. bruger !"; } if($_SESSION['level']==1) { echo "Du er redaktør !"; } if($_SESSION['level']==2) { echo "Du er admin !"; } } else { include('login_form.php'); }
det var noget sådan jeg tænkte, men det er da værd at ligge mærke til at det du har lavet muligvis fungerer og så er det jo fint nok, jeg giver dig blot mine ideer til opbygningen af systemet :)
Forresten så skal man være ekstra opmærksom når man bruger værdien 0 til variabler.
I nogle tilfælde kan værdien 0 nemlig blive opfattet som false...så i ovenstående ville jeg nok sætte 1=alm.bruger, 2=redaktør og 3=admin så undgår du med sikkerhed en tvetydighed i behandlingen af din variabler.
Her kommer lige en lille opdatering - jeg har læst en masse tutorials rundt omkring, taget dine forslag til mig og startet mere eller mindre forfra. :)
$postuser = $_POST["username"]; $postpass = md5($_POST["password"]); $result = mysql_query("SELECT * FROM `users` WHERE username = '$postuser' AND password = '$postpass'"); $count = mysql_num_rows($result); $r = mysql_fetch_assoc($result); ?> <div> <table align="center" width="550"> <?php if ($_POST['login']) { if (isset($_POST['username']) && isset($_POST['password'])) { if ($count = '1') { $_SESSION['username'] = $r["username"]; $_SESSION['userlevel'] = $r["userlevel"]; echo '<tr><td>Du logges nu ind, og sendes til forsiden...</td></tr>'; echo '<meta http-equiv="refresh" content="1;url=index.php">'; } else { echo '<tr><td>Brugernavn eller password er forkert, klik <a href="login.php"><strong>her</strong></a> for at prøve igen.</td></tr>'; } } else { echo '<tr><td>Du skal indtaste både brugernavn og password, klik <a href="login.php"><strong>her</strong></a> for at prøve igen.</td></tr>'; } } else { echo ' <form method="post" action="'.$_SERVER["PHP_SELF"].'"> <tr> <td colspan="2"><strong>Log ind</strong>:<br><br></td> </tr> <tr> <td>Brugernavn: </td> <td align="right" style="padding-right: 15px;"><input name="username" type="text" size="80"></td> </tr> <tr> <td>Password: </td> <td align="right" style="padding-right: 15px;"><input name="password" type="password" size="80"></td> </tr> <tr> <td></td> <td align="right" style="padding-right: 15px;"><input type="submit" name="login" value="Log ind"></td> </tr> </form>'; } ?> </table> </div> <?php include("footer.php"); ?>
################ login.php slut # ################
Sådan ser min login.php ud, som ikke længere er inkluderet i menuen :) Jeg har dog et problem, for det virker som om den logger mig fint nok ind, men bagefter når den redirecter mig til forsiden, kan jeg ikke se, at jeg er logget ind.
Jeg har prøvet at redirecte mig til showstatus.php, for at teste om jeg overhovedet er logget ind.
if ($_POST['login']) { if (isset($_POST['username']) && isset($_POST['password'])) { if ($count = '1') { $_SESSION['username'] = $r["username"]; $_SESSION['userlevel'] = $r["userlevel"]; echo '<tr><td>Du logges nu ind, og sendes til forsiden...</td></tr>'; echo '<meta http-equiv="refresh" content="1;url=index.php">'; } }
Den skriver "Du logges nu ind, og sendes til forsiden..." og bagefter sender den en til forsiden, men sessions'ne bliver ikke registreret? Hvorfor ikke?
Jeg sidder og kigger lidt på det du har lavet og jeg har da et par forslag.
F.eks. så syntes jeg godt om din første plan med at holde det hele på en side istedet for at sende brugeren fra den ene til den anden side. Det betyder sikkert ikke så meget for brugeren men jeg syntes det bliver sværere for mig at overskue koden. Derudover skal man jo hvergang man laver en ny side, tænke over konsekvensen af at brugeren tilgår denne side direkte.
Men jeg sidder og skriver lidt til dig, som du måske kan bruge. Så I'll be back :)
Okay prøv at kigge på dette. Det er stort set magen til det jeg postede tidligere men nu har jeg tilføjet lidt forklaring.
<?php
session_start();
// Først checker vi om brugeren har sendt formen (Altså om nogen prøver at logge ind). // Hvis der er POST-data checker vi i databasen (tabellen 'users') om POST-data er korrekte. // Hvis vores query $q indeholder 1 række så er brugeren godkendt og skal logges ind. // Det gør vi ved at definerer $_SESSION['username'] og $_SESSION['level'].
if(isset($_POST['username']) && isset($_POST['password'])) { $q=mysql_query("SELECT * FROM users WHERE username='".$_POST['username']."' AND password='".md5($_POST['password'])."'"); if(mysql_num_rows($q)==1) { $r=mysql_fetch_assoc($q); $_SESSION['username']=$_POST['username']; $_SESSION['level']=$r['level']; } }
// Det stykke kode der står herover vil kun blive 'kørt' hvis en bruger har udfyldt login formen og sendt den.
// Men hvis nu en bruger 'besøger' denne side uden at have sendt login formen, må vi lave et check af om han er logget ind i forvejen. // Husk at vi lige har logget den bruger ind som sendte login formen. // Det gør vi ved at checke om isset($_SESSION['username']) og isset($_SESSION['level']) // Hvis de to session variabler findes så bestemmer indholdet af level brugerens adgangslevel. // Hvis ikke de findes så er brugeren ikke logget ind og så viser vi brugeren vores login form.
For det første så istedet for at bruge en masse echo statements, så gemmer jeg alt output i variabler som jeg så sender til sidst i scriptet. For at forstå fordelen ved dette skal vi kigge lidt på hvad der sker når vi sender 'noget' til browseren. Den første gang vi laver echo "Test"; så sender vi en header til browseren som indeholder en masse informationer. I dette tilfælde er det vigtige at denne header indeholder vores session_id. Det betyder at når vi først har lavet f.eks. en echo statement så kan vi IKKE ændrer i vores session variabler, derfor er det en fordel at samle alt output op i variabler og så først sende det hele, når vi er færdige med scriptet.
Den fejl meddelelse man får siger jo også de hele..noget med "Cannot modify header, header allready sent !"
Det er måske lidt mere forståeligt hvis vi kigger på cookies. Cookies ligger også i header informationen, så hvis vi ønsker at sætte eller ændre en cookie skal det gøres FØR header'en sendes.
Mvh. Johan.... sender lige for at læse det selv :)
En af de ting der er gået igen i de kode eksempler du viser er at du i starten af scriptet altid checker POST-data mod databasen, du skal kun checke mod databasen hvis der er sendt POST-data. Hvis ikke der er sendt POST-data, skal du checke om der er en valid session ($_SESSION['username']), og hvis der heller ikke er det så er brugeren ikke logget ind.
Men lad mig høre dine overvejelser omkring det jeg har skrevet. Det er jo nemmere at vide hvad der kræver forklaring, hvis du spørger om det :)
Ok.. Meget underligt! Havde lige skrevet en masse, som den tilsyneladende ikke havde lyst til at tilføje her..
Nårhh, men det kort og det lange er, at jeg har flyttet min POST-data check ind hvor den hører til, samt fået samling på mine echo statements med en $output variabel.
Mange tak for hjælpen, det lader til at køre udemærket nu. Du må gerne smide et svar, da din hjælp har været meget brugbar.
Men har du gjort noget for at sikre din database mod f.eks. SQL injektion ?
Jeg vil gerne hjælpe dig med at sikre dit script imod uvenlige brugere, men lad mig høre om du er interesseret. Hvis du er skal du nok lige smide den kode du er endt med så vi kan kigge på det :)
Jeps, og jeg siger mange tak for hjælpen. Hvis du stadig abonnérer på denne tråd skal jeg nok spørge hvis der er nogle spørgsmål. Så kan vi evt. tage et nyt spørgsmål+point til det..
Det kan vi sagtens. Men du vil nok få mere ud af det hvis du blot opretter et nyt spørgsmål således at du også kan få input fra andre. 'Ulempen' er selvfølgelig at alle jo har deres måde at gøre tingene på og derfor sikkert vil prøve at ændrer i den struktur du har valgt, men det skal man jo opfatte som en hjælp til at se samme sag fra flere sider :)
Mvh. Johan
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.