Kirjautuminen

Haku

Tehtävät

Keskustelu: Nettisivujen teko: PHP tietoturvaa ja taas sivunavigaatiota

Sivun loppuun

Omasual [23.04.2008 10:47:27]

#

Täällä on tosta navigaatiosta ollut sata kertaa juttua, mutta ei ihan oikeata vastausta löytynyt.

Testivaihe on ohi ja nyt pitäisi tähän lisätä vähän tietoturvaa. Eli linkit ovat index.php?s=index jne. Menuun haetaan sivut kannasta ja tehdään linkki niistä joista on kannassa jotain. Tällä hetkellä homma menee niin, että GETillä kerrotaan suoraan mitä haetaan kannasta. Kuinka teen tämän tietoturvallisemmin?

Sivustoa pystyy päivittämään kätevästi selaimella, ilman mitään boldaus ym wysiwyg-härpäkkeitä. Toisinsanoen käytetään vain textareaa tekstieditorina, jossa koodi on. Tällä hetkellä se toimii hyvin, kun siellä on vain hötömölöä. Kannattaisiko jossain määritellä jotain, muuttaa käyttäjän syötettä tms? Syötehän siis tapahtuu suojatusti ja luotetusti.

Ääkköset menee sellaisenaan, <p>, <a>, <li> toimii hyvin ilman kikkailuja.

walkout_ [23.04.2008 12:16:52]

#

http://www.securityfocus.com/bid/18219

https://www.php.net/mysql_real_escape_string

tiedä sitten onko tästä hyötyä..

$_GET['page'] = mysql_real_escape_string($_GET['page']);
// tämä poista vahingolliset erikoismerkit

$_GET['page'] = preg_replace("![^0-9A-Za-z\s]!", "", strip_tags($_GET['page']));
// tämä pakottaa että muutujassa voi olla vain merkit 0-9, A-Z ja a-z

En tiedä filteroiko noi sen muuttujan. Kun noi pistää index.php:n alkuun.

Yksi tapa on tehdä homma PDO_MYSQL rajapinnalla jolloin automaagisesti SQL-käsky ei kännisty jos siinä on mitään ylimääräistä kuten esim ID muuttuja ei ole Integer, jos kannassa ks. fieldi on esim. INT(11)

Siis riski jos se on näin

mysql_query("SELECT * FROM taulu WHERE sivu = ".$_GET['sivu'].";");

niin tohon voi GET muutujalla syöttä mitä tahansa SQL:llä kuten index.php?sivu=jotain; DROP taulu yms..

Tätä ei sit kandee tehdä koskaan

<?php eval($_POST['jotain']); ?>

Wizard [23.04.2008 13:04:59]

#

Hankalaa.

Oletan, että hyväksyt vain merkkejä a-z ja 0-9 eli muuttujan tarkistus hoituu yhdellä käskyllä:

<?php
if(ctype_alnum($_GET['page'])):
    // Tee jotain
endif;
?>

Erikoismerkkejä ei kannata sallia osoiterivillä ilman pätevää syytä. Huomaa, että nuo ctype_* funktiot ovat C-peräisiä eli ne eivät tunne ääkkösiä (skandeja) ja muita vastaavia merkistöjä. https://www.php.net/manual/en/ref.ctype.php

Walkoutin tarjoama preg_replace funktion käyttöä en suosittele, koska se KORVAA haitalliset merkit, mutta jos tiedät, että niitä ei hyväksytä, niin miksi edes viedä sellaista muuttujaa kantakyselylle? Tulee turha kysely jos korvaat haitalliset merkit esim. tyhjällä joka ei kuitenkaan palauta mitään. Saat evaluoitua tuon falsen suoraan ctype_alnum metodillakin.

Ja opettele vaan käyttämään ihan mysql & mysqli rajapintoja, pdo_mysql on tehoton ja sen toimivuus tuntuu olevan välillä hieman niin ja näin. Muuttujien arvojen ja tyyppien tarkistuksen voi tehdä itsekin erittäin helpolla. Lisäksi kannattaa käyttää boolean vertailuja.

Sitten noissa osoiteriveissä kannattaa ottaa huomioon HTML 4 ja valid lähdekoodi.

-W-

tsuriga [23.04.2008 13:39:15]

#

