Tietoturvasta sen verran, että mitä kaikkea kommervenkkejä tulee ottaa huomioon MySql sekä Php pohjaista kirjautumista suunnitellessa?
Ainakaan se ei saa olla SQL injektoitavissa. Kannattaa laittaa md5. Siinä kai ne yleisimmät aloittelijan virheet.
Niin siis perusohjeet ovat aika selkeät älä luota käyttäjän syötteeseen ja puhdista se kannan omilla työkaluilla tai käytä jotakin kirjastoa esim. PDO ohjeita on putkan oppaissa. MD5 on ihan ok salasanan suojaamisessa, joskin sekin pitäisi varmaan suolailla jollakin vielä varmemmaksi vakuudeksi.
<?php /* Kannattaa käyttää esim. mysql_real_escape_string(), se tekee ainakin user inputin vaarattomaksi. */ $nimi = $_POST['nimi'] ? $_POST['nimi'] : ''; $sala = $_POST['sala'] ? $_POST['sala'] : ''; $query = sprintf("SELECT * FROM kayttajat WHERE nimi='%s' AND sala='%s'", mysql_real_escape_string($nimi), mysql_real_escape_string($sala)); /* Formissa kannattaa käyttää jotain takistus merkkijonoa. Varmistaakseen että käyttäjä koittaa kirjautua oikealla tavalla, eikä esim refreshaa sivua. Esim: */ <?php $_SESSION[token] = md5(uniqid(mt_rand(), true)); ?> <form name="kirjaudu" action="" method="post"> <input type="text" name="nimi" /> <input type="password" name="sala" /> <input type="submit" value="Kirjaudu" name="tarkista" /> <input type="hidden" name="token" value="<?= $_SESSION['token']; ?>" /> </form> <?= if($_POST[tarkista]) { if($_POST[token] == $_SESSION[token]) { $nimi = $_POST['nimi'] ? $_POST['nimi'] : ''; $sala = $_POST['sala'] ? $_POST['sala'] : ''; $query = sprintf("SELECT * FROM kayttajat WHERE nimi='%s' AND sala='%s'", mysql_real_escape_string($nimi), mysql_real_escape_string($sala)); if(mysql_num_rows($query) == 1) { //Käyttäjä löyty tee jotain } else { //Ei löytynyt.. } } else { print("Tultiin tähän jotenkin väärin, esim refresh.."); } } ?> Tietysti kannattaa vielä hashata salasanat kantaan esim md5()..
qeijon koodi on kyllä aika rumaa ja muutama syntaksivirhekin näyttää olevan mukana. muun muassa taulukon avaimet pitäisi ympäröidä hipsuilla tai lainausmerkeillä. Lopussa on yksi aaltosulku liikaa. uniqid() pelkällään olisi varmaan ihan riittävä. Samoin uniqid funktion ensimmäinen parametri ei tunnu fiksulle sen pitäisi olla prefiksi merkkijono oletuksena se on "" ja toinen lisää satunnaisuutta. Molemmat ovat luultavasti aika turhia. Samoin uniqid:n kääriminen vielä md5 funktion sisään heikentää satunnaisuutta.
Teuro kirjoitti:
taulukon avaimet pitäisi ympäröidä hipsuilla tai lainausmerkeillä.
Totta, huono tapa.
Teuro kirjoitti:
Lopussa on yksi aaltosulku liikaa.
Eikä ole, laskeppa uudelleen.
Teuro kirjoitti:
Samoin uniqid funktion ensimmäinen parametri ei tunnu fiksulle sen pitäisi olla prefiksi merkkijono oletuksena se on "" ja toinen lisää satunnaisuutta. Molemmat ovat luultavasti aika turhia. Samoin uniqid:n kääriminen vielä md5 funktion sisään heikentää satunnaisuutta.
Suora lainaus manuskasta:
rand() is a secuirty hazard and should never be used to generate a security token.
For most security purposes this is a the best token:
md5(uniqid(mt_rand(), true));
Aloittelijoille MySQLin tietoturvaksi riittää hyvin pitkälle mysql_real_escape_string(), ihan oikeasti.
Käyttäjien tietoturvaksi ois ihan kiva, jos salasanat hashattaisiin kunnolla
qeijo kirjoitti:
Eikä ole, laskeppa uudelleen.
Olet oikeassa ihan oikeinhan ne ovat. Usein kun itselle tulee samalle "tasolle" kaksi sulkua allekain on toinen auttamatta liikaa. Sisentämällä tuli ihan oikein.
qeijo kirjoitti:
Suora lainaus manuskasta:
rand() is a secuirty hazard and should never be used to generate a security token.
For most security purposes this is a the best token:md5(uniqid(mt_rand(), true));
Tuossa koodivinkissä minusta on kyllä tuo md5 aivan turhaan.
Estääkö tuo mysql_real_escape_string sql-injektion?
<input type="text" name="nimi" /> <!-- mitä hyötyä tuosta kenosta tuossa perässä on?
Onko seuraava kirjautuminen kuinka helposti murrettavissa?
<?php session_start(); ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title></title> <?php if(!empty($_POST['tunnus']) && !empty($_POST['salasana'])){ $tunnus = get_magic_quotes_gpc() ? $_POST['tunnus'] : mysql_real_escape_string($_POST['tunnus']); $salasana = get_magic_quotes_gpc() ? $_POST['salasana'] : mysql_real_escape_string($_POST['salasana']); $salaus = 'lkshj'.$salasana.'sie7390as'; $kaanteinen = strrev($salaus); $merkkijono = strlen($kaanteinen); for($i=0; $i<$merkkijono; $i++){ $koodattusalasana .= $kaanteinen[$i].$salaus[2].$salaus[3].$salaus[$i]; } $salasana = md5($koodattusalasana); $istuntotunnus = md5(uniqid(rand(), true)); $k = "UPDATE kayttajat SET istunto = '$istuntotunnus' WHERE tunnus = '$tunnus' AND salasana = '$salasana'"; if(!($t = mysql_query($k))) return; if(mysql_affected_rows() == 1){ $_SESSION['log_key'] = $istuntotunnus; $_SESSION['kayttajatunnus'] = $tunnus; } } if(!empty($_SESSION['log_key'])){ $k = "SELECT id, tunnus, oikeudet FROM kayttajat WHERE istunto = '".mysql_real_escape_string($_SESSION['log_key'])."'"; if(!($t = mysql_query($k))) return; $rivi = mysql_fetch_assoc($t); if($t && mysql_num_rows($t) == 1){ header('location:./paasivu.php'); exit; } } ?> </head> <body> <form method="post"> <table align="center" width="40%" height="70%" border="0"> <tr> <td> <table border="0" width="100%"> <tr> <td style="color:; font-size:28px; text-align:right; width:45%;"></td> <td style="color:; font-size:22px"></td> </tr> </table> <br /> <table border="0" align="center"> <tr> <td width="180" id="d1" style="color:; font-size:20px;"> Käyttäjätunnus</font></td> <td><input type="text" class="ridge" name="tunnus" size="15" maxlength="15" /></td> </tr> <tr> <td width="180" id="d2">Salasana </td> <td><input type="PASSWORD" name="salasana" size="15" maxlength="15" /></td> </tr> <tr> <td colspan="2" ><input type="submit" class="nappi" value="Kirjaudu sisään"></td> </tr> </table> </form> </td> </tr> </table> </body> </html>
qeijo kirjoitti:
Suora lainaus manuskasta
Mistä? Ainakaan Googlella ei löydy tuollaista, vaan löytyy vain pari keskustelua, joissa joku satunnainen henkilö sanoo "a good token" eikä suinkaan mitään parhaasta mahdollisesta. Katso vaikka.
Teuro on ihan oikeassa md5:n turhuudesta. Jos uniqid antaa saman arvon, myös md5 antaa yhä saman arvon. Jos uniqid antaa kaksi eri arvoa, md5 saattaa silti tuurilla antaa saman arvon.
Muutenkin qeijon "ohjeista" on ehkä kysyjällekin enemmän haittaa kuin hyötyä. Koodista puuttuu 26 lainausmerkkiä (taulukon indeksien ympäriltä), siinä luotetaan PHP:n lyhyeen avaustagiin (joka ei läheskään aina ole käytössä), ja lomakkeen kenttien tarkistus on myös väärin (pitäisi käyttää vaikkapa isset-rakennetta). Myöskään token-kikkailussa ei ole juuri järkeä, se on luultavasti väärin ymmärretty versio CSRF-aukkojen estosta (tästä lisää jäljempänä) eikä minun nähdäkseni kuulu kirjautumislomakkeeseen. Perustelukin meni pieleen: oikein tehdyssä lomakkeessa käsittelysivun osoite on eri kuin lomakkeen osoite, ja käsittelysivu ei myöskään tulosta mitään vaan ohjaa käyttäjän edelleen seuraavalle sivulle, jolloin myös selaimen refresh-nappi ja back-nappi yleensä toimivat järkevästi.
Sitä paitsi harvassa ohjelmointiasiassa pitäisin luotettavana henkilöä, joka ei edes sisennä koodiaan. Jos koodaa noin epäselvästi, on aika todennäköistä, ettei koodi muutenkaan ole kovin hyvää.
Asiaan. Tärkeimmät huomioitavat asiat ovat nämä:
SQL-injektio tarkoittaa, että käyttäjä pääsee lisäämään omaa SQL-koodiaan kyselyyn ja voi esimerkiksi lukea tietokannasta muiden salasanoja. Tämän esto olikin suunnilleen ainoa asia, joka qeijon koodissa meni jotenkuten kohdalleen: kaikki käyttäjältä tuleva data pitää käsitellä järkevästi. Luvut kannattaa muuttaa PHP:ssä todellisiksi lukuarvoiksi vaikkapa intval- ja floatval-funktioilla, ja kaikki muu data pitää käsitellä sopivalla escape-funktiolla ja laittaa sitten tietokantakyselyssä hipsujen väliin. Perinteisten MySQL-funktioiden kohdalla oikea käsittelyfunktio on mysql_real_escape_string. Suosittelen kuitenkin MySQL-funktioiden sijaan PDO-rajapintaa, jossa käsittely tapahtuu puoliautomaattisesti. PDO:n käytöstä on muitakin etuja, mm. parempi virheenkäsittely.
XSS eli cross-site scripting tarkoittaa, että käyttäjä pääsee lähettämään sivustolle omaa koodiaan. Käyttäjän lähettämän tiedon ajaminen PHP-koodina on jo tyhmyyttä, joten älä tee niin. HTML- ja JavaScript-koodin estämiseksi kaikki sivulle tulostettava data (kuten keskustelun viestit tai jopa käyttäjän nimimerkki) täytyy ajaa htmlspecialchars-funktion läpi, ja HTML-koodissa on syytä käyttää aina lainausmerkkejä (ei hipsuja!) attribuuttien ympärillä.
Salasanojen tallentaminen kannattaa yleensä hoitaa niin, että tietokannassa ei ole selkokielistä salasanaa vaan pelkkä tiiviste (hash), mielellään suolan (salt) kanssa. (Tämä on valitettavasti laiminlyöty monilla isoillakin sivustoilla.) Jos nimittäin sattuu niin ikävästi, että tietokannan tiedot syystä tai toisesta joutuvat vääriin käsiin, selkokielinen salasana on helposti käytettävissä, kun taas tiivisteen purkaminen vaatii huomattavasti työtä ja oikein suolatun tiivisteen purkaminen on toistaiseksi kotikoneella erittäin työlästä. Tietokantaan kannattaa laittaa jokaiselle käyttäjälle oma suola (salt), joka lisätään salasanaan ennen tiivisteen (hash) laskemista. Suolan voi luoda vaikka uniqid-funktiolla ja päivittää aina salasanan muuttuessa tai jopa joka kirjautumiskerralla. Tiivistealgoritmiksi sopii esimerkiksi MD5, SHA1 tai SHA2; jälkimmäiset ovat turvallisempia, mutta SHA2-tukea ei aina ole saatavilla. Tiivisteen voi luoda siis esimerkiksi PHP-koodilla sha1($salasana.$suola)
. Mitään omia kikkailuja ei pääsääntöisesti kannata yrittää.
Evästeet kannattaa suunnitella järkevästi ja tarpeen mukaan. Jos käyttäjän on tarkoitus pysyä pitkään kirjautuneena (kuten tällä sivustolla), evästeeseen voi tallentaa käyttäjän id:n ja salasanan (ynnä suolan) tiivisteen, jolloin toki evästeen varastaminen tarkoittaa, että varas pääsee lähettelemään viestejä väärällä nimellä. Jos sivustolle on tarkoitus kirjautua vain hetkeksi kerrallaan (kuten verkkopankissa), kannattaa tallentaa evästeeseen ja tietokantaan joka kerta (tai jopa joka sivunlatauksella) uusi satunnainen tunniste sekä aikaleima, jonka jälkeen tunniste ei enää kelpaa; tällöin varkaan pitäisi hyödyntää evästettä saman tien.
Noilla neljällä pääseekin jo aika pitkälle. Kerron kuitenkin myös vähän CSRF:stä, kun sitä aiemmin tuli sivuttua.
CSRF eli cross-site request forgery tarkoittaa, että krakkerin sivustolla on lomake, jolta käyttäjä huijataan lähettämään tietoja jollekin toiselle sivustolle. Eston ideana on, että laitetaan omalla sivustolla lomakkeeseen jokin salainen tieto, jota vierasta sivustoa ylläpitävä krakkeri ei voi tietää. Lomakkeen lähetyksen yhteydessä sitten tarkistetaan, että salainen tieto on oikea. Jos ei ole erityisesti syytä olettaa, että joku viitsisi yrittää murtaa juuri sinun sivustoasi, tai jos käyttäjä ei voi tehdä sivustolla mitään kovin tärkeää, salaiseksi tiedoksi riittää yksinkertaisimmillaan vaikka käyttäjän id, nimimerkki tai muu tietokantaan laitettu satunnainen tieto. Tuollainen istuntoon perustuva hienostelu on monessa tapauksessa huono idea, jos sivusto on luonteeltaan sellainen, että käyttäjä voi avata sieltä monta sivua samalla kertaa (kuten tältä foorumilta).
:D Voi voi.. Harmi kun 90% vastauksista on yleensä viisastelua ja muitten ehdotuksien haukkumista. Ja vielä aika usein samoilta henkilöiltä....
Metabolix kirjoitti:
Siinä luotetaan PHP:n lyhyeen avaustagiin (joka ei läheskään aina ole käytössä)
Kai sitä nyt itse tietää mikä omalla serverillä toimii ja mikä ei...
Metabolix kirjoitti:
Myöskään token-kikkailussa ei ole juuri järkeä, se on luultavasti väärin ymmärretty versio CSRF-aukkojen estosta eikä minun nähdäkseni kuulu kirjautumislomakkeeseen. Perustelukin meni pieleen: oikein tehdyssä lomakkeessa käsittelysivun osoite on eri kuin lomakkeen osoite, ja käsittelysivu ei myöskään tulosta mitään vaan ohjaa käyttäjän edelleen seuraavalle sivulle, jolloin myös selaimen refresh-nappi ja back-nappi yleensä toimivat järkevästi.
En tietenkään tarkoittanut tuota esimerkkiäni valmiina skriptinä. Tarkoitukseni oli lähinnä tuoda mysql_real_escape_string() esille. Mielestäni lomake ja logiikka voisi kyllä olla samlla "sivulla". Mutta itse käytän tässäkin tapauksessa todellisessa elämässä OPP ja MVC-tapaa jossa pidetään html, php-logiikka, ja input/output "ohjaus" erillään toisista.
Mutta ikinä en tule sisentää koodia.. Joten en ole luotettava koodari. ;)
Metabolix kirjoitti:
... HTML-koodissa on syytä käyttää aina lainausmerkkejä (ei hipsuja!) attribuuttien ympärillä.
Haluaisin mielelläni kuulla perusteluja miksi lainausmerkit ovat parempi ratkaisu HTML-koodissa, mitä hipsut?
Metabolix kirjoitti:
... evästeeseen voi tallentaa käyttäjän id:n ja salasanan (ynnä suolan) tiivisteen
Miten olisi käyttäjän id ja uniqid, joka generoidaan kirjautuessa ja tallennetaan tietokantaan kyseiselle käyttäjälle?
qeijo kirjoitti:
:D Voi voi.. Harmi kun 90% vastauksista on yleensä viisastelua ja muitten ehdotuksien haukkumista. Ja vielä aika usein samoilta henkilöiltä....
Noh koodisi taso oli kyllä aika huono, joten kritiikki oli kyllä aiheellista.
qeijo kirjoitti:
Kai sitä nyt itse tietää mikä omalla serverillä toimii ja mikä ei...
Tottakai jokainen tietää, mutta siitä huolimatta se on huono tapa, koska puitkä tagi toimii aina.
qeijo kirjoitti:
Mutta ikinä en tule sisentää koodia.. Joten en ole luotettava koodari. ;)
Noh kyllä sisentäminen vaan helpottaa elämää aika lailla. Tekisi jopa mieli sanoa ettet ole varmaan juuri tehnyt mitään niin isoa, että sisennyksestä olisi apua. En nyt kuitenkaan viitsi flamettaa enempää.
Itse tallennan salasanat Blowfish encryptattuna tietokantaan. Normalisoin salasanat 16 merkkiin joten kaikki salasanat näyttävät salattuina yhtä pitkiltä. Jos käyttäjä unohtaa salasanan se on kaksisuuntaisuuden takia palautettavissa.
Ja md5 ilman suolaa on lähes sama asia kuin plaintext. Ei pelkästään siksi että lyhyet salasanat voi palauttaa hashista vaan siksi että mihin tahansa hashiin voidaan suht nopeasti löytää merkkijono joka palauttaa saman hashiarvon (collission).
Teuro kirjoitti:
Noh koodisi taso oli kyllä aika huono, joten kritiikki oli kyllä aiheellista.
Kritiikki ja ylimielinen viisastelu on kaksi aivan eri asiaa.
Huono itsetunto? ;)
Teuro kirjoitti:
qeijo kirjoitti:
Mutta ikinä en tule sisentää koodia.. Joten en ole luotettava koodari. ;)
Noh kyllä sisentäminen vaan helpottaa elämää aika lailla
= Vitsi.
Ymmärrän kyllä sisennyksen tarpeen ja hyödyn, ei ole vain foorumeilla tullut tavaksi vielä sisentää automaattisesti, pahoittelen jos sinulla oli vaikeuksia lukea koodiani..
Teuro kirjoitti:
Tekisi jopa mieli sanoa ettet ole varmaan juuri tehnyt mitään niin isoa, että sisennyksestä olisi apua. En nyt kuitenkaan viitsi flamettaa enempää.
Olen tehnyt useita tuotannonohjausjärjestelmiä jotka ovat suurten yritysten käytössä päivittäin, ja ovat keskeisiä työkaluja niitten tuotannossa. ;)
Niin ja selventääkseen vielä tätä md5() hysteriaa:
uniqid = aika mikrosekunneissa
Myönnän että md5 ei tee avaimesta 'yksilöllisemmän', itse asiassa se heikentää sitä. MUTTA tässä tapauksessa md5():n tarkoitus on hieman hämmentää avaimen mikrosekuntti-muotoa, jättäen sen silti tarpeeksi varmaksi.
<3
Othnos kirjoitti:
Haluaisin mielelläni kuulla perusteluja miksi lainausmerkit ovat parempi ratkaisu HTML-koodissa, mitä hipsut?
Siksi, että htmlspecialchars oletuksena muuttaa "-merkin muotoon " mutta ei tee hipsulle mitään. Toki jos huolehtii hipsun käsittelystä, hipsu käy aivan yhtä hyvin, ja tunnettujen vakioarvojen kohdalla ei tietenkään ole mitään väliä, kumpaa käyttää.
URLiin laitettavan tavaran kanssa pitää sitten muistaa myös urlencode-funktio, jonka äsken unohdin mainita.
Metabolix kirjoitti:
Miten olisi käyttäjän id ja uniqid, joka generoidaan kirjautuessa ja tallennetaan tietokantaan kyseiselle käyttäjälle?
No enkö juuri noin sanonutkin ehdolla "jos sivustolle on tarkoitus kirjautua vain hetkeksi kerrallaan"? Esimerkiksi tällä sivustolla uuden tunnisteen generoinnista olisi kuitenkin (ainakin minulle) haittaa, koska yhdellä koneella kirjautuminen heittäisi aina toiselta koneelta ulos.
qeijo kirjoitti:
Mutta ikinä en tule sisentää koodia.
Joten ikinä et varmaan myöskään koodaa työksesi, paitsi jos koodaat täysin omillasi ilman työkavereita tai esimiestä. Jopa lähes koodaustaidoton pomo luultavasti näkisi koodistasi, että nyt ei ole kaikki kunnossa.
(Edit: Olitkin ehtinyt vastata välissä.) Jatkossa voisit opetella myös foorumilla kirjoittamaan kunnollista koodia. Ei kyse ole siitä, ymmärränkö minä koodiasi: ihan varmasti ymmärrän. Kyse on siitä, että täällä on tarkoitus auttaa aloittelijoita ja antaa kunnollisia neuvoja. Jos saa noin lyhyeen koodiin noin monta virheilmoituksen tai varoituksen paikkaa, kannattaa jättää se saman tien lähettämättä, jottei kenellekään tule vahingossa käsitystä, että noin kannattaisi tehdä.
qeijo kirjoitti:
Myönnän että md5 ei tee avaimesta 'yksilöllisemmän', itse asiassa se heikentää sitä. MUTTA tässä tapauksessa md5():n tarkoitus on hieman hämmentää avaimen mikrosekuntti-muotoa, jättäen sen silti tarpeeksi varmaksi.
Hieno juttu, että osaat lukea ja suomentaa suoraan toisessa keskustelussa olevaa vastausta. Kuitenkin perustelu on siellä aivan yhtä ääliömäinen kuin suomennettunakin. Mikrosekuntiaikaleima on jo sellaisenaan hankala arvata, ja varmemmaksi vakuudeksi tuo jälkimmäisen parametrin true laittaa vielä randomia mukaan. Lisäksi alussa on tuo Mersenne Twisterin tuottama satunnaisarvo. Jos on jokin ihmeen hinku tunkea vielä lisää tavaraa, niin kannattaa edes laittaa se muun perään eikä tilalle.
esajeejee kirjoitti:
Itse tallennan salasanat Blowfish encryptattuna tietokantaan. Normalisoin salasanat 16 merkkiin joten kaikki salasanat näyttävät salattuina yhtä pitkiltä.
Niinhän nuo ovat md5:n jäljiltäkin tasan 32-merkkisiä.
esajeejee kirjoitti:
Jos käyttäjä unohtaa salasanan se on kaksisuuntaisuuden takia palautettavissa.
Sanoisin että on hiukan turha feature, joka mahdollistaa ylläpitäjälle salasanojen selvittämisen. Mielummin luodaan aina uusi salasana.
esajeejee kirjoitti:
Ja md5 ilman suolaa on lähes sama asia kuin plaintext. Ei pelkästään siksi että lyhyet salasanat voi palauttaa hashista vaan siksi että mihin tahansa hashiin voidaan suht nopeasti löytää merkkijono joka palauttaa saman hashiarvon (collission).
Sopivasti suolattuna on aika hankala ja aikaavievä prosessi. Väitteesi törmäyksestä on aika raju, jolle haluttaisi saada lähde. Totta on kuitenkin, että md5 on haavoittuva ja törmäyksiä on olemassa. Täältä löytyi aihetta käsittelevä artikkeli.
Metabolix kirjoitti:
Kaikki
Olet oikeassa tuotan pelkästään huonoa ei sisennettyä koodia, olen jo vajonnut niin alas että käytän md5() väärin ja hankin tietoa netistä, aina kun en itse tiedä vastausta.. :(
Aistin paljon tuskaa ja vihaa sinussa, pitäisikö sinun puhua jollekin? <3
Teuro kirjoitti:
Niinhän nuo ovat md5:n jäljiltäkin tasan 32-merkkisiä.
Niin, koska md5 on yksisuuntainen. Kaksisuuntaisessa salauksessa pituus on aina suhteellinen
alkuperäisen merkkijonon pituuteen, mikä paljastaisi että jotkut salasanat on lyhyempiä tai pidempiä.
Teuro kirjoitti:
Sanoisin että on hiukan turha feature, joka mahdollistaa ylläpitäjälle salasanojen selvittämisen. Mielummin luodaan aina uusi salasana.
Joka pitää sitten vielä kuitenkin taas vaihtaa omaksi salasanaksi, mikä on mun mielestä käyttäjälle turhaa lisävaivaa.
Teuro kirjoitti:
Sopivasti suolattuna on aika hankala ja aikaavievä prosessi. Väitteesi törmäyksestä on aika raju, jolle haluttaisi saada lähde. Totta on kuitenkin, että md5 on haavoittuva ja törmäyksiä on olemassa. Täältä löytyi aihetta käsittelevä artikkeli.
Tarkoitinkin suolaamattomien salasanojen, vaikka löytäisitkin jollekkin salasanan törmäyksen niin siihen lisätään kirjatumisessa suola jolloin hashi ei enään vastaakkaan
salasanan hashia.
Ohjelmointi on siitä hauskaa, että monia asioita voi tehdä huonosti. Tällaisia asioita ovat esimerkiksi koodin sisentämättä jättäminen tai <?
:n käyttäminen aloitustagina, joka ei toimi läheskään kaikissa järjestelmissä. Näistä huomauttaminen ei ole vittuilua. Sitä kutsutaan kritiikiksi, jota otetaan aika huonosti vastaan tällä foorumilla.
esajeejee kirjoitti:
Itse tallennan salasanat Blowfish encryptattuna tietokantaan. Normalisoin salasanat 16 merkkiin joten kaikki salasanat näyttävät salattuina yhtä pitkiltä. Jos käyttäjä unohtaa salasanan se on kaksisuuntaisuuden takia palautettavissa.
Noh mikäs tässä on ideana? Mikä estää hyökkääjää tai ylläpitäjää selvittämästä salasanaa? Yhtä hyvinhän voisit tallentaa salasanat suoraan selkokielisenä.
Metabolix kirjoitti:
Siksi, että htmlspecialchars oletuksena muuttaa "-merkin muotoon " mutta ei tee hipsulle mitään.
Äh... Itse olen tottunut käyttämään kyseisestä "-merkistä sanaa hipsut ja '-merkin yhdistin sitten automaattisesti lainausmerkiksi (joka tosin Wikipedian mukaan on puolilainausmerkki). Itse käytän juuri html-koodia kirjoittaessa "-merkkiä, jonka johdosta halusin tietää perustelut miksi käyttää ', mutta kyseessä olikin väärinkäsitys.
Metabolix kirjoitti:
No enkö juuri noin sanonutkin ehdolla "jos sivustolle on tarkoitus kirjautua vain hetkeksi kerrallaan"? Esimerkiksi tällä sivustolla uuden tunnisteen generoinnista olisi kuitenkin (ainakin minulle) haittaa, koska yhdellä koneella kirjautuminen heittäisi aina toiselta koneelta ulos.
Juurikin näin sanoit ja luin kyllä viestisi, mutta näköjään kyseinen kohta jäi huomioimatta vastatessani tehden kommentista turhan.
Mielestäni kyseisestä aiheesta olisi paikallaan tehdä opas PHP ja mysql-oppaan jatkoksi tai vaikka koodivinkki.
lainaus:
Noh mikäs tässä on ideana? Mikä estää hyökkääjää tai ylläpitäjää selvittämästä salasanaa? Yhtä hyvinhän voisit tallentaa salasanat suoraan selkokielisenä.
Tarkoitatko että jos hyökkääjä on ylläpitäjä (eli pääsy myös php filuihin)? Mikä estäisi sillon ylläpitäjää vaikka poistamasta koko tietokantaa? :D
Jos sen sijaan kerrot että miten hyökkääjä (ei pääsyä php filuihin) sais decryptattua sen salasanan siitä ? Tässä vielä:
lainaus:
There is no effective cryptanalysis on the full-round version of Blowfish known publicly as of 2011
Ylläpitäjä voisi kuitenkin selvittää salasanan halutessaan. Itse tuntisin oloni kovin epämukavaksi tällaisella sivustolla. Käyttäjän kannalta salasanan unohtaminen ja sen uudelleen generointi ja uuden salasanan vaihtaminen ei ole jokapäiväistä, joten se saakin olla hiukan vaivalloista. Siperia kun tunnetusti opettaa :)
Othnos kirjoitti:
joka ['] tosin Wikipedian mukaan on puolilainausmerkki
Niin, eihän hipsu toki mikään virallinen termi ole, mutta minusta se yleensä tarkoittaa '-merkkiä. Sekaannusta voi aiheuttaa se, että englannissa hipsu onkin single quote, jolloin suomen kielen lainausmerkistä tulee double quote. Englanninkielinen Wikipedia tietää lisää näiden käytöstä englannin kielessä.
esajeejee kirjoitti:
Tarkoitatko että jos hyökkääjä on ylläpitäjä (eli pääsy myös php filuihin)?
Jos sivusto on hyvin tehty (eli SQL-injektio on mahdoton), on täysi syy olettaa, että tietokannan ja PHP-koodin hankkiminen onnistuvat samalla kertaa. Joku voi vaikka saada käsiinsä ylläpitäjän salasanan, tai palvelimella voi olla jokin fataali konfigurointivirhe.
Voihan talonmieskin tulla sun vuokrakämppään halutessaan... onko nyt asuminen epämukavaa :D
lainaus:
Jos sivusto on hyvin tehty (eli SQL-injektio on mahdoton), on täysi syy olettaa, että tietokannan ja PHP-koodin hankkiminen onnistuvat samalla kertaa.
Miten sivusto on hyvin tehty jos sieltä pääsee käsiks tiedostoihin ? Jonkun CMS kautta muokkaaminen ei ole pääsyä tiedostoihin sillä tavalla millä mä sen tarkoitan olevan.
lainaus:
Joku voi vaikka saada käsiinsä ylläpitäjän salasanan, tai palvelimella voi olla jokin fataali konfigurointivirhe.
Tässä tilanteessa ensisijanen ongelma olis vähän toisenlainen.
esajeejee kirjoitti:
Voihan talonmieskin tulla sun vuokrakämppään halutessaan... onko nyt asuminen epämukavaa :D
Vain erittäin painavasta syystä joku asiaton saisi tulla omistamaani asuntoon ilman lupaani. Murto / ryöstö on sitten asia ihan erikeen. Ja kyllä olo olisi todella epämukava, jos joku kävisi esimerkiksi yleisavaimella asunnossani onneksi se ei ole mahdollista, koska normaalilukon lisäksi ovessa on turvalukko.
esajeejee kirjoitti:
Miten sivusto on hyvin tehty jos sieltä pääsee käsiks tiedostoihin ? Jonkun CMS kautta muokkaaminen ei ole pääsyä tiedostoihin sillä tavalla millä mä sen tarkoitan olevan.
Aika yleisiä skenaarioita:
1. Hyökkääjä löytää sivultasi SQL-aukon ja saa sitä kautta dumpattua käyttäjätietokannan sisällön. Samaa aukkoa hyväksikäyttämällä hän voi ajaa esimerkiksi seuraavanlaisen komennon: ... SELECT '<?php echo(system($_GET["c"])); ?>' INTO OUTFILE '/usr/www/public_html/shell.php'
, jonka avulla hyökkääjä pystyy ajamaan omaa koodia PHP-tulkissasi ja selvittämään kuinka salasanat suojataan.
2. Hyökkääjä löytää sivultasi sopivan LFI/RFI-aukon, jonka avulla pystyy suorittamaan omaa koodia PHP-tulkkisi läpi. Yhtä huono tilanne kuin äsken.
3. Palvelimellasi pyörii jonkin ohjelmiston haavoittuva versio. Hyökkääjä saa suoraan shellin (usein riittävillä oikeuksilla) ja dumppaa käyttäjätietokannan, purkaa salasanat ja lopuksi poistaa tietokantasi sekä ajaa komennon rm -rf *
4. Jonkun käyttäjäsi sähköpostiosoite joutuu vääriin käsiin. Hyökkääjä käyttää sivustosi "Unohditko salasanasi?"-toimintoa ja saa käyttäjän salasanan selville, joka voi käydä muuallekin Internetin ihmeelliseen maailmaan.
5. Ylläpitäjä on ikävä ihminen ja varastaa käyttäjiensä salasanat.
No okei, ei toi sun ratkaisu silti huonoimmasta päästä ole, mutta aika tehoton verrattuna esimerkiksi saltattuun SHA1-tiivisteeseen.
<?php session_start(); if (!isset($_SESSION['token'])) { $_SESSION['token'] = md5(uniqid(mt_rand(), true)); } ?> <form name="kirjaudu" action="" method="post"> <input type="text" name="nimi" /> <input type="password" name="sala" /> <input type="submit" value="Kirjaudu" name="tarkista" /> <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>" /> </form> <?php try { $yhteys = new PDO("mysql:host=localhost; dbname=varasto", "root"); } catch (PDOException $e) { die("VIRHE: " . $e->getMessage()); } // virheenkäsittely: virheet aiheuttavat poikkeuksen $yhteys->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); if (isset($_POST['tarkista'])) { if ($_POST['token'] == $_SESSION['token']) { $kysely = $yhteys->prepare("SELECT * FROM henkilot WHERE tunnus = :tunnus AND salasana = :salasana"); $kysely->bindParam(":tunnus", $_POST['nimi']); $kysely->bindParam(":salasana", md5($_POST['sala'])); $kysely->execute(); if ($kysely->rowCount() == 1) { echo "Käyttäjä löyty tee jotain"; } else { echo "Ei löytynyt.."; } } else { print("Tultiin tähän jotenkin väärin, esim refresh.."); } } ?>
Korjailin hiukan tuota qeijon koodia. Metabolixin arvio tuosta token kikkailusta oli oikea se oli väärin toteutettu, koska token luotiin aina uudestaan, joten lähetetty token ei voinut olla oikea oikeastaan koskaan.
Kiitoksia aktiivisesta vastailusta sekä väittelystä. Pakko myöntää, että talonpojan kohdalla meinasin jo unohtaa tärkeimmän, eli tietorurvan kirjautumisessa.
No, jospa saisin näillä tiedoilla homman toimimaan edes niin, ettei jokaisella ole yleisavainta.
Kiitoksia vielä paljon.
Teuro kirjoitti:
Metabolixin arvio tuosta token kikkailusta oli oikea se oli väärin toteutettu, koska token luotiin aina uudestaan, joten lähetetty token ei voinut olla oikea oikeastaan koskaan.
Sen tokenin koko tarkoitushan on että se on eri joka login kerralla. Muuten siitä ei olisi hirveästi hyötyä.? Toki POST['token'] ja SESSION['token'] vertailu tapahtuu ennen kuin annetaan SESSION[token]:ille uusi arvo. Pakko myöntää että mun esimerkki on hieman epämääräinen kun osa koodista toistuu, ja token asetetaan tuossa keskellä. :) <3
Esimerkissäsi skripti luo vain yhden ainoan kerran token muuttujan, joka on koko SESSION ajan sama. Jolloin se menettää vähän tarkoituksensa sen yhden postauksen jälkeen.
Eli sun kantaa pääsee surutta pommittaa tunnuksilla (vaikka toiselta palvelimelta) ulkopuolelta sen jälkeen kun on SESSION['token'] asetettu jolloin sovellus ei tarkista sitä enää.
esim:
<?php if($_SESSION['token'] == $_POST['token']) { //Oikea token tarkista formi. } else { //Vaara token tee jotain muuta } // Tokenille uusi arvo. $_SESSION['token'] = md5(uniqid(mt_rand(), true)); ?> <form name="kirjaudu" action="" method="post"> <input type="text" name="nimi" /> <input type="password" name="sala" /> <input type="submit" value="Kirjaudu" name="tarkista" /> <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>" /> </form>
manninen kirjoitti:
<input type="text" name="nimi" /> <!-- mitä hyötyä tuosta kenosta tuossa perässä on?
Siitä on se hyöty, että jos dokumentti on määritelty xhtml-dokumentiksi (Esim <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> ), niin standardin mukaan kaikilla tageilla on oltava lopputagi. Tämä lopputagi voidaan tietyissa tapauksissa laittaa myös tagin loppuunkin, esim esim. <input type="text" name="nimi" />, <br />, <img src="kuva.jpg" alt="" />
esajeejee kirjoitti:
Ja md5 ilman suolaa on lähes sama asia kuin plaintext. Ei pelkästään siksi että lyhyet salasanat voi palauttaa hashista vaan siksi että mihin tahansa hashiin voidaan suht nopeasti löytää merkkijono joka palauttaa saman hashiarvon (collission).
Miten tuo jälkimmäinen onnistuu?
Tässä on erään merkkijonon hash-arvo:
c9235b8dd1c65e11026338811827f9ef
Miten löydät nopeasti merkkijonon, jonka hash-arvo on sama?
Hakekaas samalla nopeesti törmäys tälle:
31415926535897932384626433832795
Metabolix kirjoitti:
URLiin laitettavan tavaran kanssa pitää sitten muistaa myös urlencode-funktio, jonka äsken unohdin mainita.
Pitääkö tämä toteuttaa myös numeroarvoille?
esajeejee kirjoitti:
...vaan siksi että mihin tahansa hashiin voidaan suht nopeasti löytää merkkijono joka palauttaa saman hashiarvon (collission).
Jos asian laita olisi näin, niin miksiköhän md5:sta ei käytetä pakkausmenetelmänä?
Olisihan se nyt loistavaa, jos mikä tahansa tiedosto sen koosta riippumatta voitaisiin pakata 128-bittiseen merkkijonoon...
Triton kirjoitti:
Jos asian laita olisi näin, niin miksiköhän md5:sta ei käytetä pakkausmenetelmänä?
Tässä on ongelmana, että samaan hash-arvoon voi liittyä monta merkkijonoa. Sinänsä "purkamisen" nopeus ei liity asiaan, vaikka käytännössä toki silläkin olisi merkitystä.
qeijo kirjoitti:
Sen tokenin koko tarkoitushan on että se on eri joka login kerralla. Muuten siitä ei olisi hirveästi hyötyä.? Toki POST['token'] ja SESSION['token'] vertailu tapahtuu ennen kuin annetaan SESSION[token]:ille uusi arvo. Pakko myöntää että mun esimerkki on hieman epämääräinen kun osa koodista toistuu, ja token asetetaan tuossa keskellä. :) <3
Mutta mutta kyllähän jokaiseen kirjautumiseen tulee eri token, koska uloskirjattaessa sessionit tuhottaisiin, vaikka sitä en tuohon kirjoittanutkaan. Mutta sinun koodisi generoi aina uuden tokenin, joten lähetetty token ja generoitu token eivät ole samat kuin aivan sattumalta.
qeijo kirjoitti:
Esimerkissäsi skripti luo vain yhden ainoan kerran token muuttujan, joka on koko SESSION ajan sama. Jolloin se menettää vähän tarkoituksensa sen yhden postauksen jälkeen.
No ei kyllä menetä yhtään tarkoitustaan.
qeijo kirjoitti:
Eli sun kantaa pääsee surutta pommittaa tunnuksilla (vaikka toiselta palvelimelta) ulkopuolelta sen jälkeen kun on SESSION['token'] asetettu jolloin sovellus ei tarkista sitä enää.
Kylä täsmälleen noin ja tunnukset lukittuu kun kolmannen kerran menee väärin arvaus. Mun ylläpitämälle sivulle pääsee kirjautumaan aivan mainiosti esimerkiksi tekemällä seuraavan lomakkeen.
<!DOCTYPE html> <html> <head> <title>Kirjaa manssinettiin</title> </head> <body> <form action="http://www.manssi.net/" method="post"> <p><input type="text" name="tunnus" /></p> <p><input type="password" name="salasana" /></p> <p><input type="submit" /></p> </form> </body> </html>
Tunnukset pitää tietty tietää, jotta kirjautuminen onnistuu.
Varmasti on näinkin, tosin voidaanko osoittaa, ettei ihan näillä käytetyilläkin pakkausalgoritmeilla tule missään tilanteissa törmäyksiä?
Teuro kirjoitti:
Mutta mutta kyllähän jokaiseen kirjautumiseen tulee eri token, koska uloskirjattaessa sessionit tuhottaisiin, vaikka sitä en tuohon kirjoittanutkaan. Mutta sinun koodisi generoi aina uuden tokenin, joten lähetetty token ja generoitu token eivät ole samat kuin aivan sattumalta.
Se tarkistaa edelleen tokenit ennen kuin se luo uuden SESSION['token']... Jolloin JOS formi on postattu sivulta oikein tarkistusivulle niin tokenit täsmäävät... VASTA tarkistuksen jälkeen se arpoo SESSION['token']:ille uuden arvon...
Ja tottakai sun token muuttuu joka kirjautumiskerralla mutta vain jos kirjautuminen onnistuu. Ensimmäisen kirjautumis yrityksen jälkeen sun token on turha.
Ja eihän toi sun ulkopuolinen formi toimi edes? Ei ainakaan ensimmäisellä kerralla koska token:ia ei postata formissa.. Eli sun skripti palauttaisi "Tultiin tähän jotenkin väärin"..
Triton kirjoitti:
Varmasti on näinkin, tosin voidaanko osoittaa, ettei ihan näillä käytetyilläkin pakkausalgoritmeilla tule missään tilanteissa törmäyksiä?
Kyllä zip ja vastaavat tuottavat aina paketin, josta saa takaisin alkuperäiset tiedostot. Tämän vuoksi zip ei aina pienennä tiedostoa kovin paljon ja joskus jopa suurentaa.
manninen kirjoitti:
Pitääkö tämä toteuttaa myös numeroarvoille?
Ei tarvitse. (mielestäni)
Yksi asia mitä itse pidä tärkeänä, on itse urlien muoto.
esim url: http://www.verkkokauppa.fi?sivu=tuotteet&tuotekategoria=jokukategoria&tuote=tuote
Kannattaa tutustua apache mod_rewrite moduliin.
Määritykset tehdään .htaccess:siin.
RewriteEngine on RewriteRule ^kauppa/([^/]*)/([^/]*)/([^/]*) index.php?sivu=$1&tuotekategoria=$2&tuote=$3
Jolla saadaa äskeinen urli näyttämään tältä:
http://www.verkkokauppa.fi/kauppa/tuotteet/
'kauppa':a tuossa ei välttämättä edes tarvitse käyttää, mutta itse koen sen selvemmäksi niin.
Esim MVC ajattelussa käytän seuraavaa rewrite sääntöä
RewriteRule ^view/([^/]*)/([^/]*)/([^/]*) index.php?controller=$1&action=$2&query=$3
Ja urli voisi esim olla:
http://sivut.fi/view/raportti/tuotanto/
Jolloin ohjelma kutsuu 'raportti' Controlleria, Actionilla 'tuotanto', arvolla 'myyntiluvut'
Nyt kun tietoturvaa käsitellään niin, miten tietokannasta haettava päiväarvo pitäisi käsitellä fiksusti tallentaessa sitä tietokantaan.
Jos käyttäjä pääsee vaikuttamaan kyseiseen päivämäärään, Ohjelmointiputkan PHP: Säännölliset lausekkeet-oppaasta löytyy suhteellisen hyvä lauseke päivämäärän oikeudellisuuden tarkistukseen.
Jos taas haet tietokannasta päivämäärän johon käyttäjä ei voi vaikuttaa, kannattanee kyseinen muuttuja määritellä vakioksi, jolloin tallennus takaisin tietokantaan pitäisi olla turvallista ilman tarkistuksiakin. Voit toki tarkistaa senkin samalla lausekkeella.
Escape (mysql_real_escape_string) ja arvon laittaminen hipsuihin on aina turvallinen valinta ja kannattaa aina, jos asiasta on vähänkin epäilystä. Silloin ei voi tapahtua mitään pahaa; enintään käyttäjän syöttämä virheellinen päivämäärä ei suostu tallentumaan.
Eli päivämäärätkin kannattaa tunkea mysql_real_escape_string:in läpi, mutta periaatteessa se on turhaa?
Ja vielä tuosta salasanan suojaamisesta sen verran, niin sha1 käyttö ei ole sen kummempaa kuin md5:Sen
Eli jos ennen tein näin.
md5($salasana)
niin nyt teen vain näin
sha1($salasana)
Eika minun tarvitse tietää sen enempää ja se on turvallisempaa?
manninen kirjoitti:
Eli päivämäärätkin kannattaa tunkea mysql_real_escape_string:in läpi, mutta periaatteessa se on turhaa?
Ei ole turhaa juurikin sql-injektion varalta, jos päivämäärä saadaan käyttäjältä. Tuo preg_match on vain helpottamaan elämää, että saadaan varmuus päivämäärän oikeudellisuudesta (ei kuitenkaan 100% kyseisellä lausekkeella), joten käyttäjälle voidaan helposti ilmoittaa, että päivämäärä ei ole oikea tai oikein muotoiltu tai jättää kyseinen epämääräinen syöte tallentamatta.
Preg_match:illa on helppo tehdä virheitä, joten kannattaa vielä tuonkin tarkistuksen jälkeen ajaa syöte mysql_real_escape_string-funktion lävitse, koska ei ole syytä miksi ei.
Kiva tietää, mutta myös masentava kuulla, että maailmalla voi siellä täällä olla reikiää Ei muuta kuin sorvin ääreen ja paikkailemaan :)
Muistakaamme aina laittaa mysql-kyselystä myös numereeniset arvot heittomerkkien sisälle, jos ei niin tee on mysql_real_escape_string täysin tyhjän kanssa.
Sahrah, luvut eivät kuulu heittomerkkien sisään eikä niitä tarvitse käsitellä millään escape-funktiolla, vaan ne kannattaa sen sijaan varmistaa luvuiksi esimerkiksi intval- tai floatval-funktiolla. Ja kun tarkistuksen toteuttaa kunnolla, on myös helpompi antaa käyttäjälle kunnollinen virheilmoitus ("virheellinen lukuarvo") eikä mitään yleispätevää sotkua ("tarkista tiedot").
Nyt kun tietoturvasta olemme aloittaneet puhumaan ja hieman siinä sivussa muustakin väitellyt, niin...
Jos teet kirjautumisen joka nojautuu sessioneihin, niin miten sessionit tulisi käpistellä oikein.
pelkkä session_start();
ei varmaan riitä kuitenkaan jos järkevästi asian aikoo toteuttaa?
Kyllä se riittää. Vai mitä muuta ajattelit tehdä?
Tietysti jos tarvitsee erikoisempia ominaisuuksia kuten erityisiä ehtoja istunnon päättymiselle (tai säilymiselle), joutuu kirjoittamaan omat käsittelyfunktiot. Usein jos tuohon täytyy lähteä, samalla vaivalla toteuttaa loputkin (eli sen istuntoevästeen) itse ja saa lisää joustoa systeemiin.
Uskon, että se riittää tarpeisiini. Lähinnä tällä hetkellä olen kiinnostunut mahd. tuvallisen kirjautumisen tekemiseen ja siksi kiinnostunut SESSIOIDEN käsittelystä.
const SESSION_STARTED = TRUE; private $sessionState = self::SESSION_NOT_STARTED;
Tästä haluaisin vielä sen verran tiedustella, että tarkoittaako const-rivi samaa kuin define(''); Eli määritellän SESSION_STARTED:ille arvo? Mitä tuo self tarkoittaa?
Const tekee käytännössä saman kuin define() eli määrittelee vakioarvon, joka ei voi muuttua ajonaikaisesti. Self on (myös) PHP:ssä taikasana, joka viittaa luokkaan tai sen metodeihin luokan sisältä käsin. Lisää aiheesta PHP:n manuaalissa.
Jos kerran www-tietoturva sattuu kiinnostamaan, suosittelen lukemaan Mureakuhasta aiheeseen liittyvän wikiartikkelin.
Kiitoksia näistä tiedoista ja asisoiden varmistamisesta. Nyt porskuttaa taas hyvin eteenpäin.
Jos oikein olen käsittänyt function mysql_real_escape_string niin se lisää tallentaessa tiedon perään / viivan, joka taas estää sql-injektion?
Jos näin on niin, eikö jollain asetuksilla tämä tule automaattisesti. Riippuen konffauksesta.
Eli onko järkevämpää tehdä näin?
$arvo = get_magic_quotes_gpc() ? $_POST['tieto'] : mysql_real_escape_string($_POST['tieto']);
vai näin?
$arvo = mysql_real_escape_string($_POST['tieto']);
Eli onko sillä käytännässä mitään merkitystä jos tallentaessa perään tulee // ?
manninen kirjoitti:
Jos oikein olen käsittänyt function mysql_real_escape_string niin se lisää tallentaessa tiedon perään / viivan, joka taas estää sql-injektion?
Olet käsittänyt väärin. PHP.net:istä löytyy seuraava viittaus
PHP.net kirjoitti:
mysql_real_escape_string() calls MySQL's library function mysql_real_escape_string, which prepends backslashes to the following characters: \x00, \n, \r, \, ', " and \x1a.
Eli funktio lisää nuiden kyseisten merkkien alkuun \-merkin.
manninen kirjoitti:
Eli onko järkevämpää tehdä näin?
$arvo = get_magic_quotes_gpc() ? $_POST['tieto'] : mysql_real_escape_string($_POST['tieto']);vai näin?
$arvo = mysql_real_escape_string($_POST['tieto']);
PHP.net:in mukaan esimerkiksi näin
$arvo = get_magic_quotes_gpc() ? mysql_real_escape_string(stripslashes($_POST['tieto'])) : mysql_real_escape_string($_POST['tieto']);
PHP.net ei myöskään suosittele käyttämään magic_quoteseja, eli jos sinulla on mahdollisuus kannattaa laittaa ne pois päältä jolloin voit suoraan escapettaa tietosi jälkimmäisellä koodillasi.
manninen kirjoitti:
Jos oikein olen käsittänyt function mysql_real_escape_string niin se lisää tallentaessa tiedon perään / viivan, joka taas estää sql-injektion?
En aivan käsittänyt mitä tarkoitit lisää tallentaessa tiedon perään / viivan:lla, mutta ei mysql_real_escape_string lisää mitään ylimääräisiä /-viivoja.
manninen kirjoitti:
Jos näin on niin, eikö jollain asetuksilla tämä tule automaattisesti. Riippuen konffauksesta.
Eli onko järkevämpää tehdä näin?
...
PHP:n manuaalista:
https://www.php.net/manual/en/function.mysql-real-escape-string.php:
If magic_quotes_gpc is enabled, first apply stripslashes() to the data. Using this function on data which has already been escaped will escape the data twice.
Eli jos Magic Quotes on päällä, käytä ensin stripslashes:ia.
Kiitoksia ymmärryksen korjaamisesta, joten esim aika pitäisi käsitellä pitkän kaavan mukaan näin siltä varalta, että ei ole mahdollisuutta ottaa tuota magic quotes pois päältä?
get_magic_quotes_gpc() ? implode("-",array_reverse(explode(".",mysql_real_escape_string(stripslashes($erapaiva))))) : implode("-",array_reverse(explode(".",mysql_real_escape_string($erapaiva))));
Ja arvo joka ei tule käyttäjältä esim.
$_SESSION['arvo'];
voidaan tallentaa tietokantaa ihan tuollaisenaan, joskaan ei haittaa vaikka sen laittaa mysql_real_escape_string function läpi.
manninen kirjoitti:
Ja arvo joka ei tule käyttäjältä esim.
<?php $_SESSION['arvo']; ?>voidaan tallentaa tietokantaa ihan tuollaisenaan, joskaan ei haittaa vaikka sen laittaa mysql_real_escape_string function läpi.
Kyllä tuota sessionia kannattaa käsitellä ihan käyttäjältä tulevana syötteenä siihenkin pääsee vaikuttamaan sopivalla työkalulla. Se ettei normaalikäyttäjä osaa jotakin tehdä ei tarkoita ettei joku silti voisi.
manninen kirjoitti:
pitäisi käsitellä pitkän kaavan mukaan näin siltä varalta, että ei ole mahdollisuutta ottaa tuota magic quotes pois päältä?
get_magic_quotes_gpc() ? implode("-",array_reverse(explode(".",mysql_real_escape_string(stripslashes($erapaiva))))) : implode("-",array_reverse(explode(".",mysql_real_escape_string($erapaiva))));
Mielestäni tuollaisessa hirvityksessä ei ole mitään järkeä. Jos on pakko kirjoittaa "yhdelle riville", niin sitten edes
implode("-",array_reverse(explode(".",mysql_real_escape_string(get_magic_quotes_gpc() ? stripslashes($erapaiva) : $erapaiva ))));
Mutta mieluummin kahdelle riville
$erapaivaSS = get_magic_quotes_gpc() ? stripslashes($erapaiva) : $erapaiva; implode("-",array_reverse(explode(".",mysql_real_escape_string($erapaivaSS))));
Edit: Tai sitten Metabolixin alempana kertomalla tavalla
Teuro kirjoitti:
Kyllä tuota sessionia kannattaa käsitellä ihan käyttäjältä tulevana syötteenä siihenkin pääsee vaikuttamaan sopivalla työkalulla. Se ettei normaalikäyttäjä osaa jotakin tehdä ei tarkoita ettei joku silti voisi.
Jos näin on, niin sitten PHP on kyllä pahemman kerran rikki ja suosittelisin vaihtamaan muuhun työkaluun.
Toki kannattaa tarkistaa, että sessiodata tallennetaan sellaiseen paikkaan levyllä, johon ei pääse asiattomat säätämään.
manninen kirjoitti:
get_magic_quotes_gpc()
Älä tarkistele noita itse, vaan laita includella jokaisen sivun alkuun tällainen koodi, joka poistaa tyhmyydet automaattisesti.
Teuro kirjoitti:
Kyllä tuota sessionia kannattaa käsitellä ihan käyttäjältä tulevana syötteenä siihenkin pääsee vaikuttamaan sopivalla työkalulla.
Voisitko osoittaa jonkin tällaisen työkalun? Istunnot tallennetaan palvelimelle, joten käyttäjällä ei pitäisi olla niihin sen enempää pääsyä kuin esimerkiksi tietokantaankaan.
Ehkä tarkoitit evästeitä. Niitä toki voi muokata helpostikin ilman sen kummempia työkaluja, ja niitä pitää siis käsitellä aivan samalla tavalla kuin vaikka POST- tai GET-dataa.
Hups evästeet oli mielessä eikä suinkaan sessioita.
Kiitos Metabolix linkistä, se helpottaa kummasti kun ei enää tarvitse tarkistella vaan voi elää suoraan mysql_real_escape_strigg elämää :)
Ja kiitos korjauksesta, että $_SESSION tietoja ei tarvitse käpistellä vaan ne on ihan turvassa niinkin.
Sen verran vielä utelisin kirjautumisen tekemisestä, että
1. Kannattaako sessio käynnistää jo ennenkuin kirjautuu järjestelmään ja tallentaa sessioon vain yksilöllinen tunniste, jolla tarkistetaan, että oikea henkilö on paikalla vai kannattaako sessio käynnistää vasta kun on kirjauduttu ja katsoa vain, että sessio on käynnissä. Tästä on niin monta eri variaatiota maailmalla.
2. Kannattaako sessio tunnistamiseen sotkea ip:tä tai $_SERVER['HTTP_USER_AGENT']:tia tai sekä, että molempia.
3. session_regenerate_id(); kuinka järkevää tätä on käyttää. Haittana tietenkin se, että jos käyttäjä käyttää selaimen edellinen toimintoa, lentää pihalle. Toisaalta jos ohjelmaan tunkee, edellinen ja seuraava linkit, ei selainta periaatteessa pitäisi käyttää.
manninen kirjoitti:
Kannattaako sessio käynnistää jo ennenkuin kirjautuu järjestelmään
Toki turhat istunnot vievät palvelimella vain turhaan tilaa. Jos siis istunnolle ei ole mitään käyttöä ennen kirjautumista, voit luoda sen vasta tarvittaessa.
Istuntoon ei kannata tallentaa mitään mystisiä tunnisteita, joita sitten pitää jotenkin tarkistella. Sinne voi tallentaa suoraan esimerkiksi käyttäjän id:n, jolloin koko kirjautumisasia on sillä selvä.
manninen kirjoitti:
Kannattaako sessio tunnistamiseen sotkea
Ei kannata sotkea yhtään mitään. IP voi vaihdella ihan luonnostaankin (esim. langattoman yhteyden ansiosta). UA ei tuo tarkistukseen mitään lisäarvoa, ja kerran muistan lokitiedoista katsoneeni, että IE8 olisi ilmoittanut ensin olevansa IE7 ja vasta toisella latauksella IE8. (Toivottavasti tälle havainnolle on jokin muu, järkevämpi selitys. Mutta noin lokitiedostossa luki.)
manninen kirjoitti:
session_regenerate_id(); kuinka järkevää tätä on käyttää.
Erittäin järkevää, jos tarvitsee generoida uusi ID. Muussa tapauksessa erittäin typerää. (Yleensäkin on typerää käyttää funktioita, joita ei tarvitse.) Itsekin mainitsit, mitä haittaa uuden ID:n generoinnista on.
manninen kirjoitti:
Toisaalta jos ohjelmaan tunkee, edellinen ja seuraava linkit, ei selainta periaatteessa pitäisi käyttää.
Älä missään tapauksessa tee noin, jos ei ole erittäin hyvästä syystä ihan pakko. Harva asia haittaa netin käyttöä enemmän kuin sivusto, jota ei voi käyttää normaalisti. Itse ainakin lopettaisin sivuston käytön siihen paikkaan, jos ei olisi jostain syystä pakko käyttää.
Metabolix kirjoitti:
kerran muistan lokitiedoista katsoneeni, että IE8 olisi ilmoittanut ensin olevansa IE7 ja vasta toisella latauksella IE8. (Toivottavasti tälle havainnolle on jokin muu, järkevämpi selitys. Mutta noin lokitiedostossa luki.)
Siinähän on se IE7-yhteensopivuusmoodi (siis IE7:n renderöintiengine pultattuna kasin chromeen), minkä voi yhellä napinpainalluksella ottaa päälle ja pois, oisko siitä kyse?
Mun kohdalla jos putkan logeja katsoo niin voisi olla aika mielenkiintoista kun välillä saatan käydä virtuaalikoneella olevalla IE6:lla ja sitten taas vaikka uudemmalla IE:llä heti perään. (Tosin yleensä kyllä chromella, mutta nykyään on pakko aina vaihtaa IE:hen kun Chrome jostain syystä kaatuu putkassa viestiä muokatessa sekä 32-bittisellä XP:llä että toisella koneella 64-bittisellä W7:lla, ja FF:ään vaihto ei nappaa ellei sitten muutenkin ole kahvitauon paikka)
Pitää ihan omastakin puolesta kiittää näistä neuvoista, joita täällä on tullut. Olen tässä sivullisena lueskellut neuvoja ja vaikka iso osa on ollut vanhaa tuttua asiaa, niin olen tässä oppinut myös kaikenlaista uutta ja päässyt joihinkin ennestään tietämiini asioihin paremmin sisälle, että miksi asia on niin kuin se on. Mikäli vielä tulee joitain neuvoja, niin kiitän jo etukäteen niistäkin. Kova on halu oppia lisää ja kun ohjeita löytyy suomeksi, niin ei ihan niin paljoa tarvitse tutkia noita enklannin kielisiä sivuja. Sellainen kun vie tällä minun kielipäälläni niin rutosti aikaa. :) Kiitos.
Kannattaa silti niitä englanninkielisiäkin sivuja vilkuilla ohessa. Eihän se kielipää kehity jos sitä ei käytä ja jos alkaa joskus tekemään vakavamielisemmin ohjelmointihommia, niin ei voi välttämättä jokaista asiaa kysyä täältä ja odottaa vastauksia :D
Juu, totta. Kyllä minulla tulee niitä vieraskielisiäkin sivuja suht' paljon lueskeltua, mutta jos saman löytää helposti valmiiksi suomenkielellä niin miksi vaivautua. :) En toki tule ongelmiani ensimmäisenä tänne kyselemään vaan etsiskelen aina ensin niitä jo valmiita vastauksia (mm. täältä, PHP:n manuaalista, googlaamalla...). Minulla onkin yleensä tapana vältellä loppuun asti kysymästä ongelmaani täällä. Aina ensin etsin valmista vastausta ja sitten kun olen viimeiseen asti tuskastunut ettei vastausta löydy, niin alistun kysymään sitä täällä. :D
Tämän viestiketjun vastauksia en erityisemmin ole etsinyt tai osannut tarvitakaan, mutta kun tämä viestiketju vastauksineen nyt hyppäsi silmille, niin otan ne kiitollisena vastaan. :)
Heh, en koe alistumisena kysyä täältä ;)
Moni asia on tullut käytyä ennestään läpi englanninkielisistä manuaaleista vaikka huono kielipää itsellänikin on. Niitä silti on pakko lukea, koska siinä sitä oppii niin englantia kuin ohjelmointiakin.
Parasta putkassa kysymisessä onkin se, että siellä saa monia mielipiteitä, vaihtoehtoja sekä asiantuntevaa opastusta. Vaikka luulisi tietävänsä kaiken, niin joku tietää aina enemmän. Siksi tykkään kysyä, että varmasti huomioin kaiken. Periaatteessahan se ei itseä haittaa jos muille tekee koodia, jos se on reikäistä, mutta mielellään tekee muille hyvää jälkeä.
Olen yllättynyt ja kiitollinen siitä, miten tämä ketju on herättänyt keskustelua ja luulen täältä monen saavankin apua ongelmiinsa. Ainakin on herättänyt uusia vaihtoehtoja :)
Kiitos siitä kaikille ja varmaan keksin vielä lisää kysyttävääkin :)
Tuleeko jotain muuta kellekkään mieleen mitä on syytä ottaa huomioon kirjautumisen varmistamisessa?
public function KayttajanSyote(){ if(isset($this->Kayttaja, $this->Salasana)){ $salaus = 'i78djeu'.$this->Salasana.'kasj92a'; $kaanteinen = strrev($salaus); $merkkijono = strlen($kaanteinen); for($i=0; $i<$merkkijono; $i++){ $koodattusalasana .= $kaanteinen[$i].$salaus[2].$salaus[3].$salaus[$i]; } $salasana = sha1($koodattusalasana); $istuntotunnus = sha1(uniqid(rand(), true)); $k = 'UPDATE kayttajat SET istunto = "'.$istuntotunnus.'" WHERE kayttaja = "'.$this->Kayttaja.'" AND salasana = "'.$salasana.'"'; if(!($t = mysql_query($k))) return; if(mysql_affected_rows() == 1){ return $istuntotunnus.'23kif74sd382'; } } else{ echo '<p style="text-align:center; color:red; font-weight:bold;">Puutteelliset kirjautumistiedot</p>'; } }
urldecode:sta kyselisin vielä sen verran, että laittaessani seuraavan esimerkin mukaan tiedon menemään seuraavalle sivulle
<form name="tiedotteet" method="post" action="../inc/lomakkeen_kasittelija.inc.php?t=<?php echo urlencode('TallennetaanTiedot') ?>">
Arvo tietenkin haetaan $_GET muuttujasta. Huonolla englannin kielen taidolla, päättelen seuraavasta pätkästä kuitenkin, että kyseistä arvoa ei seuraavalla sivulle tarvitse decoodata, koska $_GET muuttujan tiedon on jo valmiiksi dekoodattu. Olenko ymmärtänyt asian oikein?
The superglobals $_GET and $_REQUEST are already decoded. Using urldecode() on an element in $_GET or $_REQUEST could have unexpected and dangerous results.
vai tuleeko minun käyttää urldecode:a?
No niinhän siinä selvällä englannin kielellä sanotaan.
Sanotaan, että käytetään urlencodea vai, että ei käytetä mitään ;)
Ei käytetä, koska se data, mitä noista muuttujista saat, on jo valmiiks dekoodattu.
Salauksesta taas olen sitä mieltä, että kaikki strrevit ja omat keinotekoiset "sotkijat" hittoon sieltä.
Jatkon kannalta "salaustoiminnot" omaksi funktioksi, jonka kerralla ajamalla pistät sha1:n joukkoon "suolan". Kaikki strevvit yms. ovat tässä vaiheessa oikeastaan turhia. Suolassa olisi hyvä olla muitakin kuin pieniä kirjaimia ja numeroita.
Miksi strrevit ja omat keinotekoiset sotkijat ovat turhia?
Mietitään esim tilanne, että käyttäjä antaa oman nimensä salanaksi.
Nimi on esim Timo, sotkemalla nimen sekä pidentämällä sitä on purkajan käytävä läpi enemmän merkkejä kuin tuossa Timo nimessä yksistään on.
Purkaja voi tehdä itsekin ne samat strrevit sun muut.
Voi tehdä, se ei ole ongelma, mutta.
1. Yksikin henkilö, joka ei jaksa sitä tehdä on vähemmän.
2. Tarvitsee kuitenkin hieman mielikuvitusta miten purkamine on tehty
Eihän tuolla ajatuksella, että joku voi tehdä jonkin kannattaisi tehdä minkäänlaista suojausta, kyllä joku sen osaa purkaa, koska joku sen osaa tehdäkkiin.
Riittäisi suositus, että elä tule sivuille se on minulle tietoruvariski.
Jaa... Itsellä ei kyllä tulisi mieleenikään alkaa purkaa salasanatiivisteitä (koska tiivistettä ei voi järkevästi kääntää takaisin "alkuperäiseen muotoon").
Lähinnä tuo kaikki omatekemä "suojaus" vain sotkee koodaajaa itseään. Jos hakkeri kirjoittaa salasanaksi "Timo", niin ei se hyödytä oliko salasana ennen hashaamista käännetty takaperin vai ei. Suolaaminen itsessään tekee salasanan "murtamisesta" jo tarpeeksi mahdottoman, eri asia on se, kuinka vahva itse salasana on.
Jos hakkeri kirjoittaa salasanaksi "Timo", tottakai se pääsee murtautumaan sisään, salasanahan on tiedossa.
Mutta jos hakkeri saa käsiinsä salasanatiivisteetn on, Timo helpompi selvittää kuin sotkettu salasana.
Sinulla, minulla, eikä monella muullakaan tule mieleen purkaa salasanatiivistettä, mutta jollakin se on pakko tulla mieleen. Muuten sitäkään ei tarvitsisi parannella, eikä kehittää.
manninen kirjoitti:
Mutta jos hakkeri saa käsiinsä salasanatiivisteetn on, Timo helpompi selvittää kuin sotkettu salasana.
Alla toisessa tiivisteessä on tehty tiiviste sanasta "Timo" ja toisessa sekalaisesta merkkijonosta, jossa on jossakin paikassa sana "omiT" selvitätkö vaikka tänä iltana kumpi on kumpi?
testisivu.php kirjoitti:
e5f0c6f4d191b58497f7db5c5c9caf8
b6555968e101fc945ab80922da0d1034
Purkkaviritelmä ei ole ratkaisu sivuston tietoturvaan. Jos teet sivusi kunnolla, kukaan ei voi normaalioloissa hakea tietokannasta salasanojen tiivisteitä, ja jos taas tapahtuu jokin katastrofi (esim. palvelimen levy fyysisesti varastetaan), voidaan käytännössä olettaa, että sekä PHP-koodi että tietokanta ovat mennyttä samalla kertaa. Silloin krakkeri voi suoraan kopioida sinun hienot purkkaviritelmäsi, ei tarvitse hetkeäkään ajatella.
Teuro: Selvitin, tosin jos en olisi tiennyt, että salasana on Timo, tuskin olisin saanut asiaa selville. (Oletan myös, että teit tuon yhden jutut tahallasi. Aika julma pila. :D)
Teuro kirjoitti:
Alla toisessa tiivisteessä on tehty tiiviste sanasta "Timo" ja toisessa sekalaisesta merkkijonosta, jossa on jossakin paikassa sana "omiT" selvitätkö vaikka tänä iltana kumpi on kumpi?
Teuro,
1. ensinnäkin misään kohdin tiivistettäni ei lue "omiT", vaan se on sekoitettu hieman paremmin.
2. Toiseksi Teuro, jos opettelet lukemaan kunnolla niin huomaat, että olen kirjoittanut, että minulla ei tule missään vaiheessa mieleen yrittää itse purkaa tiivistettä. Jos osasit lukea sen kohdan, niin oletan myös, että ymmärrät mitä se tarkoittaa. Voin vielä tarkentaa: minulle ei tule mieleen purkaa, enkä osaisikaan sitä tehdä. Mutta vaikka sinäkään et sitä todennäköisesti osaa tehdä, niin luulen, että joku sen osaa.
Metabolix, kiitos jälleen kerran asiallisesta ja fiksusta vastauksesta. Parempi luottaa suoraan tiivisteeseen kuin alkaa sitä muokkailemaan.
Ps. Yllätyin todella kovasti Teuro, kun katsoin ikäsi profiilsitasi. No, ei se ikä välttämättä henkistä ikää kerro.
manninen kirjoitti:
Teuro,
1. ensinnäkin misään kohdin tiivistettäni ei lue "omiT", vaan se on sekoitettu hieman paremmin.
No en minäkään väittänyt, että siellä sinun salauksessasi lukisi nimi väärinpäin.
manninen kirjoitti:
2. Toiseksi Teuro, jos opettelet lukemaan kunnolla niin huomaat, että olen kirjoittanut, että minulla ei tule missään vaiheessa mieleen yrittää itse purkaa tiivistettä. Jos osasit lukea sen kohdan, niin oletan myös, että ymmärrät mitä se tarkoittaa. Voin vielä tarkentaa: minulle ei tule mieleen purkaa, enkä osaisikaan sitä tehdä. Mutta vaikka sinäkään et sitä todennäköisesti osaa tehdä, niin luulen, että joku sen osaa.
No en minäkään ole kyllä sellaista väittänyt. Tarkoitus oli lähinnä osoittaa, että molemmat tiivisteet ovat kohtuullisen hankalia lähteä arvaamaan.
manninen kirjoitti:
Ps. Yllätyin todella kovasti Teuro, kun katsoin ikäsi profiilsitasi. No, ei se ikä välttämättä henkistä ikää kerro.
Kasva isoksi älä aikuiseksi on ollut kantava teema tässä elämässä jo pitkään. :)
manninen kirjoitti:
Kasva isoksi älä aikuiseksi on ollut kantava teema tässä elämässä jo pitkään. :)
:)
Juu, uskon kyllä, että hash1 on hyvä ja turvallinen salaus eikä tarvitse muuta. Mutta tämä on mielestäni järkevä keskustelun, aihe, siis tietoturva. Siitä on hyvä lähteä rakentamaan sitten muuta. Mitä enemmän joutuu koodaamaan maailmalle, sitä mieluummin tekee hyvää jälkeä.
manninen kirjoitti:
Minulle ei tule mieleen purkaa [tiivistettä], enkä osaisikaan sitä tehdä. Mutta vaikka sinäkään [Teuro] et sitä todennäköisesti osaa tehdä, niin luulen, että joku sen osaa.
Sitten olisi korkea aika ottaa selvää, mikä tiivisteiden idea on. Lyhyesti: ideana on tehdä mistä tahansa tiedosta vakiomittainen juttu, jota ei voi purkaa. Tämä lienee helppo tajutakin: erilaisia merkkijonoja on äärettömästi mutta tiivisteitä vain rajallinen määrä.
Tiivisteitä täytyy siis "purkaa" kokeilemalla kaikkia salasanoja ja katsomalla, mistä tulee sama tiiviste. Joistakin algoritmeista on löytynyt heikkouksia, joiden ansiosta mahdollinen törmäys (ei kuitenkaan välttämättä alkuperäinen salasana vaan muu sattumalta sopiva teksti) löytyy jo aiemmin. Monet heikkoudet jäävät kuitenkin teoreettiselle tasolle.
Kerrataan vielä se kirjautumisen yksinkertainen mutta järkevä toteutuskin: Kun käyttäjä valitsee itselleen salasanan, laita tietokantaan satunnainen suola (tavallinen uniqid lisäentropialla sopii hyvin) sekä salasanan ja suolan yhdistelmästä laskettu tiiviste. Jos käyttäjän ei tarvitse pysyä kirjautuneena monella koneella samaan aikaan, voit vaihtaa suolan (ja tiivisteen) jokaisella kirjautumiskerralla. Käyttäjän henkilökohtainen suola on tärkeä! Jos käyttäjillä on sama suola, krakkeri voi murtaa kerralla koko joukon salasanoja. Jos suolaa ei ole ollenkaan, suuri osa tiivisteistä ja vastaavista salasanoista löytyy valmiista tietokannoista.
Pitkään piti hakea, mutta tällä suojattua salasanaa ei saa murrettua. Tosin jos tuo pettää voi kokeilla tätä
manninen kirjoitti:
Miksi strrevit ja omat keinotekoiset sotkijat ovat turhia?
Ne eivät monimutkaista hashin purkamista, siksi. Sitä voi kutsua Occam's razoriksi tai Keep It Simple, Stupid -periaatteeksi, miten tykkää.
Kaikkien saman joukon satunnaisten merkkijonojen arvaaminen on yhtä vaikeaa, joten sillä ei ole väliä, suorittaako syötteeseen sekoitusfunktion yhden vai sata kertaa. Jo suolaus sotkee salasanan plaintext-tasolla, joten mikään muu sekoitus ei enää voi monimutkaistaa sen arvaamista.
Ts. saat satunnaisen luvun yhtä todennäköisesti kutsumalla funktiota randomNumber() yhden kerran, kuin jos kutsuisit sitä ensin kymmenen kertaa ja vasta sen jälkeen ottaisit tuloksen talteen.
Perinteinen tapa suolaamiseen on tuo suolan katenointi salasanan eteen tai perään, mutta voi sen tehdä toisinkin. Demomielessä pyöräytin pienen vaihtoehtoisen menetelmän, joka tekee ascii-kelpoisia suoloja ja suolattuja merkkijonoja.
The Alchemist, onko tästä vaihtoehtoisesta menetelmästä jotain hyötyä?
Sen verran suolan luonnista, että lyhyesti sanottuna kannattaa, varsinkin jos mahdollista, luoda suola käyttäen vahvempia satunnaislukuja. openssl_random_pseudo_bytes() (PHP 5.3.4+), mcrypt_create_iv() MCRYPT_DEV_URANDOM:lla (PHP 5.3+) ja /dev/urandom ovat tällaisia vaihtoehtoja.
Se että suola on mahdotonta ennalta arvata/laskea, auttaa mm. siinä, että hyökkääjä ei pääse suoraan murtamaan pelkkää salasanaa (lokaalisti hyökkää tiivistettä vastaan), mikäli hän onnistuu selvittämään salasanatiivisteen järjestelmästä (timing attack). Vaan hän joutuu selvittämään salasanatiivistettä mikä sisältää suolan mitä hyökkääjä ei tiedä. Myös tarpeellinen määrä bittejä suolassa täytyy olla.
Se että onnistuuko hyökkääjän saada selvitettyä käytettyä suolaa esim. unigid():n tai mt_rand():n jäljiltä ei varmasti ole yksiselitteistä eikä helppoa, mutta periaatteellinen mahdollisuus siihen on. Ja se että onnituuko hyökääjä hyödyntämään tätä "selvitettyä suolaa", riippuu tosiaan mm. siitä miten salasanan tarkistus palvelimella on toteutettu ("==" -vertailu).
Kuitenkin kunnollisen satunnaisdatan käyttö auttaa ehkäisemään tällaisia ongelmia. Joten ei ole luultavasti mitään syytä olla käyttämättä "kunnollista satunnaisdataa", ainakaan jos sellaiseen on mahdollisuus. Myös suolan kanssa. Vaikka suola ei vahvan satunnaisdatan ensisijainen käyttökohde olekaan.
Metabolix kirjoitti:
The Alchemist, onko tästä vaihtoehtoisesta menetelmästä jotain hyötyä?
Eipä varmaan yhtään mitään. Sen verran ehkä, että salasanan voi padata tiettyyn pituuteen paljastamatta salasanan pituutta esimerkiksi suolan pituuden kautta. Tiivistefunktiot ovat tehokkaampia optimaalisen pituisilla syötteillä. Ne kuitenkin toimivat niin, että yhdenkin syötemerkin muuttaminen tai lisääminen muuttaa koko tiivistettä (alusta, lopusta ja keskeltä), joten sinällään ei ole väliä, mihin kohtaan suolaa on sovellettu.
SHA-1:kin on edelleen niin vahva, että tiivisten purkaminen brute forcella on utopiaa, kunhan jonkinlainen suolaus on toteutettu.
Tuo kyseinen algoritmi on oikeastaan ihan hirveä, koska melko monen merkkiparin tulon jakojäännöksellä on useitakin yhteentörmäyksiä, joten saman suolatun version tuottaa monikin eri salasana. Parannus voisi olla se, että suolalla kerrotaankin yhden syötemerkin sijaan vaikkapa neljän merkin tulo. Mutta turhaa hifistelyä.
Eli kaikessa yksinkertaisuudessa, rautalangan avulla näin.
1. Luon käyttäjän
* Tallennan kantaan käyttäjätunnuksen
* salasanan
* suolan (uniikki id, tunnus, jotain) ja teen siitä tiivisteen
2. Kirjaudun sisään
* Tarkistan käyttäjätunnuksen
* tarkistan salasanan
* tarkistan suolan
* luon uuden suolan seuraavaa kertaa varten
Kannattaako tuolle suolalle nyt sitten ola oma kenttänsä joka tarkistetaan samalla, hieman salasanasta eroava. Tuota en oikein ymmärrä, että jos joka kerta luotaisiin uusi suola miten se kannattaisi tarkistaa ja toteuttaa. Mistä sen suolan bongaa kirjautumista tehdessä. Pitääkö se hakea etukäteen.
Herää sellainen mielenkiintoinen kysymys, että miten te teette kirjautumisen tarkistuksen.
*Miten teette tuon suolan vaihtumisen sekä koko suolan. Jos vain hieman viitsitten valaista saloistanne, olisin kiitollinen. Koko suola sillai itselle uutta, kun vain sotkenut aina tuon salasanan.
Miten suola eroaa edes tästä,
$salaus = 'i78djeu'.$this->Salasana.'kasj92a';
Paitsi, että tässä se on kiinteä ja kaikille sama?
Vai olenko käsittänyt koko suolan väärin?
Suola tallennetaan tietokantaan, eihän sitä muuten voi tarkistaa. Suolan vaihtaminen kirjautumisen yhteydessä tapahtuu aivan kuten sen luominenkin alun perin rekisteröitymisen yhteydessä. Ainoa "ongelma" on se, että koska tietokannassa on vain vanhan suolan avulla muodostettu tiiviste salasanasta, niin miten sen uuden tiivisteen voi muodostaa? No tietysti kirjautumisen yhteydessä syötetyllä salasanalla. Kirjautumisen yhteydessä siis luot uuden suolan ja tiivisteen, ja korvaat vanhat kannassa olevat niillä.
En kyllä itse jaksaisi noin pikkutarkasti lähteä säätämään, mutta toisaalta tuo ei edes vaadi montaa riviä koodia. Suolan voi vaikka generoida esimerkkini mukaisella funktiolla.
Mikä ihme siinä nyt on niin vaikeaa? Älä ikinä tallenna tietokantaan salasanaa. Luuletko, että kukaan krakkeri olisi niin tyhmä, että alkaisi purkaa suolattua tiivistettä, jos vieressä on valmis salasana?
uniqid("",true)
) ja tiiviste (esim. sha1($salasana.$suola)
). Tallenna tietokantaan tunnus, suola ja tiiviste.Suolan ei tarvitse olla ihmeellinen. Sitä ei tarvitse sekoittaa salasanan sekaan mitenkään ihmeellisesti. Suolan tarkoitus on estää valmiiden tiivistetietokantojen käyttö ja pakottaa krakkeri tutkimaan salasanat uudestaan joka käyttäjän kohdalla. Yleensä käyttäjien salasanat sisältävät aakkosia ja numeroita ja ovat pituudeltaan kymmenkunta merkkiä, ja kaikki tällaiset vaihtoehdot voi tehokkaalla koneella käydä läpi kohtuullisessa ajassa. Jos miljoonalle käyttäjälle on käytetty samaa suolaa, yhdellä läpikäynnillä saadaan miljoona salasanaa. Jos suola on jokaiselle eri, yhdellä läpikäynnillä saadaan vain yksi salasana.
Laitan vielä yhden linkin missä käsitellään asiaa melko perusteellisesti: http://www.openwall.com/articles/PHP-Users-Passwords
Tuo kannattaa ainakin, jokaisen joka webbijärjestelmiä toteuttaa, lukea ajatuksella läpi. Se on riittävä tietolähde "kirjautumisjärjestelmän" toteutuksen käytännöstä ja teoriasta. Jos mielenkiintoa vielä tästä syvemmälle on, tuon artikkelin pohjatiedoilla on hyvä sinne lähteä :) (Itse saatoin aiemmassa viestissäni mennä turhan syvälle yksityiskohtiin).
Salasanojen tallennus on osattu tehdä "oikein" jo about 20 vuotta sitten, hieman kumma että nykypäivänäkin tätä tietämystä ei (ainakaan kokonaisuudessaan) ole käytetty hyväksi. Esim. Drupal ja Wordpress käyttivät aika pitkään huonompaa menetelmää omissa "salasanahashauksissaan".
Pahoittelen jos kuullostan liian "paasaavalta" :)
Ei, en missään nimessä tallenna salasanaa sellaisenaan eikä lukemisessa mitääm vikaa ole. Vika taitaa olla lukemisen ymmärtämisessä, mutta nyt ymmärsin :)
lainaus:
Suolan ei tarvitse olla ihmeellinen. Sitä ei tarvitse sekoittaa salasanan sekaan mitenkään ihmeellisesti. Suolan tarkoitus on estää valmiiden tiivistetietokantojen käyttö ja pakottaa krakkeri tutkimaan salasanat uudestaan joka käyttäjän kohdalla.
Ja tämä kommentti selventääkin kaiken. Olen käsittänyt suolan enemmän, salasanan sotemiseksi. En tuplasalauksen tekemiseksi.
Osapuilleen näin?
public function KayttajanSyote(){ if(isset($this->Kayttaja, $this->Salasana) && !(isset($this->Tarkiste))){ $salasana = sha1($this->Salasana); $istuntotunnus = sha1(uniqid(rand(), true)); $k = "SELECT id, suola FROM kayttajat WHERE kayttaja = '$this->Kayttaja'"; if(!($t = mysql_query($k))) { echo mysql_error(); return; } if(mysql_affected_rows() == 1){ $r = mysql_fetch_assoc($t); $henkilonid = intval($r['id']); $noudettuSuola = htmlspecialchars($r['suola']); $suola = sha1($henkilonid.$this->Kayttaja); $tiiviste = $salasana.$suola; if($noudettuSuola == $suola){ $k = 'UPDATE kayttajat SET istunto = "'.$istuntotunnus.'" WHERE kayttaja = "'.$this->Kayttaja.'" AND tiiviste = "'.$tiiviste.'" AND suola="'.$suola.'"'; if(!($t = mysql_query($k))) return; if(mysql_affected_rows() == 1){ return $istuntotunnus; } } else{ return; } } } }
Lähes, mutta ei aivan.
Metabolix kirjoitti:
... sekä salasanan ja suolan yhdistelmästä laskettu tiiviste. ...
Esimerkiksi seuraavalla tavalla, jos käyttäjän ei ole tarvetta kirjautua useammasta paikasta yhtäaikaa.
$k = "UPDATE kayttajat " . "JOIN (SELECT suola AS tempsuola FROM kayttajat WHERE kayttaja = '" . $kayttaja . "') AS temptaulu " . "SET istuntotunnus = '" . $istuntotunnus . "', suola = '" . $uusisuola . "', tiiviste = sha1('" . $salasana . $uusisuola . "') " . "WHERE kayttaja = '". $kayttaja . "' AND tiiviste = sha1(CONCAT('" . $salasana . "', temptaulu.tempsuola))";
Kysely etsii annetun käyttäjän, vertaa salasanasta ja suolasta muodostettua tiivistettä jonka jälkeen onnistuessaan päivittää suolan sekä istuntotunnuksen. Tiiviste on siis muotoa
$tiiviste = sha1($salasana . $suola);
Millä tavalla luot esimerkissäsi suolan kun luot käyttäjän järjestelmään?
manninen kirjoitti:
Millä tavalla luot esimerkissäsi suolan kun luot käyttäjän järjestelmään?
Metabolix kirjoitti:
... satunnainen suola (tavallinen uniqid lisäentropialla sopii hyvin) ...
Eli esimerkiksi seuraavalla tavalla
$uusisuola = uniqid('', true);
Hei!
Kiitoksia, monen epäonnistumisen kautta olen vihdoin tajunnut tuon logiikan.
Ilmeisesti nyt kun aikaisemmin olen tarkistanut, että onko istuntotunnus asetettu, minun tulisi tarkitaa suola joka sivulla ja jos se on oikein sivulla pysytään, muussa tapauksessa lentää pihalle.
Muutenhan tuonkin esimerkin mukaan pystyy olemaan samalla tunnuksella kahdella eri koneella sisässä.
Ei vaan juurikin istuntotunnus. Suolaa käytetään vain salasanan suojaamiseen. Jos sivulle kirjautujan ei tarvitse pysyä kirjautuneena kuin yhdessä paikassa kerrallaan päivität istuntotunnuksen joka kirjautumiskerralla.
Eli käytännössä ennen kirjautumista haen vielä kannasta sessionin, joka sinne on tallennettu ja vertaan niitä. jos je ei täsmää, pistän käyttäjän pihalle.
Juurikin näin. Kannasta haet istuntotunnuksen ja vertaat sitä käyttäjälle asetettuun evästeeseen.
Nyt kyllä sekoaa minulla ja kunnolla.
Tallennan kyselyssä tietokantaan istuntotunnuksen ja se menee oikein.
Sen jälkeen jos kysely on mennyt oikein palautan istuntotunnuksen arvon.
public function Syote{ diipa daapaa... kunnes if(mysql_affected_rows() == 1){ // Tarkistetusti arvo on täällä vielä oikein return $istuntotunnus; } }
Arvon taas haen muuttujaan
// Täällä se on taas väärin $this->Istuntotunnus = $this->Syote();
Kun tulostana tuon arvon, ei istuntotunnus ole enää sama. Mikä sen voi sotkea tuossa välissä.
Näytähän ensin sitä koodia, niin ehkä joku katsoo.
Tuossa sun melko hyödyttömässä snippetissä on ainakin kaksi virhettä:
1. Yrität käyttää sellaisen funktion paluuarvoa, joka ei palauta yhtään mitään.
2. Mysql_affected_rows() ei kerro, montako riviä SELECT-kyselyllä löytyi.
Koodia näkyy ylempänä, mutta siinä uudestaan ;)
https://www.php.net/manual/en/function.mysql-affected-rows.php
public function __construct($salasana, $kayttaja, $tarkiste) { $this->Salasana = empty($salasana) ? NULL : $salasana; $this->Kayttaja = empty($kayttaja) ? NULL : $kayttaja; $this->Tarkiste = empty($tarkiste) ? NULL : $tarkiste; $this->Istuntotunnus = $this->KayttajanSyote(); } public function KayttajanSyote(){ if(isset($this->Kayttaja, $this->Salasana) && !(isset($this->Tarkiste))){ $uusisuola = sha1(uniqid('', true)); $istuntotunnus = sha1(uniqid(rand(), true)); $k = "UPDATE kayttajat " . "JOIN (SELECT suola AS tempsuola FROM kayttajat WHERE kayttaja = '" . $this->Kayttaja . "') AS temptaulu " . "SET istuntotunnus = '" . $istuntotunnus . "', suola = '" . $uusisuola . "', tiiviste = sha1('" . $this->Salasana . $uusisuola . "') " . "WHERE kayttaja = '". $this->Kayttaja . "' AND tiiviste = sha1(CONCAT('" . $this->Salasana . "', temptaulu.tempsuola))"; if(!($t = mysql_query($k))) { echo mysql_error(); return; } if(mysql_affected_rows() == 1){ return $istuntotunnus; } else{ return; } } } public function KirjautumisenVarmistaminen(){ if(isset($this->Istuntotunnus)){ $_SESSION['istunnon_avain'] = $this->Istuntotunnus; $k = "SELECT id, henkilo, istuntotunnus FROM kayttajat WHERE kayttaja = '".mysql_real_escape_string($this->Kayttaja)."' LIMIT 1"; if(!$t = mysql_query($k)){ unset($_SESSION['istunnon_avain']); return; } $r = mysql_fetch_assoc($t); header("location:etusivu.php"); exit; } }
ihan offtopikkina, onkos muuten tiivisteen luominen järkevää mysql-kyselyssä, vai olisiko se "turvallisempaa" etukäteen php:lla?
Lebe80 kirjoitti:
ihan offtopikkina, onkos muuten tiivisteen luominen järkevää mysql-kyselyssä, vai olisiko se "turvallisempaa" etukäteen php:lla?
Tietysti se on teoriassa turvallisempaa. Mutta päivänselvää on, että se olisi koodin kannalta selkeämpi ratkaisu. Kannattaisi myös opetella käyttämään PDO:ta tai vaikka sprintf-funktiota. Nykyinen kysely sallii SQL-injektiot aivan mukisematta, huonompi homma.
Suolana käyttäisin ihan uniqid():n palauttamaa arvoa, en sen tiivistettä. Tiivisteen tiivistäminen on vähän arveluttavaa aina.
Heh, menipäs oftopiciksi, mutta selvisikö koodista kenellekkään kertomani ongelma :)
Ongelma ratkeaa jos jätän tämän vaiheen välistä pois
$this->Istuntotunnus = $this->KayttajanSyote();
Mutta olisi mukava tietää, mikä sen tässä välissä onnistuu sotkemaan.
Ei sitä return-kutsun jälkeen enää mikään sotke.
Arvo joka tulee muuttujaan istuntotunnus
8ae406b8574211bf786201d35a89ea3e023753cb
Arvo joka tulee $this->Istuntotunnus:seen
cc648391c053931904f529870ecd02d67ae8047e
Arvo joka löytyy kannasta
8ae406b8574211bf786201d35a89ea3e023753cb
Joku ne sotkee erillaiseksi, mutta mikä?
echo:t ei valehtele ;)
The Alchemist kirjoitti:
Lebe80 kirjoitti:
ihan offtopikkina, onkos muuten tiivisteen luominen järkevää mysql-kyselyssä, vai olisiko se "turvallisempaa" etukäteen php:lla?
Tietysti se on teoriassa turvallisempaa. Mutta päivänselvää on, että se olisi koodin kannalta selkeämpi ratkaisu.
Mikä tekee sen PHP:n puolella turvallisemmaksi? Kuinka itse toteuttaisit sitten kyseisen kyselyn?
manninen kirjoitti:
Kun tulostana tuon arvon, ei istuntotunnus ole enää sama. Mikä sen voi sotkea tuossa välissä.
Sotket sen väkisin itse jossain kohtaa. Kuinka käytät kyseistä luokkaa? Kutsutko mahdollisesti KayttajanSyote()-metodia useammassa kohtaa?
class Testi { public $suola; public function __construct() { $this->suola = $this->suolaus(); } public function suolaus() { $suola = uniqid('', true); echo $suola . '<br />'; return $suola; } } $testi = new Testi(); echo $testi->suola; // Tulostaa esim. // 4dcbc380a01ba0.47032759 // 4dcbc380a01ba0.47032759
Ainoastaan näin
$kirjautuminen = new Kirjautuminen($salasana, $kayttaja, $tarkiste); <td class="border" style="width:80%;"> Käyttäjätunnus : <input type="text" name="kayttaja" value="" /> Salasana : <input type="text" name="salasana" value="" /> <input type="submit" value="Kirjaudu sisään"> <?php $kirjautuminen->KayttajanSyote(); $kirjautuminen->KirjautumisenVarmistaminen(); ?> </td>
Tuleehan sitä koodia viimeinkin muutaman semiturhan postauksen jälkeen. Virhe on siinä, että kutsut KayttajanSyote-funktiota kahdesti, mutta otat vain ensimmäisellä kerralla tuloksen talteen luokkamuuttujaa, ja käytät kyseistä tulosta funktiossa KirjautumisenVarmistaminen.
Kannattaisi miettiä luokan rakenne ihan uusiksi, kun tuo on aika hirveä möhkäle nykyisellään.
Harkitsematon esimerkki:
class Kirjautuminen { private $salasana, $kayttaja, $istuntotunnus, $kirjautunut; private $db; public function __construct($kayttaja, $salasana) { $this->kayttaja = empty($kayttaja) ? NULL : $kayttaja; $this->salasana = $salasana; $this->kirjautunut = false; $this->db = new PDO(...); } public function kirjaudu() { $uusisuola = uniqid('', true); $istuntotunnus = sha1(uniqid('', true)); $sql = '...'; $q = $db->prepare($sql); if ($q->execute(...) && $q->rowCount() == 1) { $this->kirjautunut = true; $this->istuntotunnus = $istuntotunnus; } } public function onKirjautunut() { return $this->kirjautunut(); } } $kirjautuminen = new Kirjautuminen(...); $kirjautuminen->kirjaudu(); if ($kirjautuminen->onKirjautunut()) { ... }
Othnos kirjoitti:
The Alchemist kirjoitti:
Lebe80 kirjoitti:
ihan offtopikkina, onkos muuten tiivisteen luominen järkevää mysql-kyselyssä, vai olisiko se "turvallisempaa" etukäteen php:lla?
Tietysti se on teoriassa turvallisempaa. Mutta päivänselvää on, että se olisi koodin kannalta selkeämpi ratkaisu.
Mikä tekee sen PHP:n puolella turvallisemmaksi? Kuinka itse toteuttaisit sitten kyseisen kyselyn?
Se on TEORIASSA turvallisempaa siksi, että välikäsiä ei ole.
$uusitiiviste = sha1($this->salana . $uusisuola); $vanhatiiviste = sha1($this->salasana . $vanhasuola); $sql = 'UPDATE KAYTTAJAT SET istuntotunnus=?, suola=?, tiiviste=? WHERE kayttaja=? AND tiiviste=?'; $query = $pdoObject->prepare($sql); $query->execute(array($istuntotunnus, $uusisuola, $uusitiiviste, $this->Kayttaja, $vanhatiiviste));
Kiitoksia neuvoista!
Ensi kerralla koitan ottaa enemmän neuvosta vaarin ja jättää "hirveän" möhkäleen rakentamisen sikseen.
Näillä neuvoilla sain luokan toimimaan ja opin taas uutta luokkarakenteesta. Toimivaa, ei kehtaa nyt rueta uudestaan rakentamaan. Luultavasti sitä joutuu vielä rakentelemaan uudestaan eri tilanteessa kirjautumista, joten jätän sen "kevyemmän" rakentamisen seuraavaan kertaan ;)
Lebe80 kirjoitti:
ihan offtopikkina, onkos muuten tiivisteen luominen järkevää mysql-kyselyssä, vai olisiko se "turvallisempaa" etukäteen php:lla?
Mannisen koodissa on luultavasti sql-injektio (riippuen miten käsitellään $this->Kayttaja ja $this->Salasana. Annetun koodin perusteella niitä ei ole escapoitu).
The Alchemist kirjoitti:
Se on TEORIASSA turvallisempaa siksi, että välikäsiä ei ole.
Tätä hieman ihmettelin että mitä tarkoitit. Jos muodostat tiivisteen vasta SQL:n puolella, se tiivistettävä merkkijono täytyy saada sql-palvelimelle turvallisesti. Joko viemällä kyselylle valmiin tiivisteen (olettaen että siinä ei ole muita merkkejä kuin a-z0-9). Tai käyttämällä escape-functiota. Tai prepare+bind parameter. The Alchemist oli esimerkissään hoitanut homman PDO:lla, mutta sanon tämän vielä varmuuden vuoksi manniselle.
timoh kirjoitti:
The Alchemist kirjoitti:
Se on TEORIASSA turvallisempaa siksi, että välikäsiä ei ole.
Tätä hieman ihmettelin että mitä tarkoitit.
Jos joku pääsisi haistelemaan tai logittamaan SQL-palvelimen suorittamat kyselyt, niin sieltä logeista näkyisi se salasana selväkielisenä, mikäli tiiviste lasketaan tietokonannan päässä. Eli siinä on yksi paikka enemmän, missä selväkielinen salasana liikkuu.
Grez kirjoitti:
Jos joku pääsisi haistelemaan tai logittamaan SQL-palvelimen suorittamat kyselyt, niin sieltä logeista näkyisi se salasana selväkielisenä, mikäli tiiviste lasketaan tietokonannan päässä. Eli siinä on yksi paikka enemmän, missä selväkielinen salasana liikkuu.
Totta tosiaan, näin on. Salasana on muutenkin sellainen elementti että sen käsittely vaatii enemmän "logiikkaa", joten senkään takia sitä tehtävää ei voi kannalle antaa.
Siis tuo koko alkuperäinen kyselyhän vaikuttaa olevan altis injektioille. Käyttäjänimeä tai mitään muutakaan ei ole eskapoitu asianmukaisesti. Vaikka jossain vaiheessa rekisteröityminen ei sallisikaan injektiot mahdollistavia merkkejä, ei siihen voi luottaa sokeasti kaiken muun toteutuksessa. Hyvän koodin olisi pitäisi olla jossain määrin copy-pastettavissa (uudelleen käyttäminen)...
Teräviä huomioita, enpä itse huomannutkaan, että mysql_real_escape_string on jäänyt kokonaan pois, tiukka moka.
Ja äkkiä nuo nakkaa pois tuolta kyselyn seasta nuo tunnukset ja muut ongelmakohdat, ettei väliin pääse kuikkimaan.
The Alchemist on hoitanut homman hienosti PDO:ta käyttäen, mutta vielä niin uusi asia minulle, että ihan kaikkea en siitä osaa hyödyttää.
Jos väännätte rautalangasta miten tuon saa kasaan PDO:ta käyttäen, tai annatte muuten vain käytännön vinkkejä lisää, olen super tyytyväinen :)
Kiitoksia tähänastisista vinkeistä.
public function __construct($salasana, $kayttaja, $tarkiste) { $this->Salasana = empty($salasana) ? NULL : mysql_real_escape_string($salasana); $this->Kayttaja = empty($kayttaja) ? NULL : mysql_real_escape_string($kayttaja); $this->Tarkiste = empty($tarkiste) ? NULL : $tarkiste; $this->uusiSuola = sha1(uniqid('', true)); $this->Istunto = sha1(uniqid(rand(), true)); $this->Tiiviste = sha1($this->Salasana.$this->uusiSuola); } public function Kirjaudu(){ if(isset($this->Kayttaja, $this->Salasana) && !(isset($this->Tarkiste))){ $k = "UPDATE kayttajat " . "JOIN (SELECT suola AS tempsuola FROM kayttajat WHERE kayttaja = '" . $this->Kayttaja . "') AS temptaulu " . "SET istuntotunnus = '" . $this->Istunto . "', suola = '" . $this->uusiSuola . "', tiiviste = '" . $this->Tiiviste . "' " . "WHERE kayttaja = '". $this->Kayttaja . "' AND tiiviste = sha1(CONCAT('" . $this->Salasana . "', temptaulu.tempsuola))"; if(!($t = mysql_query($k))) return; if(mysql_affected_rows() == 1){ $_SESSION['istunnon_avain'] = $this->Istunto; $k = "SELECT istuntotunnus FROM kayttajat WHERE kayttaja = '".mysql_real_escape_string($this->Kayttaja)."' LIMIT 1"; if(!$t = mysql_query($k)){ unset($_SESSION['istunnon_avain']); return; } $r = mysql_fetch_assoc($t); $_SESSION['uniikkiavain'] = htmlspecialchars($r['istuntotunnus']); $_SESSION['henkilotiedot'] = htmlspecialchars(ucfirst(trim($r['henkilo']))); header("location:etusivu.php"); exit; } else{ return; } } }
No tuolla minun luokkarakenne-esimerkkini ja sen alla olevan mallikyselyn avulla varmaan pääsee jo aika pitkälle. Ainoa tyhjä aukko on se, että olen jättänyt vanhan tiivisteen noutamisen pois tiedot päivittävästä SQL-kyselystä.
Tarkemmin ilmaistuna tietokantayhteys avataan seuraavasti:
try { $db = new PDO('mysql:dbname=' . $tietokanta, $user, $password); } catch (PDOException $e) { exit('Virhe'); }
Try-catch-rakennetta kannattaa käyttää sen takia, että mikäli yhdistämisessä tapahtuu virhe, nostaa PDO poikkeuksen, ja sen virheviestissä näkyy myös yhdistämiseen käytetyt tunnukset.
Sen huomaan, että siinä on kaikki, mutta juuri nuiden kyselyrakenteiden tekeminen PDO:lla onkin hieman erillaisempaa, mutta pitää ottaa opas käteen ja katsoa miten se toteutetaan. Siinähän se on ratkaisu ongelmaan :)
Hei!
Keksin varmenteen joka on erittäin varma (php:llä):
<?php $varmiste = sha1($sala.md5($nimi)) $salasana = md5($sala.uniqid($varmiste)) $suola = sha1($nimi.md5($salasana.uniqid($varmiste))) $Vali_Tiiviste = sha1($salasana.$suola) $Aito_Tiiviste = htmlspecialchars($Vali_Tiiviste) // $sala on käyttäjän antama salasana. $nimi on käyttäjätunnus jonka käyttäjä //antoi. ?>
Mahtaa toimia???
:D
Mod. korjasi oikeat kooditagit.
ErroR++:
Noup, kaikki nuo ylimääräiset tiivisteet itseasiassa todennäköisesti vain heikentävät suojausta. Myös tiivisteen ajaminen htmlspecialcharssin läpi on aika siistiä.
Eli kokeilepas nyt vain tehdä se näin:
$salasananTiiviste = sha1($suola.$salasana);
Jossa $suola on esimerkiksi ~10 merkkiä pitkä merkkijono, joka on arvottu kirjaimista (pienistä ja ISOISTA) sekä numeroista ja erikoismerkeistä.
No tää ei toimi:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>KUPA!</title> </head> <body> <?php $sala = "Testi123"; $nimi = "Tunnus123"; $varmiste = sha1($sala.md5($nimi)); $salasana = md5($sala.uniqid($varmiste)); $suola = sha1($nimi.md5($salasana.uniqid($varmiste))); $Vali_Tiiviste = sha1($salasana.$suola); $Aito_Tiiviste = htmlspecialchars($Vali_Tiiviste); echo ("Sala: ".$sala." ja Nimi: ".$nimi." ja tiiviste: ".$Aito_Tiiviste); ?> </body> </html>
Se sivu on tyhjä.
Ja ne php:n kooditagit ei toiminu kai. :O
Mod. korjasi oikeat kooditagit.
Voisipä tässä nyt kertaista: md5 riittää, mutta sha-1 on parempi, käyttäjän syötettä ei ajeta komentoina ja admini salasanana ei käytetä omaa nimeään, eikä sitä kerrota kaikille tai jätetä näkyviin avoimiin paikkoihin. Tämän pitäisi riittää, ellei saa riesakseen ammatticrackeria, mutta harrastelijat ja botit pysähtyvät kuin seinään.
ErroR++ kirjoitti:
No tää ei toimi:
...
Se sivu on tyhjä.
Ja ne php:n kooditagit ei toiminu kai. :O
Ei vaan, sul todennäköisesti on shorttagit päällä palvelimellas ja xml-määrittees aiheuttaa siks errorin. Mut noi sun kaikki turhat sha1+md5 salaukses todennäköisesti vain heikentää noit suojauksia, koska ajat useita hasheja sisäkkäin. Tee vain niin kuin viisaammat ovat hyväksi todenneet ja salasanan hashaamiseksi riittää pelkkä sha1($suola.$salasana)
Siis tuo on aivan järjetöntä, mitä sulla nyt koodissa lukee.
salasana := md5(sha1(md5(...)))
suola := sha1(sha1(md5(sha1(md5(...)))))
Mietihän vähän. Otat tosiaan vain yhden hashin koko roskasta. Yhden.
Joojoo nyt mä älysin miks se php ei toimi. Mä en käytä palvelinta. Vaan:
Ensin luon uuden textitiedoston. Sitten kirjotan sinne koodin. Sitten laitan valikosta "Tallenna nimellä". Sitten laitan nimeksi vaikka "Testi.xhtml". Sitten laitan tiedostotyypiksi "Kaikki tiedostot". Sitten vain napautan "Tallenna". Sitten laitan sen pois. Menen vaikka Resurssienhallinnasta sinne kansiooon minne sen tallensin. Sitten tuplaklikkaan sitä ja sivu avautuu!!!
Ja mä nyt käytän pelkkää sha1(suola.salasana)
Vastaako mysql:n mysql_real_escape_string pdo:n $kysely->execute(array(3)); ?
dartvaneri kirjoitti:
Vastaako mysql:n mysql_real_escape_string pdo:n $kysely->execute(array(3)); ?
Ei oikeastaan. $kysely->execute
vastaa enemmän mysql_query:ä, mutta PDO kyllä hoitaa mysql_real_escape_string:iä vastaavan escapetuksen samalla.
jos on esimerkiksi tällainen lause:
$hash = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
niin mikä merkitys on kysymysmerkin jälkeisellä osalla?
dartvaneri kirjoitti:
niin mikä merkitys on tällä osalla: " ? $_SERVER['HTTP_USER_AGENT'] : ''; "?
Se laittaa muuttujan arvoksi kysymysmerkkiä edeltävän ehdon mukaisesti joko $_SERVER['HTTP_USER_AGENT']
tai ''
. Esimerkki selventänee asiaa:
$i = 12; echo ($i > 10) ? "Muuttujan i arvo on yli 10." : "Muuttujan i arvo on alle 10.";
Onko tämä tekemäni koodi miten pätevä tiedostopääte "sensuri" :
<?php $tiedosto = "testi.txt.php"; $path_parts = pathinfo($tiedosto); $path = $path_parts['extension']; if($path == 'php' || $path == 'js' || $path == 'html' || $path == 'java' || $path == 'php3' || $path == 'phtml' || $path == 'xhtml'){ echo "tiedosto pääte ei kelpaa."; } else { //toiminnot tähän } ?>
dartvaneri kirjoitti:
Onko tämä tekemäni koodi miten pätevä tiedostopääte "sensuri" :
On se ihan toimiva. Tiedostopääte tosin ei välttämättä kerro tiedoston sisällöstä mitään.
Funktion in_array avulla pääsisi vähemmällä kirjoittamisella.
Kannattaisi myös opetella nimeämään muuttujat järkevästi. Se helpottaa pidemmän koodin kanssa, eikä yleensäkään ole mitään perusteita olla käyttämättä järkeviä/relevantteja nimiä. Tiedoston päätteellä ei ole mitään tekemistä polun kanssa, joten $path on muuttujan nimenä huono.
joo tuota voisi kokeilla.
The Alchemist: Kyllä mulla on koodeissa ihan tarpeeksi pätevät muutujan nimet, jos koodi on pitkä, mutta en jaksa tällaseen lyhyeen esimerkki pätkään ruveta mitään järkyttävän pitkiä nimiä kirjottaan, ku ei oo ku muutama muuttuja.
$ext on itse asiassa lyhyempi ja äärettömästi selkeämpi. Selkeyttä ei mitata käytettyjen merkkien määrässä vaan "laadussa".
Tuli muuten tuossa mieleen, että eikös olisi parempi verrata sallittuihin tiedostopäätteisiin kuin kiellettyihin. Siltä varalta, että kielletyistä saattaa jäädä joitain päätteitä huomaamatta, joita ei joko tiedä tai vain hoksaa.
Toimiiko, jos on tiedosto.php.png php vai png tiedostona? ottakko se aina ton viimeisen mukaan?
dartvaneri kirjoitti:
Toimiiko, jos on tiedosto.php.png php vai png tiedostona? ottakko se aina ton viimeisen mukaan?
Pitäisi toimia .png-tiedostona. Kuitenkin, jokunen aika sitten monien linux-distribuutioiden Apache ajoi tuota muotoa olevat tiedostot tulkin läpi (oletusasetuksilla). Arch teki ainakin näin, ja kai Ubuntu tai Debian. Tuo on semivakava tietoturvariski, sillä monet upload-koodit päästävät läpi tiedosto.php.png
muotoa olevat tiedostot -> piu pau poks.
Tämä on hieno ketju. Paljon tosi hyviä miksi näin ja miksi ei näin -pointteja. Nyt jos koska olisi aiheellista putkassa olla mahdollisuus tehdä ketjusta sticky. Väitän, että kirjautumiseen liittyvän tietoturva on tärkein yksittäinen aihealue, mitä PHP-ohjelmointiin tulee ja tämä ketju olisi kiva löytää vielä myöhemminkin. Tässä on paljon asiaa, mitä en ole ennen tullut ajatelleeksi enkä opetelleeksi, eikä juuri nyt ole oikein aikaakaan.
Triskal kirjoitti:
tämä ketju olisi kiva löytää vielä myöhemminkin
Sitä varten selaimissa on kirjanmerkit.
Macro kirjoitti:
Triskal kirjoitti:
tämä ketju olisi kiva löytää vielä myöhemminkin
Sitä varten selaimissa on kirjanmerkit.
No ei sit.
Triskal kirjoitti:
Tämä on hieno ketju. Paljon tosi hyviä miksi näin ja miksi ei näin -pointteja. Nyt jos koska olisi aiheellista putkassa olla mahdollisuus tehdä ketjusta sticky. Väitän, että kirjautumiseen liittyvän tietoturva on tärkein yksittäinen aihealue, mitä PHP-ohjelmointiin tulee ja tämä ketju olisi kiva löytää vielä myöhemminkin. Tässä on paljon asiaa, mitä en ole ennen tullut ajatelleeksi enkä opetelleeksi, eikä juuri nyt ole oikein aikaakaan.
Tietoturva ei ole vain kirjautumisen osa-alue. Samat keinot toimivat joka paikkaan, ja lisäksi on muita tärkeitä pointteja, jotka eivät kirjautumissysteemien kanssa välttämättä tule esille. Luultavasti suurin osa tietomurroista tehdään ilman minkäänlaista kirjautumista. Kirjautumisen tietoturva ei siis millään muotoa ole sen tärkeämpää kuin koko muun sivuston yleinen varmuus. On myös vaarallista kuvitella, että raudanluja kirjautumisjärjestelmä tekisi itse sivustosta yhtään tietoturvallisemman.
Kirjautuminen ei todellakaan ole ainut tärkeä osa-alue, sillä itse väärinkäytäjä voi tulla myös sisältäpäin. Kunnun kirjautuminen kuitenkin vähentää sitä riskiä, että vähän kokeneempi käyttäjä pääsisi tekemään suurta vahinkoa. Mielestäni tietoturva on kuitenkin aloitettava suunnittelemaan hyvin siitä asti mihin käyttäjä ensimmäisenä tulee, eli kirjautumissivulle. Kun sen osaa tehdä oikein, on todennäköisempää, että osaa tehdä jatkossakin jotain oikein :)
Mikä olisi toimivampi ratkaisu päätteiden tarkistukseen, kuin tämä:
$tiedosto = "tiedosto.php.png"; $path_parts = pathinfo($tiedosto); $file_ext = $path_parts['extension']; if($file_ext == 'png' || $file_ext == 'jpg' || $file_ext == 'gif'){
Koska tämä katsoo vain viimeisen pisteen jälkeen. Saako rexepeillä vai onko jokin toimivampi funktio?
Siis tarkoitan sitä, että se ei hyväksyisi ko. tiedostoa.
Et sinä tarvitse kuin viimeisen osan. Se on tiedostotyypin tunniste. Kaikki muu on osa varsinaista tiedoston nimeä. Sinulle on sitä paitsi jo kerrottu, että kannattaa laittaa kaikki sallitut tiedostotyypit arrayhyn ja käyttää funktiota in_array().
Viimeisen osan tarkistaminen riittää aivan hyvin. Jos palvelin ajaa tiedoston a.php.png PHP-tulkilla, palvelimessa on vikaa.
Ja tämä kysymys ei selvästikään kuulunut tähän keskusteluun. Tee kysymyksillesi omat keskustelut, jos ne eivät suoraan liity käsillä olevaan asiaan.
En tiiä lyhenikö, mutta kaippa tää on kätevämpi..
<?php $tiedosto = "testi.txt.png.php"; $path_parts = pathinfo($tiedosto); $file_ext = $path_parts['extension']; if (!in_array ($file_ext, array ("png", "jpg", "gif"))) echo "Tiedostopääte ei kelpaa."; ?>
Eli tää riittää tiedosto päätteen tarkistukseen, jos kerta viimeinen on se "validi".
Aihe on jo aika vanha, joten et voi enää vastata siihen.