Gli errori di configurazione di PHP, tipicamente in ambiente VPS o dedicato, sono comuni quanto pericolosi per la stabilità del sito, specialmente se non si dispone di un sistemista di professione: in questo tutorial vedremo come mettere in sicurezza il server dai più comuni attacchi informatici. Per intervenire sulla macchina remota, sarà necessario anteporre a qualsiasi linea di comando l’istruzione ssh, che permetterà di accedere in modalità sicura ai comandi della VPS (o del server) su cui disporremo di un’utenza, di una password da inserire su richiesta e di un host di riferimento. Per maggiori informazioni, ed esempi basilari a riguardo, si veda la guida su come utilizzare ssh.
Per i nostri esempi assumeremo quanto segue in termini di locazione dei file e loro rispettivi nomi.
- DocumentRoot: /var/www/html
- Web server di default: Apache
- file di configurazione PHP di default: /etc/php.ini
- directory contenente le estensioni PHP di default: /etc/php.d/
- file di inizializzazione per la sicurezza: /etc/php.d/security.ini (nota: deve essere creato mediante un editor di testo)
- sistemi operativi: RHEL / CentOS / Fedora Linux (le istruzioni riportate funzioneranno con qualsiasi distribuzione di Linux come Debian / Ubuntu, così come qualsiasi altro sistema Unix-based quale OpenBSD/FreeBSD/HP-UX).
- porte TCP/UDP di default: nessuna
Come ulteriore vincolo aggiungeremo di eseguire le operazioni come root user (comando sudo…), in modo da poter accedere liberamente a tutti i comandi di cui abbiamo bisogno.
Conoscere i vari tipi di rischi informatici
Come discusso già sul nostro blog in svariate occasioni, i principali rischi per la sicurezza su un server PHP sono i seguenti.
- XSS – Una delle vulnerabilità più comuni causate da varie misconfigurazioni, si può limitare sostanzialmente validando con attenzione tutte le variabili di input, specie se provenienti da URL o da form di input.
- SQL injection – Si tratta di una vulnerabilità diffusa a livello di database: se le variabili in ingresso non vengono filtrate c’è il rischio che un utente anonimo possa eseguire query arbitrarie in SQL, comprese quelle per visualizzare le utenze oppure cancellare o alterare i dati presenti sul sito. L’escape più comune è l’utilizzo della funzione mysql_real_escape_string().
- Upload di file – Implica la possibilità per un utente esterno malevolo di caricare file arbitrari ed eseguirli sulla macchina: anche qui si può ridurre questa possibilità utilizzando apposite direttive oppure filtrando le variabili di input in maniera opportuna (ad esempio impedendo l’upload di file con estensione PHP).
- Inclusione di file remoti – In questa circostanza si contempla la possibilità per un attaccante di eseguire codice arbitrario, installare backdoor o cancellare file del sito. Si può evitare a livello PHP disabilitandolo in vari modi.
- Funzione eval() – Permette di valutare il contenuto di una stringa arbitraria come se fosse codice PHP: se questo è molto utile da un lato, dall’altro rivela una falla notevole nel momento in cui chiunque possa inserire istruzioni a propria scelta. In certi ambienti PHP-based questa funzione viene disabilitata del tutto.
- Cross-site request forgery: è una vulnerabilità a cui sono esposti i siti web dinamici qualora siano progettati per ricevere richieste da un client senza meccanismi di verifica se la richiesta sia stata inviata intenzionalmente oppure no. Include la possibilità di rubare, modificare e alterare dati sensibili, e si evita in primis facendo in modo di non usare variabili di tipo GET per le pagine che includano un cambiamento di stato per l’utente.
1) Vedere il numero di versione PHP che sta funzionando
Nello specifico basterà aprire una finestra del terminale e digitare:
php -v
che produrrà un output tipo il seguente.
PHP 5.3.3 (cli) (built: Oct 24 2011 08:35:41) Copyright (c) 1997-2010 The PHP Group Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
Fornendo questo tipo di istruzione su un sistema basato su RedHat:
cat /etc/redhat-release
otterremo invece qualcosa del genere, relativa al sistema operativo nello specifico:
Red Hat Enterprise Linux Server release 6.1 (Santiago)
2) Scoprire i moduli PHP attivi
Per eseguire questa operazione daremo il comando:
php -m
che produrrà un output simile:
[PHP Modules] apc bcmath bz2 calendar Core ctype curl date ... zlib [Zend Modules] Suhosin
La raccomandazione fondamentale, in questo caso, è legata al fatto di utilizzare un numero quanto più possibile ridotto di moduli, ovviamente misurandolo sulle effettive esigenze di cui abbiamo bisogno, e senza ricorrere a software superfluo. Ad esempio, se volessimo disattivare sqlite3 dovremmo scrivere:
rm /etc/php.d/sqlite3.ini
che implica la rimozione del file .ini in questione, oppure (in modo equivalente):
mv /etc/php.d/sqlite3.ini /etc/php.d/sqlite3.disable
che ne causa la ridenominazione. Per installare giusto l’indispensabile e compilare da zero PHP sulla propria macchina si può, a questo punto, ricorrere ad un’istruzione sulla falsariga della seguente.
./configure --with-libdir=lib64 --with-gd --with-mysql --prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --sysconfdir=/etc --datadir=/usr/share --includedir=/usr/include --libexecdir=/usr/libexec --localstatedir=/var --sharedstatedir=/usr/com --mandir=/usr/share/man --infodir=/usr/share/info --cache-file=../config.cache --with-config-file-path=/etc --with-config-file-scan-dir=/etc/php.d --enable-fastcgi --enable-force-cgi-redirect
Teniamo conto in questo tipo di approccio, ovviamente, che la rimozione di moduli che servono alla nostra applicazione potrebbero generare errori imprevisti, o malfunzionamenti a vari livelli.
3) Far trapelare meno informazioni possibili
Per ridurre il numero di informazioni disponibili esternamente (e questo è in effetti uno dei cardini di sicurezza di qualunque sistema), possiamo editare il file /etc/php.d/security.ini ed impostare la seguente direttiva al suo interno:
expose_php=Off
Nel momento in cui expose_php è abilitato, di fatto, una semplice curl permetterà ad un attaccante, anche se non loggato, di visualizzare informazioni sulla versione attuale di PHP. Un esempio viene riportato di seguito:
curl -I http://www.miosito.com/index.php
che produrrà in output informazioni preziose per generare un possibile attacco, come ad esempio:
HTTP/1.1 200 OK X-Powered-By: PHP/5.3.3 Content-type: text/html; charset=UTF-8 Vary: Accept-Encoding, Cookie X-Vary-Options: Accept-Encoding;list-contains=gzip,Cookie;string-contains=wikiToken;string-contains=wikiLoggedOut;string-contains=wiki_session Last-Modified: Thu, 03 Nov 2011 22:32:55 GMT ...
4) Minimizzare i moduli PHP da caricare (Dynamic Extensions)
PHP supporta le cosiddette estensioni dinamiche: di default RHEL carica tutto ciò che trova all’interno della cartella /etc/php.d/. Se vogliamo disabilitare un modulo in particolare, non dovremo fare altro che trovare il file di configurazione all’interno della sudddetta cartella e commentare il nome del modulo in questione. In alternativa è possibile anche, come visto prima, rinominare o cancellare il file di configurazione annesso. Ribadiamo ancora una volta come sia importante lasciare attivi solo i moduli utilizzati dall’applicazione stessa: se ad esempio non ci serve il modulo gd (se il nostro sito non lavora sulle immagini a livello server, per intenderci), andiamo a scrivere le tre seguenti istruzioni distinte via terminale:
cd /etc/php.d/ mv gd.{ini,disable} /sbin/service httpd restart
Se ora volessimo abilitare nuovamente il modulo gd, andremo invece a scrivere:
mv gd.{disable,ini} /sbin/service httpd restart
5) Loggare tutte le operazioni internamente, ed evitare di mostrare errori all’utente
Andiamo ad editare il file /etc/php.d/security.ini ed impostiamo la seguente direttiva:
display_errors=Off
Assicuriamoci, inoltre, che tutti gli errori PHP vengano salvati su file:
log_errors=On error_log=/var/log/httpd/php_scripts_error.log
Questo ci permetterà di tenere sotto controllo le applicazioni ed accorgerci se stanno generando errori nel tempo (il file di log, in questo caso, chiaramente crescerà di dimensione).
6) Impedire l’upload di file sul server
Andiamo nuovamente ad editare il file /etc/php.d/security.ini ed impostiamo la seguente direttiva:
file_uploads=Off
Se la nostra applicazione ha bisogno di sfruttare l’upload di file, comunque, siamo costretti ad abilitare quesa feature, ma possiamo inserire un vincolo sulla dimensione massima dei file accettati:
file_uploads=On upload_max_filesize=1M
Per filtrare i file che vengono caricati sul sistema si può intervenire a livello javascript, utilizzando ad esempio le apposite funzioni fornite da librerie come blueimp.
7) Disabilitare l’esecuzione remota di codice
allow_url_fopen permette alle funzioni PHP, come ad esempio include, require e file_get_contents(), di eseguire operazioni potenzialmente illecite. Spesso i programmatori web dimenticano tutto questo e non filtrano adeguatamente le variabili in ingresso, esponendo l’intero sito a possibilità di code injection. Una combinazione tra l’abilitazione di allow_url_fopen e la mancanza di filtri sulle variabili in input causa la maggioranza di questo genere di vulnerabilità su CMS e applicazioni web.
Per disabilitare questo tipo di funzione andremo a scrivere all’interno del file /etc/php.d/security.ini la seguente riga (se già presente allow_url_fopen=On deve essere cambiata come segue):
allow_url_fopen=Off
Per quanto riguarda i rischi legati alle include, si può disabilitare un’ulteriore funzione andando a scrivere:
allow_url_include=Off
8) Abilitare SQL in modalità safe
Editiamo in questo caso /etc/php.d/security.ini ed andiamo a settare la direttiva seguente:
sql.safe_mode=On
Se impostato su on, le funzioni di connessione al database ignorano qualsiasi parametro venga solo passato nel codice, utilizzando solo username e password di default. Questo potrebbe di fatto impedire a CMS come WordPress di funzionare, costringendovi ad effettuare alcune modifiche a livello di configurazione. Si dovrebbe, inoltre, ricorrere a mysql_escape_string() e funzioni di filtraggio customizzate caso per caso per garantire maggiore sicurezza, e fare inoltre in modo di disabilitare le “magic quotes” di PHP in questo modo:
magic_quotes_gpc=Off
9) Controllare la dimensione dei dati inviabili via POST
Editiamo ancora una volta /etc/php.d/security.ini e settiamo la direttiva seguente, andando ad inserire un valore realistico per una POST:
post_max_size=1K
Questo serve ad evitare che un attaccante possa bloccare il server o causarne problemi di spazio su disco inviando file molto grossi. Attenzione che questa impostazione influenza anche la dimensione dei file in upload, per cui il valore indicato in questa sede dovrà essere comunque maggiore di quello impostato per il parametro upload_max_filesize. Si può anche aggiungere un vincolo sui metodi che si possono utilizzare per l’upload editando il file httpd.conf:
<Directory /var/www/html> <LimitExcept GET POST> Order allow,deny </LimitExcept> ... </Directory>
10) Evitare gli attacchi di tipo DoS
Per fare questo andremo ad impostare la massima durata dell’esecuzione di uno script, il tempo massimo di parsing di uno script ed il limite massimo sulla memoria RAM utilizzabile:
max_execution_time = 30 max_input_time = 30 memory_limit = 40M
Tale impostazione può essere inserita all’interno del solito file /etc/php.d/security.ini.
11) Installare il modulo Suhosin
Si tratta di un modulo per la sicurezza molto avanzato che si può installare per proteggere PHP al meglio: per maggiori informazioni si veda la pagina ufficiale.
12) Disabilitare le funzioni PHP rischiose
Come abbiamo già discusso in un post apposito, alcune funzioni PHP sono a maggiore rischio per la sicurezza: per disabilitare, qualora non ci servano, possiamo sfruttare la seguente direttiva nel file /etc/php.d/security.ini.
disable_functions =exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
13) Abilitare il cgi.force_redirect in ambiente Apache+PHP-CGI/FastCGI
Questa direttiva nel file /etc/php.d/security.ini permette di impedire l’esecuzione di script arbitrari lato server, ad esempio http://miosito.it/cartella_hack/script_arbitrario.php:
cgi.force_redirect=On
14) PHP User e Group ID
Se PHP funziona come utente di root, il modulo mod_fastcgi può diventare un rischio per la sicurezza: in questo caso bisogna intervenire nel dettaglio sulla specifica configurazione su cui stiamo lavorando. La procedura è stata descritta nel dettaglio all’interno dell’articolo Differenze tra suPHP, suPHPexec e Apache suEXEC (prima parte), Differenze tra suPHP, suPHPexec e Apache suEXEC (seconda parte), ed in quello dedicato agli handler PHP.
15) Limitare l’accesso di PHP al file system
È inoltre opportuno fare in modo che PHP disponga di accesso limitato ai file: per farlo si può ricorrere alla direttiva seguente. La direttiva open_basedir imposta proprio questo genere di vincolo, ed impedisce ad un utente malintenzionato che riesca ad accedere di “navigare” anche in cartelle di sistema.
open_basedir="/var/www/html/"
16) Verificare il path delle sessioni PHP
Bisogna fare in modo che le sessioni, cioè i file temporanei che vengono scritti dinamicamente al fine di memorizzare informazioni di scambio dati tra le pagine, non siano leggibili dall’esterno: in altri termini tale impostazione viene regolamentata da queste due direttive:
session.save_path="/var/lib/php/session" upload_tmp_dir="/var/lib/php/session"
Il path indicato come valore per le variabili session.save_path e upload_tmp_dir deve essere rigorosamente esterno alla cartella pubblica (nel nostro esempio /var/www/html), e dovremmo anche verificare che non sia leggibile o scrivibile da utenti non autorizzati: ad esempio se scriviamo
ls -Z /var/lib/php/
andremo a listare i file di sessione che possono contenere informazioni riservate e/o manipolabili:
drwxrwx---. root apache system_u:object_r:httpd_var_run_t:s0 session
17) Aggiornare il software periodicamente
Come si fa ad aggiornare la versione di Linux su cui stiamo lavorando? È molto semplice, ed è altrettanto importante farlo per limitare i rischi per la sicurezza. Per effettuare l’update andiamo a scrivere:
yum update
oppure
apt-get update && apt-get upgrade
18) Limitare l’accesso a file e directory
Il comando chown (“cambia proprietario”) modifica il proprietario e/o il gruppo assegnato di uno o più file e directory: nel nostro caso potremmo evitare che Apache non funzioni come utente root bensì come utente del gruppo “apache”.
chown -R apache:apache /var/www/html/
In determinati ambienti, e qualora non sia richiesto dal CMS utilizzato, si può anche pensare di rendere la cartella /var/www/html/ di sola lettura, in modo da evitare l’inserimento di file esterni malevoli:
chmod -R 0444 /var/www/html/
Questa impostazione potrebbe comunque compromettere il funzionamento corretto del vostro sito, per cui verificare attentamente se sia opportuno farlo o meno. Un’alternativa potrebbe essere quella di settare i permessi sulle directory sotto /var/www/html/ al valore 0445:
find /var/www/html/ -type d -print0 | xargs -0 -I {} chmod 0445 {}
Se ad esempio state utilizzando WordPress (che ipotizziamo funzionare nella sottocartella “blog” della root), è necessario concedere dei permessi di scrittura nella cartella di caching, e questo si può fare in questo modo.
chmod a+w /var/www/html/blog/wp-content/cache echo 'deny from all' > /var/www/html/blog/wp-content/cache/.htaccess
19) Proteggere i file di configurazione da eventuali modifiche
Potete utilizzare queste istruzioni da linea di comando:
chattr +i /etc/php.ini chattr +i /etc/php.d/* chattr +i /etc/my.ini chattr +i /etc/httpd/conf/httpd.conf chattr +i /etc/
e questo vale ovviamente anche per i singoli file nella directory /var/www/html/:
chattr +i /var/www/html/file1.php chattr +i /var/www/html/
20) Installare SELinux
SELinux è un’estensione per la sicurezza del sistema operativo molto utile se lo state utilizzando come webserver: per visualizzare un elenco dei moduli abilitati digitate da terminale
getsebool -a | grep httpd
ed otterrete indicazioni dettagliate del tipo:
allow_httpd_anon_write --> off allow_httpd_mod_auth_ntlm_winbind --> off allow_httpd_mod_auth_pam --> off allow_httpd_sys_script_anon_write --> off httpd_builtin_scripting --> on httpd_can_check_spam --> off httpd_can_network_connect --> off httpd_can_network_connect_cobbler --> off httpd_can_network_connect_db --> off httpd_can_network_memcache --> off httpd_can_network_relay --> off httpd_can_sendmail --> off httpd_dbus_avahi --> on httpd_enable_cgi --> on httpd_enable_ftp_server --> off httpd_enable_homedirs --> off httpd_execmem --> off httpd_read_user_content --> off httpd_setrlimit --> off httpd_ssi_exec --> off httpd_tmp_exec --> off httpd_tty_comm --> on httpd_unified --> on httpd_use_cifs --> off httpd_use_gpg --> off httpd_use_nfs --> off
Per disabilitare il supporto CGI, ad esempio, potete usare:
setsebool -P httpd_enable_cgi off
21) Installare Mod_security
Mod_security è un’estensione per la sicurezza utile per rilevare eventuali intrusioni dall’esterno: il suo file di configurazione si può utilizzare per evitare attacchi XSS e simili.
# impedisce di aprire file nella cartella /etc/ SecFilter /etc/ # evita SQL injection SecFilter "delete[[:space:]]+from" SecFilter "select.+from"
22) Eseguire PHP+Apache in una Chroot Jail
Permette al server di funzionare in “camera stagna” limitando le possibilità di attacco: si abilita ad esempio mediante FreeBSD jails, XEN, KVM e OpenVZ.
23) Utilizzare un firewall per proteggere il sistema
L’impostazione di iptables opportune impedisce ad un attaccante di ottenere file dal vostro sito mediante wget, stabilendo delle regole per le utenze che accedono. Ad esempio potete fare in modo che l’utente pippo si connetta solo mediante la porta 80.
/sbin/iptables -A OUTPUT -o eth0 -m owner --uid-owner pippo -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
Di seguito un esempio che blocca tutte le connessioni verso l’esterno tranne quelle del server SMTP:
# .... /sbin/iptables --new-chain apache_user /sbin/iptables --append OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT /sbin/iptables --append OUTPUT -m owner --uid-owner apache -j apache_user # allow apache user to connec to our smtp server /sbin/iptables --append apache_user -p tcp --syn -d 192.168.1.100 --dport 25 -j RETURN # Allow apache user to connec to api server for spam validation /sbin/iptables --append apache_user -p tcp --syn -d 66.135.58.62 --dport 80 -j RETURN /sbin/iptables --append apache_user -p tcp --syn -d 66.135.58.61 --dport 80 -j RETURN /sbin/iptables --append apache_user -p tcp --syn -d 72.233.69.89 --dport 80 -j RETURN /sbin/iptables --append apache_user -p tcp --syn -d 72.233.69.88 --dport 80 -j RETURN ######################### ## Add more rules here ## ######################### # No editing below # Drop everything for apache outgoing connection /sbin/iptables --append apache_user -j REJECT
24) Tenere d’occhio i log di sistema
La posizione dei log può cambiare volta per volta a seconda delle varie configurazioni: ad esempio alcuni modi per visualizzarlo da riga di comando (mostrando solo eventuali errori nel secondo e terzo caso) sono i seguenti.
Log di apache (da riga di comando):
tail -f /var/log/httpd/error_log grep 'login.php' /var/log/httpd/error_log egrep -i "denied|error|warn" /var/log/httpd/error_log
Log di PHP (da riga di comando):
tail -f /var/log/httpd/php_scripts_error.log grep "...etc/passwd" /var/log/httpd/php_scripts_error.log
25) Lavorare in modalità Service Per System / VM Instance
Per i siti web più grossi è opportuno suddividere in vari sottodomini le parti del sito, in termini di database, contenuti statici e dinamici. La separazione in varie macchine virtuali (ottenibile mediante VPS server) permette di fare in modo di limitare i danni qualora un attaccante prendesse possesso della macchina da remoto, circoscrivendo così l’attacco. Nella pratica potremmo avere un singolo server nginx che riceve tutti i pacchetti in ingresso e li smista al destinatario (il database, il contenuto statico/dinamico ecc.), fungendo nel contempo da load-balancer. Si tratta comunque di configurazioni avanzate che interessano quasi esclusivamente i portali ad elevatissimo traffico giornaliero.
2 commenti