walkout_ kirjoitti:

Yksi tapa on tehdä homma PDO_MYSQL rajapinnalla jolloin automaagisesti SQL-käsky ei kännisty jos siinä on mitään ylimääräistä kuten esim ID muuttuja ei ole Integer, jos kannassa ks. fieldi on esim. INT(11)

Kyllä ainakin bindParamsiakin käyttäen tuossa tilanteessa tapahtuu automaattinen tyyppikonversio. Ainakin mikäli on annettu parametriksi kentän tyyppi, mahdollisesti myös ilman kyseistä parametria. BindParams antaa myös suojaa SQL-injektioita vastaan.

Kts. myös filter-lisäosa.

EDIT: Ja jos yllä olevaa herraa on uskominen, kuten varmaan on, ja tuo PDO on todellakin hidas, niin myös MySQLi-tukee sidottuja parametreja. Tuo MySQLi on oikeastaan se luontevampi, oliopohjainen vaihtoehto myslin käpristelyyn PHP:n vitosversiossa.

Omasual [23.04.2008 13:51:22]

#

Wizard kirjoitti:

Sitten noissa osoiteriveissä kannattaa ottaa huomioon HTML 4 ja valid lähdekoodi.

Tämä meni vähän ohi, miten tulisi ottaa huomioon osoiteriveissä?

Lähdekoodi on validia XHTML 1.0 Strictiä.

Kiitos vastauksista, näillä pärjänen.

walkout_ [23.04.2008 14:29:07]

#

tsuriga kirjoitti:

walkout_ kirjoitti:

Yksi tapa on tehdä homma PDO_MYSQL rajapinnalla jolloin automaagisesti SQL-käsky ei kännisty jos siinä on mitään ylimääräistä kuten esim ID muuttuja ei ole Integer, jos kannassa ks. fieldi on esim. INT(11)

Kyllä ainakin bindParamsiakin käyttäen tuossa tilanteessa tapahtuu automaattinen tyyppikonversio. Ainakin mikäli on annettu parametriksi kentän tyyppi, mahdollisesti myös ilman kyseistä parametria. BindParams antaa myös suojaa SQL-injektioita vastaan.

Kts. myös filter-lisäosa.

EDIT: Ja jos yllä olevaa herraa on uskominen, kuten varmaan on, ja tuo PDO on todellakin hidas, niin myös MySQLi-tukee sidottuja parametreja. Tuo MySQLi on oikeastaan se luontevampi, oliopohjainen vaihtoehto myslin käpristelyyn PHP:n vitosversiossa.

Jaa.. no sitten erääss PHP oppassa puhutan paskaa että se on kinggi tai ei haluata kertoa ihan kaikkia yksityiskohtia. Kirjat on yleensä kirjoitettu kyllä niin että tekijä haluaa mainostaa ja kehua valitsemiaan asioita. Kuten että PHP on niin Cool ja kuningas ja samaa hehkutusta on yllätys yllätys viereisessä J2EE-kirjassa jossa muut tekniikat hakutaan.

Ja mitä hitauteen tule niin onko sillä väliä jollain perus saitilla jossa on ne kaikki 1-100 sivua?
Kutenkaan ei ole kyse tietokannasta jossa on 20000 kpl rivejä relaatioineen jolloin jo monella asialla voidaan optimoida kantaa ja tai hidastaa.

Wizard [23.04.2008 19:24:17]

#

walkout_ kirjoitti:

Jaa.. no sitten erääss PHP oppassa puhutan paskaa että se on kinggi tai ei haluata kertoa ihan kaikkia yksityiskohtia. Kirjat on yleensä kirjoitettu kyllä niin että tekijä haluaa mainostaa ja kehua valitsemiaan asioita. Kuten että PHP on niin Cool ja kuningas ja samaa hehkutusta on yllätys yllätys viereisessä J2EE-kirjassa jossa muut tekniikat hakutaan.

Ja mitä hitauteen tule niin onko sillä väliä jollain perus saitilla jossa on ne kaikki 1-100 sivua?
Kutenkaan ei ole kyse tietokannasta jossa on 20000 kpl rivejä relaatioineen jolloin jo monella asialla voidaan optimoida kantaa ja tai hidastaa.

