PHP seadistamine
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.