PHP Security
Sadržaj |
Vrste napada na web aplikacije
SQL injection
SQL injection je vrsta napada na web aplikacije kod kojih je korisnički unos manipuliran na način da ranjiva aplikacija izvrši neželjene SQL naredbe i pritom može kompromitirati razne povjerljive podatke. Klasičan primjer SQL injectiona, ako imamo ovakav upit:
SELECT bankcard_pin,bankaccount FROM users WHERE username = '{$username}' and password = '{%password}'
Korisnik može unijeti slijedeće te se na taj način prijaviti u sustav bez znanja lozinke i username-a:
$username = "' or 1=1 --'"; $password = "bla"; //Greška je ta što je programer htio postići odvajanje koda od podataka: SELECT bankcard_pin,backaccount FROM users WHERE username = ' ' or 1=1 -- ' and password = 'bla'; //No server to interpretira kao: SELECT bankcard_pin,backaccount FROM users WHERE username = ' ' or 1=1 --' and password = 'bla';
Cros site scripting
XSS je tip napada kod kojeg se maliciozna skripta unosi u povjerljivu web stranicu. Dešava se kada web stranica prikazuje neprovjerene podatke, u tom slučaju napadač može unijeti malicioznu skriptu (najčešće JavaScript) koja se izvršava ukoliko korisnik posjeti stranicu. XSS se dijeli na:
- Stored XSS - maliciozna skripta se trajno nalazi na stranici.
- Reflected XSS - napadač natjera korisnika da klikne na maliciozni link, koji će izvesti zahtjev prema web stranici. Zahtjev sadrži ubačeni JavaScript koji putuje na web stranici i reflektira se u korisnikovom browseru.
Primjeri XSS napada:
<body onload=alert('test1')> /* kada bilo koji korisnik posjeti stranicu pojavit će mu se obavijest na ekranu */ <script>doSomethingEvil();</script> /* izvršavanje raznih skripti */
Kako bi spriječili ovakav napad, prilikom izrade naše aplikacije ukoliko ispisujemo nešto što je korisnik prethodno unio u npr. neku formu potrebno je koristiti sljedeću naredbu
echo htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
Drugi nači je unutar PHP-a koristiti naredbu strip_tags($input, '<br')
koja prima dva argumenta: varijablu unosa te niz oznaka koje su dopuštene.
Cross site request forgery
CSRF je napad kod kojeg je korisnik natjeran da izvrši neželjene akcije unutar aplikacije u koju je trenutno prijavljen. Pomoću social engineeringa, napadač može navesti korisnika da klikne na maliciozni link ili posjeti malicioznu stranicu. Takvi napadi najčešće se preventiraju tzv. session tokenima (jedinstvenima po sjednici). Ukoliko je aplikacija dizajniranja za slanje novaca preko GET zahtjeva sa parametrima jedan od napada mogao bi izgledati ovako:
http://bank.com/transfer.do?acct=MARIA&amount=100000
Slično tome pretpostavimo da postoji POST zahtjev kao ovaj:
POST http://bank.com/transfer.do HTTP/1.1 acct=BOB&amount=100
Napadač bi vrlo lako mogao poslati POST zahtjev preko forme i na taj način napraviti napad:
<form action="http://bank.com/transfer.do" method="POST"> <input type="hidden" name="acct" value="MARIA"/> <input type="hidden" name="amount" value="100000"/> <input type="submit" value="View my pictures"/> </form>
Postoji nekoliko načina kako se možemo zaštititi od ovakvih napada a jedan od najkorištenjih su CSRF tokeni. Korištenjem tokena koji su jedinstveni za svakog korisnik i nalaze se unutar sesije te su genirani od nekog kriptiranog sigurnog generatora omogućuj se zaštiva protiv ovakvog vrsta napada.
Uvod u Zend Framework 2
Zend Framework je framework za izgradnju web aplikacija implementiran u PHP-u. Open-source je, objektno-orijentiran te modularan. Često se naziva kao ‘component-library’ zbog velikog broja komponenata koji se mogu koristiti zasebno (u Zendu to zovu 'use-at-will').
Dakle, za razliku od nekih drugih PHP frameworka koji se najčešče vode politikom ‘all or nothing’, funkcionalnosti Zend Frameworka moguće je uključiti u nativnu PHP aplikaciju. Razlog tome je što Zend slijedi SOLID principe OOP-a.
Između ostalog, pruža MVC (Model-View-Controller) implementaciju koja se može koristiti za strukturu Zend Framework aplikacije. Web aplikacije se praktično mogu podijeliti na tri sloja: prezentacijski, sloj poslovne logike i sloj pristupa podacima. MVC design-pattern dostojno slijedi ovu podjelu. Svaki od navedenih slojeva odvojen je kao posebni dio koda:
Model - dio aplikacije koji definira osnovne funkcionalnosti, pristup podacima te poslovnu logiku.
View - definira dio aplikacije vidljiv korisnicima. Vizualizira podatke koje model sadrži, a osim toga često i skupljaju podatke od strane samih korisnika.
Controller - djeluje i nad modelom i nad view-ovima. Kontrolira protok podataka u model te ažurira view u slučaju promjene podataka. Kontroler je u biti dio koda koji drži model i view odvojeno.
Zend Framework koristi tzv. Front Controller design pattern, što znači da se svi zahtjevi usmjeravaju prema jednom ulazu (index.php). Za to je potrebno konfigurirati .htaccess datoteku na način da svi zahtjevi upućuju na index.php. Nakon dolaska zahtjeva, index.php inicijalizira autoloader i bootstrap prije pokretanja aplikacije.
Konfiguracija .htaccess:
<IfModule mod_rewrite.c> RewriteEngine on RewriteRule (.*) controller.php [L] </IfModule>
Također, to omogućuje implementaciju sigurnosnih mjera (filtriranje i validacija inputa, autentikacija) na jednom mjestu.
Čitav proces obrade jednog web requesta u Zendu:
Potrebno je osigurati siguran rad svih komponenata Zend aplikacije, ukoliko samo jedna komponenta ne zadovoljava sigurnosne zahtjeve, sve aplikacije koje koriste tu komponentu mogu biti kompromitirane. Zend pruža velik broj komponenata koje enkapsuliraju različite sigurnosne zahtjeve kao što su: filriranje sadržaja, sprečavanje injekcija, upravljanje sjednicom. Načini na koje Zend Framework može učiniti aplikaciju sigurnijom bit će navedeni u daljnjem tekstu.
Konfiguriranje Zend-a
Najjednostavniji način konfiguriranja Zend aplikacije je koristeći Composer. Composer je dependency manager za PHP, ukratko, automatski preuzima sve potrebne library-e, dependency-e koje bi inače bilo puno teže manualno konfigurirati. Preporučeni način za početak izgradnje Zend Framework projekta je preuzeti Zend Sceleton aplikaciju koja sadrži ZF2 MVC slojeve i potrebne module.
Preuzimanje Composer-a:
//Linux cd /var/www/public_html/SISprimjer git clone git://github.com/zendframework/ZendSkeletonApplication.git cd ZendSkeletonApplication php composer.phar install //Windows git clone git://github.com/zendframework/ZendSkeletonApplication.git --downloadati composer sa web-a u direktorij projekta u cmd-u: composer install
Nakon instalacije pokrećemo Skeleton aplikaciju na Apacheu:
Sigurnosne značajke Zend Frameworka
Sigurnosni alati u Zend Framework-u
Zend pruža velik broj sigurnosnih alata koje omogućuju izradu sigurnijih web aplikacija. Alati:
- Zend\Authentication
- Zend\Captcha
- Zend\Crypt
- Zend\Escaper
- Zend\Filter
- Zend\InputFilter
- Zend\Permissions
- Zend\Math
- Zend\Validator
Neki od najvažnijih su prikazani u nastavku. Zend\Authentication pruža API za svrhu autentikacije i sadrži adaptere za najčešće scenarije. Primjer autentikacije:
$authentication = new AuthenticationService(); $adapter = new My\Auth\Adapter($username,$password); $result = $authentication->authenticate($adapter); if(!$result->isValid()){ //Authentication failed:error message } else{ //Authentication succeeded }
Zend\Permissions\Acl pruža implementacije ACL-a (access control liste) koja predstavlja listu dopuštenja objekata (rola) određenom objektu (izvoru):
$acl = new Acl(); $acl->addRole(new Role('user')) ->addRole(new Role('guest')) $acl->addResource(new Resource('resource')); $acl->allow('user','resource'); $acl->deny('guest','resource');
Zend\Validator pruža API za najčešće korištene oblike validacije podataka. Recimo, često je korištena validacija email adrese:
$validator = new Zend\Validator\EmailAddress(); if(!$validator->isValid($mail)){ //Email is not valid } else{ //Email is valid }
Zend\InputFilter je API za filtriranje i validaciju unosa podataka poput $_POST, $_GET vrijednosti. Za slanje podataka u input filter koristi se metoda setData().
$email = new Input('email'); $email->getValidatorChain() ->attach(new Validator\EmailAddress()); $password = new Input('password'); $password->getValidatorChain() ->attach(new Validator\StringLength(8)); $inputFilter = new InputFilter(); $inputFilter->add($email) ->add($password) ->setData($_POST); //dalje provjeriti sa $inputFilter->isValid()
U Zendu je implementirana Zend\Escaper klasa u svrhu prevencije od XSS napada. Sadrži pet različitih metoda za output escaping:
- escapeHtml - za html body kontekst
- escapeHtmlAttr - za html atribute
- escapeJs - za JavaScript
- escapeCss - za CSS kontekst
- escapeUrl - za URI kontekst
Valja još napomenuti i Zend\Crypt koji sadrži dosta korisne implementacije za kriptiranje podataka poput simetričnog, asimetričnog kriptiranja, HMAC funkcija, hasha, blok šifri itd. Blok šifra je recimo dosta dobro implementirana jer koristi AES 256, PKCS7, PBKDF, CBC+HMAC (SHA-256).
SQL injection
Kako bi preventirali ovakvu vrstu napada, preporuka je koristiti tzv. placeholdere (?) na mjesta gdje očekujemo podatke te unaprijed pripremiti SQL naredbe i poslati tu komandu na server (PREPARE) te dodati podatke (EXECUTE) i naposljetku dohvatiti podatke (FETCH). Zend Framework koristi klasu Zend_db koja uvelike olakšava programerima obranu od ove vrste napada zbog toga što koristi pripremljene procedure što je više moguće. Pripremljene procedure osiguravaju da napadači ne mogu promijeniti namjenu upita, iako je dio upita i dalje unesen od strane napadača. Primjer "prepare + execute" u Zend-u:
//spajanje na bazu $db = Zend_Db::factory('Mysql', array( 'host' => '127.0.0.1', 'username' => 'user', 'password' => 'pass', 'dbname' => 'test' )); //prepare + execute $select = $db->select() ->from('users',array('bankcard_pin,'bankaccount')) ->where('username = ?',$username) ->where('password = ?',$password); while($row = $select->fetch()){ //Dohvat podataka }
Na ovaj način osiguravamo da server uvijek interpretira upit kao (neće se ponoviti greška kao u prethodnom slučaju):
//U slučaju napada $username = "' or 1=1 --'"; $password = "bla"; //Server interpretira uvijek: SELECT bankcard_pin FROM users WHERE username = ' or 1=1' AND password = 'bla';
Još jedna tehnika obrane je escaping korisničkog unosa. Prema OWASP-u, ova metoda je nešto nesigurnija i ne može se garantirati da će spriječiti sve moguće napade SQL injection-om. U Zend-u, koriste se funkcije quote ili quoteInto. Quote escape-a samo znakove koji mogu terminirati string (', ", \0), dok se quoteInto češće koristi kod SQL upita.
$string = "O'Neal"; $name = $db->quote($string); // 'O\'Neal' $sql = $db->quoteInto("SELECT * FROM users WHERE userid = ?", $string);
Cross site request forgery
Ne postoji automatska zaštita od CSRF-a, pa je potrebno manualno. Zend Framework koristi Zend_Form_Element_Hash koji kreira jedan takav token koji se pridodaje formi:
class My_Form extends Zend_Form { function __construct() { parent::__construct(); $this->addElement('hash', 'csrf_token', array('salt' => get_class($this).'some salty text')); } }
Cross site scripting
U Zend-u, XSS ranjivosti mogu se pojaviti u View-u. Zend ne sadrži automatsko escape-anje podataka poput nekih drugih frameworka pa je zaštita od XSS-a zadatak programera. Najbolja praksa za prevenciju XSS-a je filtriranje inputa, a escapeanje outputa.
Primjer HTML escape-a:
$escaper = new Zend\Escaper\Escaper('utf-8'); <div class="user-input> <?php echo $escaper->escapeHtml($input); ?> </div
Za filtriranje inputa preporučljivo je koristiti HTML Purifier ili sanitizirajuće funkcije poput filter_var. Na Githubu postoji već integrirani HTML Purifier kao Zend Filter. Primjer preuzet sa Git-a:
//Standalone korištenje $purifier = new \zf2htmlpurifier\Filter\HTMLPurifierFilter(); echo $purifier->filter('<a href="#" onlick="javascript:alert(xss)">link</a>');
Broken authentication and session management
Korisnici često implementiraju svoje upravljanje sesijom, što često zna loše završiti. Preporuka je koristiti već ugrađene alate za upravljanje sesijom unutar određenog frameworka. Tako Zend_Session pomaže u upravljanju i čuvanju sesijskih podataka. Zend_Session_Namespace se koristi za odvajanje pojedinih sesijskih podataka. Pojedinim podacima se manipulira pomoću Zend_Session_Namespace objekata, a ne direktno preko superglobala $_SESSION. Svaka instanca Zend_Session_Namespace-a se referencira na odgovarajuće mjesto u $_SESSION superglobalnom polju.
$nameSpace = new Zend_Session_Namespace('namespace'); /Odgovara $_SESSION['namespace'];
Ukoliko se ne navede ključ za namespace, svi podaci se spremaju u namespace sa ključem 'Default'. Takođe, preporuča se ne pozivati direktno PHP funkcije _set,_get,_unset i _isset, osim iz podklase. Umjesto toga, standardni operatori pridruživanja automatski pozivaju potrebne funkcije. Primjer dohvaćanja sesijskih podataka:
$namespace = new Zend_Session_Namespace(); // default namespace $namespace->foo = 100; echo "\$namespace->foo = $namespace->foo\n"; if (!isset($namespace->bar)) { echo "\$namespace->bar not set\n"; }
Fiksacija sesije sprječava se ponovnim generiranjem session id-a (tokena) prilikom svake promjene statusa.
$defaultNamespace = new Zend_Session_Namespace(); if (!isset($defaultNamespace->initialized)) { Zend_Session::regenerateId(); $defaultNamespace->initialized = true; }
Za autentikaciju, unutar Zenda postoji Zend_Auth. Njegova svrha je dohvaćanje autentikacijskog adaptera koji se koristi za autentikaciju. Nakon uspješne autentikacije, mora biti prisutan prilikom svakog zahtjeva koji ima obavezu provjeriti je li korisnik uspješno autenticiran, a za to se koristi prethodno navedeni Zend_Session_Namespace. Recimo da imamo login formu koja standardno zahtjeva username i password, pravilan primjer autentikacije koji koristi MD5 hash funkciju i salt (preuzeto sa dokumentacije):
$adapter = new Zend_Auth_Adapter_DbTable( //postavljanje adaptera $db, 'users', 'username', 'password', 'MD5(CONCAT(?, password_salt))' ); $adapter->setIdentity($loginForm->getValue('username')); //stupac u tablici koji predstavlja identitet $adapter->setCredential($loginForm->getValue('password')); //stupac u tablici koji predstavlja password $auth = Zend_Auth::getInstance(); $result = $auth->authenticate($adapter); //autentikacija if(!$result->isValid()){ //autenthication failed } else{ //authentication succeeded }
Konfiguracija igra veliku ulogu u sigurnosti sessiona, sve SSL aplikacije bi trebale koristiti secure flag (preventira slanje cookiesa preko nekriptiranog kanala). Osim toga preporučljivo je i korištenje httpOnly atributa (cookiesi mogu biti dohvaćeni samo od strane server) kao dodatne zaštite protiv XSS-a te postaviti maksimalni lifetime sessiona. Primjer konfiguracije:
Zend_Session::setOptions(array('cookie_secure' => true, 'name' => 'myApp', //zasebni session name 'save_path' => '/sessions/myApp', //zasebni session storage 'cookie_httponly' => true, 'gc_maxlifetime' => 15 * 60 ));
Brute force attack
Zend\Crypt\Password ima implementiran bcrypt koji uspješno sprečava brute force napade za crackiranje passworda. Sigurnost bcrypta leži u njegovoj sporosti - za hash treba čak do sekunde što čini brute force napad skoro nemogućim za izvesti. Bcrypt je derivacijska funkcija koja prima tzv. cost parametar koji određuje broj ciklusa koji će algoritam provesti, što je veći cost to će izračun hasha biti duži i time otežati brute force napad. Zend je prethodno koristio za cost 14, no od verzije 2.3 taj je broj smanjen na 10 zbog velikog vremena za sprečavanje DOS napada. No, ukoliko želimo eksplicitno promijeniti cost to možemo učiniti pomoću funkcije setCost. Implementacija bcrypta:
use Zend\Crypt\Password\Bcrypt; $bcrypt = new Bcrypt(); $bcryptpass = $bcrypt->create('user password'); $password = "password to check"; if($bcrypt->verify($password,$bcryptpass)){ //password verificiran } else{ //password nije verificiran }
Primjer projekta
Prema tutorijalu je izrađena web aplikacija u Zend Frameworku (dostupno na repozitoriju: Github). Aplikacija je jednostavna i omogućuje dodavanje i brisanje željenih bilješki te njihov unos/brisanje/update u bazi.
Nad izrađenom aplikacijom provedeno je testiranje unutar alata OWASP ZAP, te su uočene neke ranjivosti blagog do srednjeg rizika. Također, provedeno je testiranje unutar alata Acunetix Web Vulnerability Scanner, no drugu opciju ne bi preporučio. Naime, iako je našla više ranjivosti (uočen i CSRF), druga aplikacija je onemogućila ulaz u bazu i obrisala velik broj datoteka unutar samog projekta. OWASP ZAP rezultati:
Error disclosure je generiranje poruka pogrešaka koje mogu u nekim slučajevima otkriti povjerljive informacije o korisnicima, nekim povjerljivim podacima i slično. Napadač može iskoristiti te podatke kako bi kasnije formirao neki složeniji napad na aplikaciju ili došao do povjerljivih podataka. Kako do toga ne bi došlo potrebno je error poruke konfigurirati na odgovarajući način, a informacije koje su bile potrebne za debug, ukloniti iz produkcijske verzije aplikacije.
Directory listing ili pretraživanje direktorija je omogućeno ukoliko na serveru postoji direktorij koji ne sadrži index datoteku. Iskorištavanje ove prijetnje je teško, no svejedno, prijetnja leži u tome da napadač može pregledati sve naše datoteke unutar tog direktorija i moguće pronaći način za zaobići sigurnosne mjere. Kako bi to onemogućili, u .htaccess datoteci potrebno je onemogućiti indeksiranje:
Options -Indexes
Clickjacking je napad prilikom kojeg korisnik ima namjeru klikom otići na željenu stranicu ili odabrati željenu radnju, no umjesto toga izvršava se neka druga, neželjena akcija. Ovaj napad preventira se slanjem X-Frame-Options HTTP response headera koji onemogućujue renderiranje stranice unutar <frame>, <iframe> i <object> elemenata koji mogu biti korišteni za takve napade. Konfiguranje apache-a kao prevencija:
Header always append X-Frame-Options SAMEORIGIN
ZAP također ukazuje na to da se ne koristi dodatna XSS zaštita unutar browsera. Zaštita koristi XSS-filter koji skenira sve zahtjeve i odgovore koji prolaze kroz browser i automatski može blokirati maliciozne skripte od izvođenja. Podržana je na nekim verzijama Chrome-a te na IE8. Omogućava se postavljanjem HTTP zaglavlja:
X-XSS-Protection: 1; mode=block
X-Content-Type-Options je zaglavlje koje koriste IE i Chrome za tzv. MIME-sniffing (istražuje bajtove kako bi se utvrdio tip podataka). No, ta opcija otvara moguću ranjivost - MIME sniffing algoritam se može prilagoditi na način da browser interpretira podatke na način da dozvoljava neželjene akcije, konkretno XSS. Zbog toga ZAP javlja da zaglavlje mora biti postavljeno na način:
X-Content-Type-Options: nosniff
--DomagojKopic 09:34, 17. siječnja 2016. (CET)
Laravel Framework
Laravel je besplatan, objektno orijentirani open-source PHP framework. Napravio ga je Taylor Otwell za izradu web aplikacija sljedeći MVC (model-view-controler) arhitekturu [13]. Uz Symphony ovo je najpopularniji framework za izradu web aplikacija. Omogućava izradu modularnih aplikacija, pojednostavljuje čitav proces izrade i održavanja aplikacija te ima ugrađeni veliki broj metoda i paketa za upravljanje sa bazama i vanjskim resursima.
Prilikom instalacije Laravel projekta automatski dobijemo veliku količinu datoteka i skripti koje svaka ima svoju zadaću. Iako neću detaljno ulaziti kako se izrađuju aplikacije u Laravel-u i objašnjavati strukturu laravel projekta, potrebno je objasniti nekoliko osnovnih pojmova kako bi mogli lakše razumjeti daljnji tekst.
Routing - Unutar svakog projekta postoji app/routes.php datoteka u kojoj definiramo naredbe za upravljanje preusmjeravanja te poziva "Controller" klase prilikom korisnikovog posjeta nekoj stranici unutar naše aplikacije.
Controllers - Unutar svake pojedine Controller klase definirane su metode u kojima se izvršavaju određene naredbe te korisniku vraća određena stranica i objekti.
Middleware - Klase koje omogućuju filtriranje HTTP zahtjeva unutar aplikacije. O njima ćemo više govoriti kasnije.
Model - Dio aplikacije koji definira osnovne funkcionalnosti, pristup podacima te poslovnu logiku.
Migrations - Migracije su "version control" za našu bazu podataka, omogućuju nam jednostavan razvoj i modificiranje naše baze podataka, odnosno tablica.
Views - To su stranice, skripte i predlošci naše aplikacije, odnosno ono što se vizualno prikazuje korisniku.
--Ivansusec 19:53, 10. siječnja 2016. (CET)
Sigurnost Laravel aplikacija
Osiguravanje sigurnosti web aplikacije te njihovih korisnika je jedno od najvažnijih dijelova svake aplikacije a Laravel ima ugrađene klase koje nam uvelike pomažu u izradi sigurne aplikacije. Na početku ću opisati kako se obraniti od svih najpoznatijih vrsta napada a kao primjer koristit ćemo manji projekt napravljen uz pomoć "Tutoriala" sa Laracast stranice. Laracast je web mjesto gdje postoji gomila sadržaja za učenje Laravel Frameworka te izgradnju "Best practise" rješenja. Aplikacija sadrži spajanje na bazu podataka, autentifikaciju i registraciju korisnika te sadrži sve osnovne dijelove tipične Laravel aplikacije. Podaci su pjesme koje je moguće kreirati, uređivati i pregledavati. Kroz aplikaciju proći ćemo kroz puno vrsta potencijalnih napada te kako je osigurana prevencija od istih.
Aplikacija sadrži nekoliko tablica od kojih nam je za razumijevanje daljnjeg teksta potrebno znati samo kako izgleda tablica "Articles" koja predstavlja tablicu pjesama. Struktura tablice "Articles" može se najlakše vidjeti u klasi migracije za tu tablicu:
class CreateArticlesTable extends Migration { public function up() { Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->integer('user_id')->unsigned(); $table->timestamps(); $table->text('body'); $table->string('title'); $table->timestamp('published_at'); $table->foreign('user_id') ->references('id') ->on('users') ->onDelete('cascade'); }); } }
Kao što vidimo za svaki stupac tablice definiran je tip podataka te ime stupca u zagradi. Tablica sadrži vanjski ključ na korisnika koji je vlasnik unosa nove pjesme, ali to nije toliko bitno zasad.
SQL Injection napadi
U Laravelu postoji takozvani The Eloquent ORM koji omogućuje sigurno i jednostavno upravljanje sa bazom podataka[13] . Svaka tablica iz baze podataka ima svoju "Model" klasu preko koje vrši interakcije nad tablicom. Bitno je napomenuti da Laravel podržava više vrsta baza podataka te postoji database.php datoteka u kojoj se definiraju podaci za vrstu i spajanje na bazu podataka. Ukoliko koristimo naš Eloquent model za dohvaćanje podataka tada smo sigurno da nam nitko ne može napraviti sql injection napad. Eloquent model bi trebao imati naziv jednine imena tablice na koju se odnosi. Tako u našem slučaju imamo tablice "Articles" te će se "Model" zvati "Article".
Primjeri sigurnog koda:
Article::findOrFail($id); Article::latest('published_at')->published()->get(); Article::latest('published_at')->where('published_at','<=',Carbon::now())->get();Prva naredba vraća artikl sa zadanim identifikatorom, druga sve artikle sortirane prema datumu postavljanja a treća još uz to ima i određeni uvjet (where klausulu). Problem nastaje kada želimo napraviti klasičan sql upit koristeći naredbu
DB::raw()
Loš primjer korištenja te naredbe je sljedeći:
$results = DB::select( DB::raw("SELECT * FROM some_table WHERE some_col = '$someVariable'") )Kao što već znamo na ovaj način napadač može u neko polje gdje se koristi takav upit upisati nešto tipa
' OR '1'='1
. Kako bi riješili ovaj problem i svejedno koristili klasičan sql upit jedno od rješenja je sljedeće:
$results = DB::select( DB::raw("SELECT * FROM some_table WHERE some_col = :somevariable"), array( 'somevariable' => $someVariable, ));
Metodu koju pozivamo na taj način ima ugrađeno da za svaki parametar vraća točno jedan redak u tablici i na taj način je spriječen sql injection.
DB::statement( 'ALTER TABLE HS_Request AUTO_INCREMENT=:incrementStart', array('incrementStart' => 9999) );
Drugi oblik sql injection napada je pokušaj manipulacije podacima iz baze. U našoj aplikaciji postoji forma za uređivanje pojedine pjesme. Forma omogućuje promjenu određenih atributa pojedine pjesme no što ako napadač proba promijeniti neko polje u tablici koje ne želimo da se može promijeniti. Jedna od mogućnosti je prilikom svakog "POST" requesta provjeravati koje vrijednosti se žele promijeniti ali onda to moramo raditi za svaku promjenu bilo kojeg podatka u našoj bazi. Puno jednostavnije rješenje je postaviti ograničenja unutar našeg "Eloquent Modela".
Recimo da želimo da korisnik može promijeniti naziv, tekst i datum tablice Articles to ćemo ostvariti sa sljedećim kodom:
class Article extends Model { protected $fillable = [ 'title', 'body', 'published_at', ]; }
Ukoliko želimo opisati koje vrijednosti u tablici ne želimo mijenjati umjesto "$fillable" koristit ćemo $guarded.
--Ivansusec 20:51, 10. siječnja 2016. (CET)
Cross-Site Scripting (XSS) napadi
U laravelu vrlo je lako spriječiti ovakve napade. Sve što je potrebno je omogućiti da korisnički unos se sprema i procesira kao čisti tekst i na taj način onemogući da se izvrši bilo kakav html, javascript i sl. dio koda.
1. <input type="text" class="form-control" name="name" <b>value="{{ old('name') }}"</b> 2. <input type="text" class="form-control" name="name" <b>value="{{!! old('name') !!}}"</b>>
Prvi redak prikazuje pravilan način za ostvarivanje obrane od XSS napada dok drugi redak prikazuje način na koji nam napadač može naštetiti sustavu ukoliko nemamo neku dodatnu zaštitu protiv XSS-a. Kao što vidimo bitno je da korismo {{ }}
sintaksu umjesto {{!! !!}
. Drugi način je malo kompliciraniji a to je da postavimo Middleware klasu u kojoj ćemo renderirati korisnički unos na način da maknemo sve oznake osim teksta iz unosa. U sljedećem primjeru možemo vidjeti klasu to na način da preuzme sve korisnikove unose i iz jednog po jednog miče oznake poput "<", ">" i sl. Middleware klase se moraju postaviti na "routes" ili globalno na sve klase a o postavljanju istih govorit ću na kraju teksta.
Primjer:
class XSSProtection { public function handle(Request $request, \Closure $next) { if (!in_array(strtolower($request->method()), ['put', 'post'])) { return $next($request); } $input = $request->all(); array_walk_recursive($input, function(&$input) { $input = strip_tags($input); }); $request->merge($input); return $next($request); } }
Broken Authentication and Session Management
Laravel ima ugrađene klase za pomoć pri autetifikaciji, registraciji i upravljanjem sesijom. Ovakva vrste napada vrlo je lako spriječiti korištenjem već definiranih klasa i metoda. Starije verzije Laravela su čak dolazile sa gotovim stranicama za prijavu i registraciju korisnika koje je lagano preuredit bez da se naruši sigurnost. U verziji 5.2 tih stranica nema ali se vrlo lako mogu pronaći na Laravel dokumentaciji[1].
Ukoliko ipak radimo vlastitu autentifikaciju i registraciju korisnika bitno je da znamo nekoliko osnovnih stvari. Za sve lozinke poželjno je napraviti hash pri registraciji, a pri svakoj prijavi provjeriti valjanosti hash-a.
password = Hash::make('secret'); /* Hashiranje lozinke */ if (Hash::check('secret', $hashedPassword)) { //code } /* Provjera valjanosti hasha */
Pri autentifikaciji korisnika poželjno je koristiti Auth::attempt
metodu. U sljedećem primjeru metoda funkcija provjerava ukoliko postoji korisnik sa zadanim email-om i lozinkom. Moguće je postaviti da se zapamti korisnik koji se prijavljuje tako da u metodu dodamo vrijednost "true" kao zadnji argument metode.
if (Auth::attempt(array('email' => $email, 'password' => $password),true)) { //izvrsi kod }
Ukoliko želimo provjeriti je li korisnik ulogiran dovoljno je upotrijebiti naredbu Auth::check()
. Nakon što je korisnik prijavljen moguće je dohvatiti njegove podatke sa Auth::user()->NekiPodatak
Laravel nam omoguće više načina i mjesta gdje možemo spremati sesije:
file - sesije se spremaju u storage/framework/sessions kao .txt datoteke
coockie - sesije se spremaju u kriptirane kolačiće
database - sesije se spremaju u bazu podataka koju koristi naša aplikacija
memcashed / redis - sesije se spremaju u brzu cashe memoriju
array - sesije se spremaju u nizove a ovaj oblik spremanja se koristi kod testiranja jer sesija nikad ne istekne.
--Ivansusec 16:26, 11. siječnja 2016. (CET)
Najšešći oblik spremanja sesija je u datoteku predviđenu za to. Sesije je moguće kriptirati no datoteka gdje se nalaze sesija nebi smjela biti dostupna nikome tako da u većini slučajeva to nije potrebno. Ukoliko ipak želimo kriptirati sesiju, u config folderu naše aplikacije postoji session.php klasa u kojoj možemo kriptiranje postaviti na true. U istoj datoteci nalaze se i ostale postavke vezane uz sesije poput oblika i načina spremanja sesija.
Nekoliko osnovnih narebi za upravljanje serijama koje su sigurne protiv napada:
Session::put('key', 'value'); /* Dodaje vrijednost sa nekim imenom (ključem) u sesiju *\ Session::get('key'); /* dohvaća vrijednost za određeni ključ *\ Session::all(); /* Dohvaća sve podatke iz sesije *\ Session::has('users'); /* Vrača 1 ako sesija sadrži vrijednost predanu kao paramentar *\ Session::flash('key', 'value'); /* Spremanje vrijednosti u sesiju ali samo za sljedeći zahjev *\
Insecure Direct Object References i Missing function level access control
Iako Laravel nema neki direktan pristup za rješavanja ovih problema postoji nekoliko ugrađenih middleware klasa koji rješavaju osnovne probleme zaštite protiv ovakvih napada te ih je lako modificirati prema našim željama. Authenticate.php i RedirectAutheticated su ugrađene klase koje omogućavaju da se za neku stranicu provjeri da li je korisnik prijavljen na sustav i ako nije preusmjeruje ga na neku drugu stranicu (npr. Početnu).
Naša aplikacija sadrži popis pjesama, željeli bi da samo prijavljen korisnik može izvršavati kreiranje i modificiranje pjesama. Koristit ćemo Authenticate.php klasu jer se u njoj nalazi metoda koja nam treba:
class Authenticate { ... public function handle($request, Closure $next) { if ($this->auth->guest()) { if ($request->ajax()) { return response('Unauthorized.', 401); } else { return redirect()->guest('auth/login'); } } return $next($request); } }
Metoda vraća korisnika na stranicu za prijavu ili mu javlja grešku ukoliko se radi o ajax zahtjevu. Da bi implementirali zaštitu za gore navedene stranice potrebno je pozvati middleware unutar "Controller"-a koji se brine o stranicama za pregled, uređivanje itd. naših pjesama. Sve što trebamo napraviti je unutar konstruktora "Controller" klase definirati za koje stranice se poziva gore napisana metoda handle (metoda se ne poziva direktno ali o tome kasnije) $this->middleware('auth',['only' => 'create']);
. Iz koda je jasno vidljivo da je autentifikacija potrebna samo za stranicu koju vraća metoda create (a ta metoda naravno vraća stranicu za kreiranje novih pjesama). Na isti način možemo umjesto parametra 'only' staviti i parametar 'except' te će onda middleware zabraniti pristup korisnicima koji se nisu prijavili na sve stranice osim navedene u nizu.
Ono na što se zapravo odnosi ovaj napad je sljedeće. Pregled detalja o pojedinoj pjesmi vrši se preko stranice http://localhost:8000/articles/2. Broj 2 na kraju poveznice označava identifikator pojedine pjesme. Što ako korisnik pokuša ručno promijeniti tu vrijednost u recimo vrijednost 3, http://localhost:8000/articles/3. Ukoliko nemamo neki mehanizam zaštite pokazati će mu se pjesma sa indentifikatorom 3 iako ju on nije kreirao te ne bi smio imati pristup istoj. Taj problem je moguće riješiti na više načina a jedan je da napravimo middleware koji će pri svakoj posjeti stranice provjeriti je li korisnik autor te pjesme. Pretpostavimo da naš Eloquent Model za korisnika sadrži metodu getAllArticles() koja vraća sve artikle čiji je vlasnik neki korisnik.
class AreYouAllowed { public function handle($request, Closure $next) { if ( !in_array($request->route('articles')->id,$request->user()->getAllArticles()->id)) ) { return redirect('index'); } return $next($request); } }
Security Misconfiguration
Na administratoru sustava je da se brine da je sav software uvijek na najnovijoj verziji (ili onoj koju sustav maksimalno podržava). što se tiče popisa datoteka u aplikaciji i default računa potrebno je pravilo podesiti .httacess datoteku ali i ona dolazi u Laravel paketu sa svim potrebnim zaštitama. Jedino što trebamo napraviti je onemogućiti korisniku da vidi pogreške ukoliko dođe do njih. Unutar Laravel projekta postoji .env datoteka koja predstavlja postavke okruženja. Iako ona sadrži određene postavke za bazu, cashe itd. to nam trenutačno nije bitno nego samo dvije linije unutar te datoteke.
APP_ENV=local APP_DEBUG=true /* Ukoliko radimo lokalno te želimo da nam budu prikazane greške */ APP_ENV=release APP_DEBUG=false /* Ukoliko se aplikacija nalazi na serveru te ne želimo da korisnik vidi detalje o greškama */
Cross-Site Request Forgery (CSRF)
Obrana protiv ovakvog oblika napada je vrlo lagana i automatizirana u Laravelu. Laravel automatski generira CSRF "Token" za svaku aktivnu sesiju korisnika. Taj token se koristi za verificiranje da je autentificirani korisnik taj koji radi određene zahtjeve prema aplikaciji. Kada koristimo forme unutar naše aplikacije potrebno je postaviti tajno polje sa CSRF token-om tako da bi već ugrađeni CRSF middleware mogao obaviti validaciju zahtjeva.
<input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">
Nije potrebo ručno verificirati CSRF token za različite vrste POST, PUT ili DELETE zahtjeva, CRSF middleware će automatski to obaviti za nas. Postoji i još jedva vrsta tokena: X-CSRF a on se upotrebljava kada koristimo ajax kod unutar naše aplikacije. Naš middleware za verificiranje XCRF tokena će također verificirati i X-CSRF token. Kako bi spremili X-CSRF token možemo ga primjerice staviti unutar "meta" oznake naše stranice
<meta name="csrf-token" content="{{ csrf_token() }}">
Kada smo stavili X-CSRF token u našu stranicu možemo ga pomoću jquery naredbe dodati u sve headere:
$.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } });
--Ivansusec 21:04, 11. siječnja 2016. (CET)
Zaključak
Postoji još nekoliko vrsta napada i problema sa sigurnošću naše aplikacije ali nisu previše povezane sa samom Laravel aplikacijom pa ih nema potrebne posebno obrađivati. Sve najpoznatije vrste napada na PHP aplikaciju vrlo se lako mogu riješiti koristeći Laravel uz prethodno navedene načine i metode te uz proučavanje Laravel dokumentacije, posebice "Middleware" klasa jer se u njima lako definira vlastita zaštita za različite vrste napada i procesa unutar aplikacije.
Dodatne informacije
Middleware
S obzirom da sam puno puta spomenuo ovaj pojam kroz objašnjavanja raznih vrsta napada mislim da bi bilo potrebno objasniti ga detaljnije te kako ga implementirati. "Middleware" je mehanizam filtracije korisničkih zahtjeva kroz naš sustav. Preko njega definiramo da li će određeni zahtjev biti dopušten, da li ćemo ga dodatno oblikovati i da li ćemo korisniku dopustiti pristup određenom resursu. Laravel dolazi sa desetak već definirane "Middleware" klase. Sve "Middleware" klase nalaze se unutar app/Middlweare
direktorija. Kreiranje nove middlware klase moguće je vrlo lako uz korištenje "Composer"-a koji omogućuje jednostavnije kreiranje određenih klasa, baratanje bazama, projektima itd. , to je složeniji mehanizam koji se ne može opisati u par rečenica. Nakon što kreiramo middleware klasu prije nego ju možemo koristiti moramo ju referencirat u app/Kernel.php
datoteci. Postoji više vrsta "Middlware"-a te su one podijeljene u nekoliko različitih nizova. Npr. možemo vidjeti kako se referencira Authenticate.php "Middleware" koji smo koristiti u primjerima:
protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, ];
Poželjno je dati naziv (svojevrsni ključ) prilikom referenciranja klase pa je tako u našem slučaju dodijeljen naziv 'auth'.
Recimo da unutar naše aplikacije imamo neku stranicu koju ne želimo da posjeti neprijavljeni korisnik jedna od načina je da unutar "Routes" dodamo "Middleware" na sljedeći način:
Route::get('administracija', [middleware => 'auth', function() { return 'You can view this page only if you are signed in'; }]);
Sada svaki put kada korisnik pokuša napraviti get zahtjev na stranicu administacija middleware će provjeriti da li je korisnik prijavljen.
Drugi način da unutar nekog od "Controllera" pozovemo middleware ili unutar konstruktura (kao u primjeru korištenom u prethodnom dijelu o napadima na aplikaciju) ili eventualno unutar pojedinih metoda klase. Ako pak želimo da koristimo middleware globalno na čitavoj aplikaciji definiramo ga unutar app/Kernel.php
ali ovaj puta unutar niza $middlware = [ ... ]
.
Maintance mode
Jedna korisna dodatna informacija je da ukoliko nam se desi da nam neko uđe u sustav ili primijetimo napad na naš sustav možemo otići u "Maintance mode" i tako onemogućiti korisniku da bilo šta radi na našem sustavu tako dugo dok opet ne vratimo aplikaciju. Prednost ovoga je što ne moramo prekinuti ili blokirati server te će nam korisniku biti ispisano da je sustav u stanju "Maintace". Sljedeće dvije linux naredbe služe nam za to:
php artisan down /* aplikaciju stavljamo u maintace mode */ php artisan up /* aplikaciju vraćamo u normalno stanje */
--Ivansusec 22:21, 11. siječnja 2016. (CET)
Testiranje aplikacije
Da bi provjerio da li je moja aplikacija sigurna napravio sam testiranje sa OWASP ZAP-om. OWASP ZAP je aplikacija koja omogućuje penetracijske testove te ukazuje na probleme u našim aplikacijama. Želio sam automatski izlistat sve datoteke unutar aplikacije sa Spider-om ali sam uspio doći samo do onih koje su dostupne svima. Na moje začuđenje iako sam ZAP je pronašao dosta problema unutar moje aplikacije kao što se može vidjeti na sljedećoj slici.
Jedan od problema je bio da CSRF token koji je zapravo Cookie ne postavlja HttpOnly flag. To se može popraviti tako da unutar "VerifyCsrfToken" Middleware-a promjeno stvaranje coockie na sljedeći način:
protected function addCookieToResponse($request, $response) { $config = config('session'); $response->headers->setCookie( new Cookie( 'XSRF-TOKEN', $request->session()->token(), time() + 60 * 120, $config['path'], $config['domain'], false, true ) ); return $response; }
Ono što je bitno je da zadnji argument u Cookie funckiji mora biti postavljen na true.
Ostali problemi većinom su vezani za nedostatak headera. Malim istraživanjem otkrio sam da ti problemi su većinom samo kod starih verzija preglednika ali ipak dodatna zaštita je poželjna. Headere možemo dodati na 3 načina:
1. Unutar svake pojedine php stranice
header('X-Frame-Options','deny'); header('X-XSS-Protection','1'); header('X-Content-Type-Options','nosniff');
2. Napraviti posebnu Middleware klasu koja će automatski dodavati headere
3. Dodavanjem headera na odgovor unutar Controller klase.
$response->withHeaders([ 'X-Frame-Options' => 'deny', 'X-XSS-Protection' => '1', 'X-Content-Type-Options' => 'nosniff' ]);
--Ivansusec 19:10, 17. siječnja 2016. (CET)
Literatura
1. Zend Framework službena stranica - http://framework.zend.com/
2. Building secure apps using ZF2 - https://www.zend.com/topics/Building-secure-app-using-ZF2.pdf
3. OWASP Top 10 - https://owasp.org/index.php/Top_10_2013-Top_10
4. Zend tutorial - https://github.com/bigemployee/Big-Sticky-Notes
5. Secure Application Development with the Zend Framework - http://static.zend.com/topics/Webinar-Zend-Secure-Application-Development-with-the-Zend-Framework.pdf
6. ZF 2.1.0, new security features and more - http://www.zimuel.it/zf-2-1-0/
7. Clickjacking - https://www.owasp.org/index.php/Clickjacking_Defense_Cheat_Sheet
8. XSS Filter - https://blogs.msdn.microsoft.com/ie/2008/07/02/ie8-security-part-iv-the-xss-filter/
9. X-Content-Type-Options - https://blogs.msdn.microsoft.com/ie/2008/09/02/ie8-security-part-vi-beta-2-update/
10. OWASP ZAP - https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project
11. Stack Overflow - http://stackoverflow.com/
12. Laravel 5 XSS Middleware - http://laravel-tricks.com/tricks/laravel-5-xss-middleware
13. Laravel Framework službena stranica - https://laravel.com
14. Raw Queries in Laravel - http://fideloper.com/laravel-raw-queries
15. Laracast - https://laracasts.com
16. Larvel book - http://www.easylaravelbook.com/blog/2015/07/22/how-laravel-5-prevents-sql-injection-cross-site-request-forgery-and-cross-site-scripting/
17. Laravel.IO forum - http://laravel.io/forum/
Članovi:
- Ivan Sušec
- Domagoj Kopić