Ihan vaan tiedoksi, että 20.000 riviä tietokannassa on mitätön määrä. Tuossa määrässä ei vielä tunnu juuri missään vaikka joku relaatio olisi päin mäntyä tai indeksi väärässä paikassa tai puuttuu joku indeksi. Tuollaisen määrän rivejä tietokantapalvelin käy kuitenkin läpi alta sekunnin yleensä vaikka tehtäisiin tauluihin full_scan. 1.000.000 riviä on myös vähän, 10 miljoonaa jo hieman ja sitten lähdetään vasta puhumaan edes jonkinkokoisista tietokannoista. Kun tietokantojen koko alkaa olemaan levyllä 25-50GB, niin voit sanoa, että nyt on jo edes hieman isompi tietokanta kyseessä. Ja tuo määrä ei sisällä edes mitään blobeja vaan on yleensä pelkkää tekstiä. Työpaikoilla on tullut katseltua tietokantoja joiden koko on levyillä satoja gigoja. Nekin ovat vain osa suurta kokonaisuutta... ;)

Jos saitilla on esim. 100 sivua frontendissä, niin se ei kerro vielä yhtään mitään taustajärjestelmien koosta.


Ohjelmointikielistä ja niiden paremmuudesta väitteleminen on turhaa. Joku soveltuu paremmin johonkin toiseen käyttöön ja joku toinen johonkin muuhun. Tuskin kovin moni enää käyttää CGI -scriptejä esim. sähköpostin lähetykseen vaikke ne sinänsä hyvin toimivatkin.

-W-

Omasual [24.04.2008 11:21:29]

#

Vielä tähän sen verran, että nyt toimii, mutta:
- kun laittaa index.php?s=yhteystiedot niin toimii. Jos laittaa index.php?s=yhteystiedot<%" niin antaa indexin.
- mutta jos laittaa index.php?s=eitataole niin antaa tyhjän, kun ei osaa hakea kannasta.

Kuinka kannattaisi toteuttaa?

$sivu = $_GET['s'];

if (ctype_alnum($sivu)) {
$haku = "SELECT id, title, linkkinimi, otsikko, teksti, DATE_FORMAT(updated,'%d.%m.%Y') AS date FROM sivut WHERE sivu = '$sivu' ORDER BY id ASC";
}

else {
$haku = "SELECT id, title, linkkinimi, otsikko, teksti, DATE_FORMAT(updated,'%d.%m.%Y') AS date FROM sivut WHERE sivu = 'index' ORDER BY id ASC";
}

Teuro [24.04.2008 12:04:38]

#

Omasual kirjoitti:

$sivu = $_GET['s'];

if (ctype_alnum($sivu)) {
$haku = "SELECT id, title, linkkinimi, otsikko, teksti, DATE_FORMAT(updated,'%d.%m.%Y') AS date FROM sivut WHERE sivu = '$sivu' ORDER BY id ASC";
}

else {
$haku = "SELECT id, title, linkkinimi, otsikko, teksti, DATE_FORMAT(updated,'%d.%m.%Y') AS date FROM sivut WHERE sivu = 'index' ORDER BY id ASC";
}

Järjestely on ehkä turhaa, koska tuossa ei saa löytyä kuin yksi ainoa sivu. Voisit tietty pakottaa haun id:n perusteella. Tämä tapa helpottaa kummasti syötteen validointia, koska jos se ei ole int niin ei tehdä hakua muutoin haetaan id:n perusteella.

Tällaisten kantojen kanssa ei kannata kikkailla optimoinneilla, koska kuten wizard mainitsikin ovat tällaiset kannata monesti melkoisen pieniä ja tietokantamoottori selviää näistä hyvinkin nopeasti.

Tietoturvallisuuskin hieman paranee kun ei anneta käyttäjälle tietoa minkä nimisiä kenttiä kannassa on. Samalla osoiterivikin hieman siistyityy.

tsuriga [24.04.2008 12:11:23]

#

Omalla kohdalla ainaki semmoset fiilikset, että osoitteessa plussaa muistettavuudesta - "auth" on helpompi muistaa sekä selvempi kuin "52" (olettaen siis, että näitä alisivuja on useampia ja jokaisella oma id). Zendillähän voit vetää esim. routerin avulla kantaan ihan eri nimet kuin mitä URLissa vilkkuu.

