PHP seadistamine

From EIK wiki

PHP seadistamine

Autor

Elar Lang
AK32

Sissejuhatus

Acunitex nimelise turbega tegeleva firma andmetel (http://www.acunetix.com/blog/web-security-zone/articles/statistics-from-the-top-1000000-websites/) on 2010 aasta alguse seisuga 68% veebiserverites Apache ning 70% veebiserverites PHP.

Serverite hooldamise ja PHP rakenduste kirjutamise vahele jääb oluline osa - PHP seadistamine ja rakendusele õigete õiguste määramine. Kuna arendaja võib pidada seda administraatori tööks ning administraator omakorda arendaja tööks, siis võib selle töö tegemise (st tegemata jätmise) juures tekkida oluline turvarisk.

Antud artikli eesmärk on juhtida tähelepanu olulisematele nüanssidele PHP seadistuse juures.

PHP seadistamine - mida jälgida

.. teise nurga alt vaadatuna - mis on enamlevinud ämbrid PHP veebikeskkonna seadistamisel?

Veateated

Kuvamine

Toodangukeskkonnas ei tohi mitte mingil juhul kuvada välja tarkvara poolt tekkinud vigade veateateid. See annab ründajale olulist infot rakenduse ülesehituse kohta ning lihtsustab rünnaku teostamist. Arenduskeskkonnas (ja ka test keskkonnas) peaks aga veateateid kuvama.

php manual: http://ee.php.net/manual/en/errorfunc.configuration.php#ini.display-errors

php.ini (vaikeväärtus):

display_errors = 1

php.ini (võiks olla):

display_errors = 0

ini_set (seade määramine jooksvalt php koodis):

ini_set('display_errors', 0); // hide errors
//ini_set('display_errors', 1); // show errors

See ei kuulu küll otseselt PHP seadistamise juurde, kuid reegel peaks ka olema, et programmikood ise ei kuva veateateid välja - näiteks, kui tekib viga SQL päringu teostamisel, siis ei tohi seda mitte mingil juhul toodangu keskkonnas välja kuvada (lihtsustab näiteks SQL injection rünnakute teostamist).

Logimine

Kõik tekkinud vead tuleb kindlasti logida, et vajadusel otsida tekkinud vigade põhjuseid (ja põhjustajaid). Veateadete logi tuleks ka jälgida, mitte lihtsalt logida.

php manual: http://ee.php.net/manual/en/errorfunc.configuration.php#ini.log-errors

php.ini (vaikeväärtus):

log_errors = 0

php.ini (võiks olla):

log_errors = 1

Detailsus

Veateateid tuleks tuvastada võimalikult suure detailsusega. Kindlasti mitte ignoreerida E_NOTICE taseme vigu, sest ka need näitavad välja programmi koodi nõrkuseid või vigu (koodis tekib situatsioon, mida arendaja pole ette näinud).

php manual: http://ee.php.net/manual/en/errorfunc.configuration.php#ini.error-reporting Eeldefineeritud detailsuse konstandid: http://ee.php.net/manual/en/errorfunc.constants.php

php.ini (vaikeväärtus):

error_reporting = E_ALL

E_ALL ei sisaldanud E_WARNING teateid kuni PHP versioonini 5.2. Alates PHP versioonist 5.3 E_ALL raporteerib kõiki teateid peale E_STRICT teadete. Alates PHP versioonist 6 on E_ALL teadete hulgas ka E_STRICT teated. Tasub jälgida PHP dokumentatsiooni. "error_reporting" väärtust saab määrata ka numbrina, igal konstandil on oma numbriline väärtus. Kuid kuna see number võib erinevate PHP versioonide puhul tähendada erinevaid asju (nt E_ALL puhul), siis on turvalisem kasutada selleks ikkagi nimelisi konstante. Samas ei ole eeldefineeritud konstandid kasutatavad kui määrata php seade väärtust Apache httpd.conf või .htaccess vahendusel.

php.ini (võiks olla):

error_reporting = E_ALL ~ E_STRICT

ini_set (seade määramine jooksvalt php koodis):

error_reporting(E_ALL ~ E_STRICT);

Failide halduse ja üles laadimise seadistus

open_basedir

open_basedir parameeter määrab, millistest kataloogidest võib rakendus failisid avada. Vaikimisi on väärtus määratama, mis tähendab, et rakendus saab faile avada tervest serverist (kui failide õigustega seda ei ole piiratud). Selle parameetri puudumine annab võimaluste teostada LFI (Local File Include) ründeid kogu failisüsteemi ulatuses.

php manual: http://ee.php.net/manual/en/ini.core.php#ini.open-basedir

php.ini (vaikeväärtus):

open_basedir = 

open_basedir vaikeväärtus PHP's puudub, st PHP poolt mingeid piiranguid ei ole (loevad vaid failiõigustest tulenevad piirangud).

php.ini (võiks olla):

open_basedir = /path/to/application/root/

Väga mugav oleks määrata open_basedir väärtuseks "." (ehk siis see sama kataloog, kus skript käivitatakse). See on aga ohtlik, kuna PHP chdir käsuga saab "aktiivse kataloogi" ja seega ka väärtuse "." tähendust muuta. Jälgida tuleks väärtuse (või väärtuste) määramisel, et vajalikele kataloogidele ligipääs alles jääks (nt kui ei ole muudetud upload_tmp_dir väärtust, siis lisada open_basedir väärtusele ka /tmp kataloog).

Saab määrata ka httpd.conf failis (ja ka .htaccess failis):

<IfModule mod_php5.c>
  php_admin_value open_basedir /path/to/application/root/
</IfModule>

NB! määrates open_basedir piirangu httpd confi kaudu, ei rakendu see siis kui php käivitada käsurealt (ehk siis kui rakendus ise on juba turvaauguga ja saab tekitada käivitatava php koodi rakenduse alla, siis saab sellest piirangust mööda).

allow_url_fopen

Selle parameetriga juhitakse, kas failide avamise funktsioonidel on õigus avada fail üle urli. Kui rakendus sellist võimalust ei vaja, peaks selline võimalus olema keelatud (et välistada RFI (Remote File Include) rünnakuid).

php manual:http://ee.php.net/manual/en/filesystem.configuration.php#ini.allow-url-fopen

php.ini (vaikeväärtus):

allow_url_fopen = 1

Vaikeväärtusena on üle urli failide avamine lubatud.

php.ini (võiks olla, kui rakendus ei vaja failide avamist üle urli):

allow_url_fopen = 0

file_uploads

Parameetriga juhitakse, kas HTTP failide üles laadimine on toetatud. Kui rakenduse funktsionaalsus seda võimalust ei vaja, peaks see olema keelatud.

php manual: http://ee.php.net/manual/en/ini.core.php#ini.file-uploads

php.ini (vaikeväärtus):

allow_url_fopen = 1

Vaikeväärtusena on üle urli failide avamine lubatud.

php.ini (võiks olla, kui rakendus ei vaja failide üles laadimist):

allow_url_fopen = 0

upload_tmp_dir

Asukoht üles laetavate failide ajutiseks talletamiseks, kuni PHP programm nendega tegeleb. Pärast pöördumist tmp kataloogist failid kustutatakse.

php manual: http://ee.php.net/manual/en/ini.core.php#ini.file-uploads

php.ini (vaikeväärtus):

upload_tmp_dir = 

Vaikeväärtusena on määramata, kasutatakse süsteemi vaikeväärtust (mis linuxi puhul on /tmp kataloog)

php.ini (võiks olla, kui rakendus ei vaja):

upload_tmp_dir = /path/to/application/root/tmp

upload_tmp_dir parameetri väärtus ei tohiks kindlasti olla avalikku veebi paistev kataloog (st ei tohi olla "document_root" alamkataloog). Vaikeväärtus võiks olla muudetud, kuna failide üleslaadimine on ka üks DoS (Denial of Service) ründemeetodeid. Koormatakse veebiserverit lõputute failide üleslaadimistega, veebiserver tekitab iga faili jaoks "upload_tmp_dir" parameetriga kataloogi ajutisi php* nimelisi faile. Kui DoS rünnaku peale veebiserver hangus, siis võib tekkida olukord, kus tmp kataloogi jäävad need üles laetud ajutised failid alles. Samal ajal LFI (Local File Include) ründevõimalust omades saab ründaja niimoodi sokutada endale vajaliku koodi veebiserverisse.

upload_max_filesize

Maksimaalne ühe üles laetava faili suurus.

php manual: http://ee.php.net/manual/en/ini.core.php#ini.upload-max-filesize

php.ini (vaikeväärtus):

upload_max_filesize = 2M

php.ini (võiks olla, vastavalt rakenduse vajadustele):

upload_tmp_dir = 2M

Väärtus peaks olema valitud rakenduse iseloomust (kui suuri faile ei oodata, ei ole seda hea ka seadistuses lubada).

max_file_uploads

Maksimaalne ühe päringuga üles laetavate failide arv.

php manual: http://ee.php.net/manual/en/ini.core.php#ini.max-file-uploads

php.ini (vaikeväärtus):

max_file_uploads = 20

php.ini (võiks olla, vastavalt rakenduse vajadustele):

max_file_uploads = 1

Väärtus peaks olema valitud rakenduse iseloomust (kui mitme faili korraga üles laadimist ei oodata, siis ei ole seda hea ka seadistuses lubada).

Muud limiidid

post_max_size

Maksimaalne postituse suurus. See ei ole nüüd otseselt failide üles laadimise parameeter, kuid üldjuhul rakendub see piirang just failide üles laadimisele. Valitud väärtus ei tohiks olla põhjendamatult suur. Kui file_uploads parameetriga on failide üles laadimine välja lülitatud, siis lihtsalt vormide täitmise kaudu eriti mahukaid postitusi lubama ei peaks.

php manual: http://ee.php.net/manual/en/ini.core.php#ini.post-max-size

php.ini (vaikeväärtus):

post_max_size = 8M

php.ini (võiks olla vastavuses rakenduse reaalsete vajadustega):

post_max_size = ?

Kui kasutatakse failide üles laadimist, siis peaks post_max_size olema suurem kui "upload_max_filesize * max_file_uploads"

memory_limit

Määrab kui palju on võimalik PHP rakendusel maksimaalselt mälu kasutada. Väärtus peaks olema sõltuvuses rakenduse reaalsete vajadustega. Kehvale rakenduse koodile mälu juurde andmine ei ole lahendus (pikas perspektiivis), sel juhul tuleks kood ringi kirjutada.

php manual: http://ee.php.net/manual/en/ini.core.php#ini.memory-limit

php.ini (vaikeväärtus):

memory_limit = 128M

php.ini (võiks olla vastavuses rakenduse reaalsete vajadustega):

memory_limit = ?

Üldjuhul on 128M pisikese PHP rakenduse jaoks väga palju (32MB'st peaks varuga piisama). Kui rakendus juhtub auklik olema ning ründaja suudab oma koodijupi serverisse toimetada, siis on ka temal korralikult ressursse käes mida kasutada.

max_execution_time

Määrab maksimaalse PHP skripti töötamise aja. Kui PHP rakendus ei tegele mahukate väljundandmete genereerimisega, siis võiks väärtus olla üsna väike. Juhul kui kasutatakse ka käsurealt PHP'd, siis seal võiks olla see piirang maas (tavaliselt käsurealt käivitatavad rakendused on cronjobid, mis tegelevad näiteks mahukate andmetöötlusprotsessidega, andmete importimisega jne).

php manual: http://ee.php.net/manual/en/info.configuration.php#ini.max-execution-time

php.ini (vaikeväärtus, sekundites):

max_execution_time = 30

Vaikeväärtus käsurealt käivitades on 0.

php.ini (võiks olla vastavuses rakenduse reaalsete vajadustega):

max_execution_time = 5

Kuna max_execution_time parameetri väärtust saab määrata ka jooksvalt ini_set funktsiooni kasutades, võiks algne väärtus olla võimalikult väike. Lisaks võiks kasutada vigade ilmnemiseks arendus- ja testkeskkondades oluliselt väiksemat väärtust kui toodangukeskkonnast.

runtime (seade määramine jooksvalt php koodis):

set_time_limit(0); // piirangut ei ole
set_time_limit(10); // piirang on 10 sekundit

Jooksvalt võib muuta ajapiirangu suuremaks kui "streamitakse" kasutajale mingeid andmeid PHP'ga (sest sel juhul sõltub see kiirus ka kasutaja interneti ühenduse kiirusest - suutlikusest talle serveeritavat infot vastu võtta).

Aeguvad parameetrid

NB! Selles peatükis käsitletavad parameetrid on kuulutatud aegunuks (kasutamine annab DEPRECATED vea) alates PHP 5.3'st ning need seaded kaovad ära alates PHP 6'st.

safe_mode

"Sade mode" kujutab endast vaikimsi mitte muudetavaid turvalisusele suunatud seadeid. Safe mode parameetri peale panemine (väärtus 1) ei pruugi alati olla "safe", see tuleks ikkagi eelnevalt läbi testida.

php manual: http://ee.php.net/manual/en/ini.sect.safe-mode.php#ini.safe-mode

php.ini (vaikeväärtus):

safe_mode = 0

php.ini (võiks olla):

safe_mode = 0

magic_quotes_gpc

Parameetriga juhitakse, kas automaatselt "escapeda" kasutaja sisendandmeid "backslashidega" (\). Automaatsed tegevused kasutaja andmetega ei ole head. Lisaks tähendab selle parameetri olemasolu, et kogu programmikood peab arvestama serveri seadetega (et vältida topelt escapemist või escapemise puudumist).

Kuna see parameeter on kuulutatud aegunuks ning kaob ära PHP 6'st, on irooniline, et just magic_quotes_gps on ravim vanade katkiste lehtede juures NULL-Byte rünnakute vastu.

php manual: http://ee.php.net/manual/en/info.configuration.php#ini.magic-quotes-gpc

php.ini (vaikeväärtus):

magic_quotes_gpc = 1

php.ini (võiks olla):

magic_quotes_gpc = 0

Rakenduse kood peab ise tegelema sisendandmete valideerimise ja kontrolliga.

register_globals

Julgen väita, et selle parameetri ja võimaluse olemasolu on rikkunud kõige enam PHP mainet (õnneks on sellest ka PHP loojad ise teinud omad järedlused). See laseb olla arendajal laisk ja lohakas ning kirjutada kohutavalt ebaturvalisi rakendusi.

php manual: http://ee.php.net/manual/en/ini.core.php#ini.register-globals

register_globals parameeter annab arendajale võimaluse kasutada Environment, GET, POST, Cookie ja SERVER muutujaid nagu tavalisi muutujaid. Väga lihtne näide (reaalselt eksisteeriv ühes Eesti firma poolt loodud CMS süsteemis) - kui kasutaja logib sisse admin keskkonda, tekitatakse sessioon võtmega "logged_in" (võtme nimi igaks juhuks muudetud). Vastava sessioni olemasolu korrektne kontroll oleks:

if (isset($_SESSION, $_SESSION['logged_in']) && $_SESSION['logged_in'] == 1) {
	// käivitada logimiskontrolli all olev kood siin
}

Oli aga tehtud nii:

if ($logged_in == 1) {
	// käivitada logimiskontrolli all olev kood siin
}

Sellise kontrolli puhul piisab, kui pöörduda administreerimiskeskkonna poole GET päringuga /admin/?logged_in=1 ning ligipääsu kontrollist ollaksegi möödas.

php.ini (vaikeväärtus):

register_globals = 0

Väga vanadel PHP'del (varasemad kui PHP 4.2) oli vaikeväärtuseks 1.

php.ini (võiks olla):

register_globals = 0

Sessioonide haldus

Sessioon on selleks, et hoida kasutaja külastuse kohta andmeid serveril. Sessioonis ei tohiks kindlasti hoida kriitilise tähtsusega andmeid (nagu näiteks lahtise tekstina kasutaja parool).

Antud parameetreid lahates eeldan, et parameetri session.save_handler väärtuseks on vaikimisi väärtus "files" (ehk siis ei ole kirjutatud mõnda oma sessioonide manageerijat).

session.autostart

Määrab, kas kasutaja külastuse kohta käivitada automaatselt sessioon. Kui rakenduse iseloom ei vaja kasutaja identifitseerimist (suvaline koduleht), siis ole mõtet ka serverit koormata selle külastuse kohta sessiooni loomisega. Vaikimisi tehakse iga sessiooni jaoks /tmp/ (vt session.save_path parameetrit) ka fail. Ebaturvalise rakenduse korral võib sessiooni fail olla ka LFI2RFE (Local File Include to Remote Code Execution) rünnaku teostamise koht.

php manual: http://ee.php.net/manual/en/session.configuration.php#ini.session.auto-start

php.ini (vaikeväärtus):

session.auto_start = 0

php.ini (võiks olla):

session.auto_start = 0

Käivitada vajadusel sessioon rakenduse koodis session_start funktsiooniga.

session.cookie_httponly

Selle parameetriga juhitakse, kas HTML's (nt JavaScripti poolt) on sessiooni cookie kättesaadav. Saab raskendada XSS (Cross-Site-Scripting) rünnakute ja cookie'de varastamise rünnakuid. See võimalus tekkis PHP'sse alates versioonist 5.2.

php manual: http://ee.php.net/manual/en/session.configuration.php#ini.session.cookie-httponly

php.ini (vaikeväärtus):

session.cookie_httponly = 0

php.ini (võiks olla):

session.cookie_httponly = 1

session.hash_function

hash_function määrab, millist algoritmi krüpteerimiseks kasutada. Väärtus 0 tähendab MD5 (128 bitti) ja väärtus 1 SHA-1 (160 bitti) algoritmi.

php manual: http://ee.php.net/manual/en/session.configuration.php#ini.session.hash-function

php.ini (vaikeväärtus):

session.hash_function = 0

php.ini (võiks olla):

session.hash_function = 1

Kuna MD5 loetakse tänapäeval juba ebaturvaliseks algoritmiks, siis võiks kasutada väärtust 1 ehk siis algoritmi SHA-1.

session.name

Määrab ära, millist sessiooni identifikaatorit kasutada (cookie nimetus). Et mitte alluda automaatsetele XSS cookie varastamise rünnetele, võiks vaikimi nimetuse siiski ära muuta.

php manual: http://ee.php.net/manual/en/session.configuration.php#ini.session.name

php.ini (vaikeväärtus):

session.name = PHPSESSID

php.ini (võiks olla):

session.name = MyApp-Sid

Väärtuseks lihtsalt midagi muud kui vaikeväärtus.

session.save_path

save_path määrab kataloogi, kus hoitakse sessiooni failisid. Vaikeväärtuseks on süsteemi "temporary" kataloog, kuhu võib ka teistel kasutajatel olla "listingu" ja lugemise õigused, st et on võimalik teostada "session hijacking" rünnakuid.

php manual: http://ee.php.net/manual/en/session.configuration.php#ini.session.save-path

session.save_path = 

Määramata väärtus tähendab, et kasutada süsteemi vaikimisi väärtust (Linuxi puhul /tmp, Windowsi puhul nt C:\WINDOWS\Temp\)

php.ini (võiks olla):

session.name = /path/to/application/root/session/

Väärtus on võimalik määrata ka scriptis:

session_save_path('/path/to/application/root/session/')

php manual: http://ee.php.net/manual/en/function.session-save-path.php

session.use_only_cookies

Määrab, et sessiooni identifikaatori hoidmine on lubatud ainult küpsistes. Kui kasutaja lehitsejal küpsised ei ole lubatud, siis sessiooni tekitada ei saa. Vastasel juhul hakatakse sessiooni id'd kaasa vedama URL parameetrina, nt PHPSESSID=hash. See aga on oluline turvarisk ning võimaldab väga lihtsalt kasutaja sessiooni varastada (session hijacking rünnak). Piisab sellest, kui kasutaja klikib antud lehel mõnd viidet teisele lehele. Teisele lehele minnes kirjutatakse sinna logidesse (nt külastuste) REFERER, ehk siis mis lehelt kasutaja tuli ja kus oli viide. Viites on aga kaasas tema sessiooni id. Kui keegi saab täpse referer aadressi teada ning läheb sellele, saabki ta omale selle kasutaja sessiooni.

php manual: http://ee.php.net/manual/en/session.configuration.php#ini.session.use-only-cookies

php.ini (vaikeväärtus):

session.use_only_cookies = 0

php.ini (vaikeväärtus alates PHP 5.3'st)

session.use_only_cookies = 1

php.ini (võiks olla):

session.use_only_cookies = 1

Muud parameetrid

short_open_tag

short_open_tag parameeter määrab, kas PHP koodi võib alustada süntaksiga "<?" või "<?php". Kui rakenduse koodis on alustatud failisid, mille poole pöördutakse "<?" süntaksiga ning parameeter short_open_tag on väärtusega 0, siis kuvatakse pöördujale antud faili PHP lähtekood. Selle parameetri muutmiseks peab veenduma, et PHP kood on ka vastavalt kirjutatud. Mõistlik on kasutada selleks mingeid automaatkontrolle, nt CodeSniffer PEAR packaget (http://pear.php.net/package/PHP_CodeSniffer/redirected).

php manual: http://ee.php.net/manual/en/ini.core.php#ini.short-open-tag

php.ini (vaikeväärtus)

short_open_tag = 1

php.ini (võiks olla):

short_open_tag = 1

Muud olulised seadistused

Failide õigused

Serveri logid

PHP rakendusel ei tohiks olla serveri logide lugemise õigust. Juhul kui rakenduse koodis on viga/turvauk LFI (Local File Include) teostamiseks, on vaja ründajal saada serverisse oma koodi LFI2RCE (Local File Include to Remote Code Execution) rünnaku teostamiseks. PHP koodi saab sokutada ka serveri logidesse, nt Apache logisse võltsides lehitseja USER_AGENT parameetrit (või saata see parameeter curl'i kasutades).

Üles laetavad failid

Kataloogidele, kuhu salvestatakse kasutajate poolt üles laetud faile, ei peaks andma failide käivitamise ja kataloogide listingu õigust (x). Samuti tuleb välistada, et kasutaja saaks üles laadida .htaccess failisid.

Avalike kataloogide failide nimekirjad

Docroot alamkataloogides olevatele failidele tuleks keelata kataloogide sisu näitamine (kui see just ei ole taotluslik). Tihti asuvad strateegilised rakenduse failid avalikus kataloogis (kuigi ei tohiks!) ning see annab ründajale olulist infot rakenduse ülesehituse kohta. Keelata saab seda nii failiõiguste tasemel kui ka Apache seadetega. Veebikülastajate eest peitmiseks piisab kui lisada igasse kataloogi näiteks tühi index.html või index.php nimeline fail.

Viirusetõrje

Kuna viiruste levitamine kolib ka veebi, siis on igati asjakohane rakendada regulaarset viirusekontrolli veebirakenduse kataloogi sisule.

Serveri info

Tuleks peita kõik serveri versioone reklaamiv info (nt annab vaikimisi Apache errorleht teada, mis versiooniga on tegu). Kui peaks ilmnema mingi oluline turvaauk, siis ei oleks vähemalt selline info automaatselt tuvastatav (mingite infot koguvate bottide poolt, mis teevad päringuid /thisfiledoesnotexists.php ning saavad versioonid teada).

Kokkuvõte

PHP seadistamisel on palju nüansse, mis vajavad, et arendajad ja administraatorid suhtleks omavahel. Rakenduse loomist alustades peaksid olema kõik seaded määratud "nii on kõige turvalisem" seisukoha järgi. Kui rakendus tõesti vajab oma funktsionaalsuse vajaduste tõttu teisiti, siis saab tagasi muuta.

Väga abiks oleks luua skript, mis kontrollib serveri seadete väärtuseid ja serverilt oodatud seadete väärtuseid. Võib muutuda rakenduse iseloom ja vajadused, tehakse serverisse tarkvara uuendusi jne - sellise kontrolliskripti vastu oleks alati kiiresti ja tõhus kontrollida.