Vsi podatki, ki je vnese uporabnik preko spletnih obrazcev so potencialni nevarni podatki. Osnovno pravilo je, da programer ne sme zaupati uporabniku vnesenih podatkov, ampak mora le-te preveriti. Eden izmed najbolj razsirjenih napadov je SQL injection. To je napad s katerim napadalec spreminja SQL stavek in posledično dobi iz podatkovne baze podatke kateri mu niso namenjeni. Poglejmo si preprost primer.
<?php mysql_connect('localhost', 'root'); $sql = 'SELECT priimek FROM uporabniki WHERE ime=\''.$_GET['ime'].'\' AND geslo=\''.$_GET['geslo'].'\''; $r = mysql_query($sql); $row = mysql_fetch_assoc($r); echo $row['priimek']; ?> |
Kot vidimo sestavimo SQL stavek iz dveh spremenljivk, ki smo ji dobili preko GET metode. Če bi uporabnik zahteval sledečo skripto: /skripta.php?ime=janez&geslo=kranjski. Iz tega dobimo veljavni SQL stavek. Prvi problem, ki se pojavi je, če ime vsebuje posebne znake kot je enojni narekovaj - '. Kot 21521x2316v vemo morajo biti nizi obdani z enojnim ali dvojnim narekovajem. V nasem primeru je niz obdan z enojnim in ker ime vsebuje enojni narekovaj, PHP smatra, da je tukaj konec niza. Preostanek pa zeli ignorirat, ampak ker ni konec niza javi napako. To je prva nevarnost, kajti napadalec lahko vidi pot skripte, vrstico in se druge podrobnosti. Prav na ta način se da izvesti SQL napad. Torej lahko napadalec po svoji zelji spreminja SQL stavek. V nasem primeru kliče skripto na sledeč način: /skripta.php?ime=admin'#. Kot vidite mu gesla niti ni potrebno vedet, da bo izpisal priimek uporabnika admin. Zraven imena se je na koncu enojni narekovaj, ki pove konec niza, znak # pa pomeni komentar, torej konec SQL stavka v tej vrstici. Tako napadalec doseze, da se geslo nikdar ne preverja.
Vidimo, da sploh ni tako tezko izvesti SQL injection napada. Zato mora programer poskrbeti za varnost in nikdar zaupati uporabniku, kaj vnese. Torej mora te podatke nekako filtrirati. Stevila filtriramo na precej preprost način. Kot vemo sta spremenljivki $_GET in $_POST polji nizov. Torej vrednosti, ki predstavljajo stevila pretvorimo v zeleni tip. Za celo stevilo lahko uporabimo sledeča primera, ki se med seboj ne razlikujeta.
<?php $id = (int)$_GET['id']; // ali funkcijo intval $id = intval($_GET['id']); ?> |
S tem zagotovimo, da je spremenljivka $id vedno tipa celo stevilo. V primeru da je GET id neveljavno stevilo se pretvori v 0.
Več dela imamo z nizi. Najprej moramo paziti kaksne podatke dobimo. Namreč PHP nam omogoča, da ze sam avtomatsko obdela vse vrednosti, ki pridejo od uporabnika in jim doda znak \. Če imamo to vklopljeno lahko preverimo s funkcijo get_magic_quotes_gpc(), katera vrne pravilno v primeru, če PHP obdela vrednosti. Takrat moramo preden izpisujemo podatke ali sestavljamo SQL stavek te vrednosti očistiti s pomočjo funkcije stripslashes(). Nato uporabimo funkcijo mysql_real_escape_string(), katera prejme kot argument niz in vrne varen niz, iz katerega lahko sestavimo SQL stavek. Tako bi v nasem primeru zgledala varna skripta.
<?php mysql_connect('localhost', 'root'); if ( get_magic_quotes_gpc() ) $_GET['ime'] = mysql_real_escape_string($_GET['ime']); $_GET['geslo'] = mysql_real_escape_string($_GET['geslo']); $sql = 'SELECT priimek FROM uporabniki WHERE ime=\''.$_GET['ime'].'\' AND geslo=\''.$_GET['geslo'].'\''; $r = mysql_query($sql); $row = mysql_fetch_assoc($r); echo $row['priimek']; ?> |
Na začetku očistimo niz in ga pripravimo na izpis, nato spremenljivki spustimo skozi funkcijo mysql_real_escape_string, preden sestavljamo SQL stavek. Seveda bi lahko funkcijo klicali kar v naslednji vrstici, kjer sestavljamo SQL stavek. To nam pride prav v primeru, če se za tem potrebujemo spremenljivki. Slabost tega je slabsa preglednost in dolga vrstica iz katere se na prvi pogled ne da razbrati, kaj vsebuje.
Pomembno je, da ne pozabimo preveriti ali nam ze sam PHP obdela niz, kajti v tem primeru bomo imeli dvakrat obdelan niz, kar spet ni zelen rezultat in lahko prav tako vodi k varnostnimi luknjami.
V prejsnjem delu smo spoznali kako preberemo podatke iz spletnega obrazca. Sedaj, ko poznamo tudi (ne)varnosti obrazcev lahko te podatke začnemo zares uporabljati. Nadaljevali bomo primer dnevnika iz prejsnjega dela sole. Naredili bomo obrazec in skripto, ki dodaja dnevniske vpise. Poglejmo si kodo.
<?php if ( get_magic_quotes_gpc() && is_array( $_POST ) ) @reset( $_POST ); $msg = @$_POST['msg']; if ( !empty( $msg ) ) else mysql_connect( 'localhost', 'matjaz', '3' ); mysql_select_db( 'planetpc' ); mysql_query( 'INSERT INTO blog (vsebina,datum) VALUES ("' . mysql_real_escape_string( $msg ) . '", "' . date( 'Y-m-d G:i:s', $date ) . '")' ); echo 'Sporočilo dodano<br><br>'; ?><form action="blog_add.php" method="POST"> Datum:<br><input type="text" name="date" value="<?php echo date( 'Y-m-d G:i' ); ?>"><br> Sporočilo:<br><textarea name="msg" rows="5" cols="30"></textarea><br> <input type="submit" value="Dodaj"> </form> |
Najprej preverimo ali ze sam PHP obdela vrednosti in jih ustrezno vrnemo v originalne vrednosti. V nasem primeru pogledamo samo POST vrednosti, saj nas druga ne zanima. Nato si naredimo začasno spremenljivko v kateri je vsebina polja. Preverimo ali vsebina polja ni prazna (pozor, funkcija empty smatra tudi vrednost 0 kot prazno). Če je obrazec potrjen in je vsebina dnevnika vpisana nadaljujemo s vpisovanjem. Preverimo ali je vpisan datum in ga ustrezno pretvorimo. V naslednjih vrsticah se povezemo z bazo in dodamo zapis v bazo.Vrednost vsebine dnevnika obdamo s funkcijo, ki zavaruje niz pred napadi, datum pa smo pretvorili ze prej in ga zato lahko v ustreznem formatu vpisemo brez strahu. Na koncu je se obrazec za vpis dnevnika.
Vidimo lahko da se vrednost polja datum vedno izpolni, saj smo določili da se izpise datum v vrednost polja. Pozorni moramo biti na if stavek, kateri odloči ali se naj izvede vpisovanje v bazo. Zadeva pri delu z obrazci se malo zakomplicira oziroma je potrebno napisati več kode, kot le pri izpisovanju vrednosti. Vendar ob upostevanju navedenih pravil mora biti vsaka aplikacija varna.
PHP nam omogoča prenos datoteke na streznik in morebitno hrambo le te na strezniku. Opozoriti je potrebno, da je moznost nalaganja datotek in shranjevanje le teh morebitna varnostna luknja, saj lahko napadalec nalozi zli kodo in to izvede. Zatorej je potrebno preden to omogočimo poskrbeti za varnost. Dokler omogočamo nalaganje slik ipd. so stvari preproste, pri nalaganju npr. datoteko s končnico .php pa lahko naredimo varnostno luknjo. Paziti moramo da uporabnik ne more dostopati do te datoteke direktno, saj lahko v tem primeru zazene to skripto.
Poglejmo si primer kako naloziti sliko tipa GIF. Najprej obrazec, s katerim poisčemo datoteko na računalniku, ki jo zelimo naloziti.
<form enctype="multipart/form-data" action="upload.php" method="POST"> <input type="hidden" name="MAX_FILE_SIZE" value="100000"> Datoteka: <input name="file" type="file"> <input type="submit" value="Nalozi"></form> |
Formi smo spremenili tip kodiranja, tako da lahko nalagamo datoteke. Polje MAX_FILE_SIZE merjeno v bajtih nam pove maksimalno velikost datoteke, ki sme biti nalozena. To polje je zazeleno, saj v primeru da je datoteka prevelika se niti ne izvede posiljanje datoteke na streznik. Polje s katerim poisčemo datoteko je po HTML standardu tipa file. Poglejmo se skripto, ki preveri pravilnost datoteke in jo shrani na streznik.
<?php if ( isset( $_FILES['file'] ) && $_FILES['file']['size'] > 0 ) if ($_FILES['file']['size'] > 100000) $uploadfile = dirname($_SERVER['SCRIPT_FILENAME']) . '/' . basename( $_FILES['file']['name'] ); if ( move_uploaded_file( $_FILES['file']['tmp_name'], $uploadfile ) ) else ?> |
Najprej preverimo ali je bila skripta poklicana preko obrazca in je bila izbrana datoteka večja od 0 bajtov. Nato preverimo ali je tip datoteke GIF slika. V primeru da ni, to izpisemo in končamo izvajanje skripte z die funkcijo. Enako preverimo za velikost datoteke. Nato si naredimo spremenljivko katera vsebuje polno pot do skripte in ime datoteke. V nasem primeru se slika shrani v isti direktorij kot je skripta. Funkcija move_uploaded_file premakne datoteko iz začasne mape na zelen cilj in vrne pravilno če datoteka obstaja oziroma je bila datoteka nalozena preko obrazca, v nasprotnem primeru vrne nepravilno. Glede na to tudi izpisemo rezultat. S tem smo uspesno nalozi datoteko na streznik. Če datoteko zelimo le prebrati in obdelati njeno vsebino, lahko dostopamo do nje iz začasne mape. Ime datoteke izvemo preko $_FILES['file']['tmp_name'] spremenljivke.
Oglejmo si se nekaj naprednejsih tehnik pri delu s PHP-jem. Prvo kar bomo omenili je vključevanje datotek. To nam pride prav na primer, kadar uporabljamo določene funkcije v različnih datotekah. Te datoteke razbijemo na več manjsih in smiselno v njih zdruzimo skupne funkcije. Poudariti je treba doseg spremenljivk.Če vključimo datoteko v svojo skripto, vsebina postane del skripte, kot da bi bila v tej datoteki. Torej so globalne spremenljivke skupne. Funkciji s katerima vključimo datoteko sta include in require. Razlika med njima je le kako se obravnavajo napake. Require sprozi ob napaki napako, ki konča izvajanje skripte, medtem ko include vrne le opozorilo in se nadaljuje izvajanje. Poglejmo si primer. Datoteka vars.php
<?php $barva = 'zelena'; $sadje = 'jabolka'; ?> |
Datoteka primer.php
<?php echo "$barva $sadje"; include 'vars.php'; echo "$barva $sadje"; ?> |
Kot vidimo se prvič ne izpise nič, saj je vsebina spremenljivk prazna oziroma nista niti definirani. Po vključitvi datoteke spremenljivki dobita vrednost in se izpise njuna vsebina.
Ena izmed tehnik, katere se posluzujejo PHP programerji je tudi uporaba @ operaterja. S pomočjo tega operaterja izklopimo javljanje napak in tako preprečimo, obiskovalcu izpis kakrsnekoli informacije kaj, kje in zakaj se je zgodila napaka.
<?php echo @filesize('datoteka_ne_obstaja'); |
V tem primeru ker datoteka ne obstaja ne bo izpisane nobene napake, saj smo za določeno funkcijo izklopili javljanje le teh. Prav tako lahko izklopimo javljanje napak za spremenljivke, na primer kadar indeks polja ne obstaja.
PHP nam omogoča da lahko vsakrsno napako obravnavamo sami. Na začetku skripte podamo sledečo funkcijo set_error_handler, s katero določimo ime nase funkcije, ki bo skrbela za napake. Navedena funkcija mora imeti vsaj dva argumenta. Prvi je stevilka napake, drugi pa sporočilo. Dodani so se lahko trije neobvezni argumenti. Napake lahko prozimo tudi ročno s pomočjo funkcije trigger_error, kateri podamo tekst napake in neobvezno kot drugi argument tip napake. To nam pride prav ob morebitni napačni vrednosti v obrazcu.
Naslednjič si bomo ogledali kako upravljamo s sejami, uporabljamo piskotke in na kratko se bomo dotaknili objektnega programiranja v PHP jeziku.
|