Omasual [24.04.2008 12:24:52]

#

Teuro: mutta sama homma id:n kanssa hakiessa. Jos kannasta löytyy sivut id-numeroilla 1-10 ja käyttäjä syöttää urliin id:n 55, niin tulee tyhjä sivu kun ei löydy kannasta sillä mitään.

Miten tuohon siis saisi sellasen, että mikäli ei löydy niin palauttaa indexin?

Metabolix [24.04.2008 12:25:24]

#

Nyt mietit väärää kohtaa koodista.

<?php
$r = mysql_query($haku);
if (mysql_num_rows($r) == 0) { // 0 tulosta, haetaan index:
  $r = mysql_query($index_haku);
}
?>

Omasual [24.04.2008 12:28:30]

#

Ah, näinpä tietty. Kiitos Metabolix!

Teuro [24.04.2008 12:41:07]

#

Samalla voisi mainita, että haettua sivua ei löydy, joten siirrytään etusivulle.

Tuossa voisi sivusto lähettä ylläpidolle mailia, että yritetty hakea sivua, jota ei löydy. Tällä tavalla saa kiinni osan rikkinäisistä linkeistä.

Metabolix [24.04.2008 12:54:45]

#

Teuro kirjoitti:

Tällä tavalla saa kiinni osan rikkinäisistä linkeistä.

Ja paljon spammia innokkaista hakkereista, jotka kokeilevat hienoa ominaisuutta. $_SERVER['HTTP_REFERER'] tietenkin kertoo usein, onko käyttäjä tullut sivulle linkistä vai omin avuin, joten sen avulla voisi nuo tapaukset erotella.

Wizard [24.04.2008 19:28:22]

#

Yhdellä kyselyllä:

select
  (select sivu from taulu where sarake = $arvo) as sivu,
  (select oletus from taulu) as index

Ja sitten vaan yksinkertaisesti vertaat haettua tulosta jotakuinkin seuraavasti:

if($tulos['sivu']):
  // sait hakutulokset $muuttujalle
  $sivu = $tulos['sivu'];
else:
  // Käytä oletusta
  # Send error msg to admin?
  $sivu = $tulos['index'];
endif;

Oliona:

if(! $tulos['sivu']:
  # Send error msg to admin?
  return $tulos['index'];
else:
  return $tulos['sivu'];
endif;

Selitys: ensimmäinen haku tuottaa tuloksen NULL jos vastausta ei löydy. Toinen haku hakee oletus sivun joka on valmiina tulos taulukossa odottamassa eikä tarvitse tehdä uutta kyselyä oletus sivun selvittämiseen.

Hyöty: vain YKSI lähetetty SQL kysely kantaan, säästäät resursseja. Jos siis haluaa hieman hifistellä ja hienostella, että DB-yhteyksiä ei käytetä turhaan. Voisit toki tuohon SQL:ään lisätä myös liian vähän käytetyn EXISTS vertailun. PL/SQL on vaativampaa, mutta sieltä löytyy todella paljon tosi hyviä asioita.

Säästit siis yhden ylimääräisen SQL kyselyn parhaimmassa tapauksessa. Jos taas tulos löytyi, niin tuon toisen selectin suorittaminen kesti ehkä 0.00001 sekuntia. Toisinpäin haitta voi olla sen yhden kyselyn suorittaminen joka kestää aika pirun paljon enemmän kun kysely ensin lähetetään, TCP/IP jumittaa, kaivetaan uutta kyselyä varten palvelimelta resursseja... Pistät kertoimeksi > 1.000 käyttäjää joka sekunti ja pääset laskemaan säästettyjen resurssien määrän sadoissa tai jopa tuhansissa euroissa. ;)

Toisin sanoen: opetelkaa syntaksi, sitä kannattaa hyödyntää. Varsinkin esimerkiksitä puuttuva EXIST on erittäin hyödyllinen. Karvat nousee pystyyn kun ajattelen tässä ketjussa mainittua tapaa jossa if(mysql_num_rows()... tyylillä testataan rivien määrää ja sitten etsitään uudelle kyselylle resursseja. Tyyli kyllä toimii, mutta käyttää loppupeleissä kokonaisuutta katsottaessa enemmän resursseja.

-W-


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta