Cloisonnement de sites web avec suPHP pour Apache
Aujourd'hui, nous allons voir comment mettre en place un cloisonnement de sites web avec suPHP pour Apache. Ce module permet d'exécuter des scripts PHP sous une identité différente, pour chaque site web hébergé sur un serveur. Ainsi, si une personne arrive à pirater l'un des sites, elle ne pourra pas impacter les fichiers des autres sites car elle ne possédera pas les bons droits d'accès.
1. Avant de commencer
Dans ce tutoriel, je pars du principe que vous connaissez le fonctionnement d'Apache, de ses modules et de PHP. Ces derniers doivent d'ailleurs être installés sur votre serveur avec les paquets RPM ou DEB.
Dans l'exemple que nous allons suivre, nous utiliserons mod_suPHP sur le serveur de Tux-planet pour cloisonner deux sections du site :
- le site principal sous WordPress, qui tournera sous l'uid www-wordpress
- le forum sous FluxBB, qui tournera sous l'uid www-forum
2. Installation de suPHP
Pour installer suPHP sur une distribution à base de RPM, téléchargez les sources depuis le site officiel. Lancez ensuite les commandes suivantes en root pour compiler le module :
yum install gcc gcc-c++ automake autoconf make httpd-devel apr apr-devel
cd /usr/local/src
tar zxvf suphp-*.tar.gz && rm -f suphp-*.tar.gz && cd suphp-*
./configure --prefix=/usr/local/suphp-0.7.1 \
--sysconfdir=/etc \
--with-apxs=/usr/sbin/apxs \
--with-apr=/usr/bin/apr-1-config \
--with-logfile=/var/log/suphp.log \
--with-setid-mode=paranoid \
--with-min-uid=500 \
--with-min-gid=500 \
--with-apache-user=apache
make
make install
On poursuit avec la mise en place d'un lien symbolique :
cd /usr/local
ln -s suphp-0.7.1 suphp
ls -l
Une fois la compilation terminée, on devra se retrouver avec les deux fichiers suivants :
/usr/local/suphp/sbin/suphp
/etc/httpd/modules/mod_suphp.so
3. Configuration de suPHP
La configuration du module se situe dans le fichier /etc/suphp.conf. Recopiez le fichier d'exemple fourni avec le code source pour avoir une base de départ :
cp /usr/local/src/suphp-*/doc/suphp.conf-example /etc/suphp.conf
Ensuite, il vous faudra adapter la configuration. Pour ma part, j'utilise les options suivantes :
[global] ;Path to logfile logfile=/var/log/suphp.log ;Loglevel loglevel=info ;User Apache is running as webserver_user=apache ;Path all scripts have to be in docroot=/home/ ;Path to chroot() to before executing script ;chroot=/mychroot ; Security options allow_file_group_writeable=false allow_file_others_writeable=false allow_directory_group_writeable=false allow_directory_others_writeable=false ;Check wheter script is within DOCUMENT_ROOT check_vhost_docroot=false ;Send minor error messages to browser errors_to_browser=false ;PATH environment variable env_path=/bin:/usr/bin ;Umask to set, specify in octal notation umask=0027 ; Minimum UID min_uid=500 ; Minimum GID min_gid=500 [handlers] ;Handler for php-scripts ;x-httpd-php="php:/usr/bin/php" x-httpd-php="php:/usr/bin/php-cgi" ;Handler for CGI-scripts x-suphp-cgi="execute:!self"
Vous devez savoir que pour faire fonctionner correctement suPHP, il faut faire tourner PHP en mode CGI et non en mode CLI comme on le fait traditionnellement. Ce changement se situe au niveau de cette ligne, dans le fichier de configuration principal :
x-httpd-php="php:/usr/bin/php-cgi"
L'utilisation du mode CGI n'a pas d'impact sur la compatibilité des scripts. En revanche, si vous ne le faites pas, vous risquez de vous retrouver avec le message d'erreur suivant dans les logs :
Premature end of script headers
A noter également qu'ici j'utilise un umask de 0027. Cela signifie que lorsque les scripts php créeront des fichiers (logs, fichiers de cache, images...), les droits seront les suivants par défaut :
- 750 pour les dossiers : -rwx-r-x---
- 640 pour les fichiers : -rw-r-----
Voici une liste qui récapitule différentes possibilités d'utilisation d'umask :
- umask 0000 = 777 pour les dossiers & 666 pour les fichiers
- umask 0022 = 755 pour les dossiers & 644 pour les fichiers
- umask 0027 = 750 pour les dossiers & 640 pour les fichiers
- umask 0077 = 700 pour les dossiers & 600 pour les fichiers
- umask 0007 = 770 pour les dossiers & 660 pour les fichiers
4. Mise en place des groupes et utilisateurs
Dans notre cas, nous allons avoir besoin de deux utilisateurs dédiés pour les deux sections du site :
adduser www-wordpress -m -s /sbin/nologin
adduser www-forum -m -s /sbin/nologin
Vous devez ensuite déplacer les fichiers des sites dans les homedir des utilisateurs. Il ne faudra pas oublier de mettre en place les bons droits d'accès (seul le propriétaire et le groupe doivent avoir accès + notion de bit SUID et umask pour que les droits se propage correctement lors de la création de fichiers ou dossier) :
chown -R www-wordpress: ~www-wordpress
chmod -R 750 ~www-wordpress
chmod -R g+s ~www-wordpress
cd ~www-wordpress && umask 0027 && cd ..chown -R www-forum: ~www-forum
chmod -R 750 ~www-forum
chmod -R g+s ~www-forum
cd ~www-forum && umask 0027 && cd ..
Pour éviter certains problèmes de droits (chargement des fichiers .htaccess, compatibilité avec mod_deflate...), on ajoute l'utilisateur apache aux groupes des utilisateurs qui exécuteront les sites :
usermod -G www-wordpress,www-forum apache
Pensez également à relancer le processus apache pour prendre en compte chaque modification de droits, le fichier /etc/group est rechargé au démarrage du serveur uniquement :
service httpd restart
5. Configuration d'Apache et des virtualhosts
Au niveau de la configuration d'Apache, il faut ajouter la ligne suivante au fichier /etc/httpd/conf/httpd.conf pour charger le module :
LoadModule suphp_module modules/mod_suphp.so
Et utiliser ces lignes dans chaque virtualhost pour lequel on souhaitera utiliser un identifiant différent d'Apache, afin de faire tourner les scripts PHP :
<Location "/"> # Configuration de suPHP suPHP_Engine on suPHP_UserGroup www-wordpress www-wordpress suPHP_ConfigPath /etc/ suPHP_AddHandler x-httpd-php AddHandler x-httpd-php .php .php3 .php4 .php5 # Configuration de PHP php_admin_flag engine on </Location>
Ensuite, on recharge la configuration d'apache pour prendre en compte les modifications :
service httpd reload
6. Test de suPHP
Pour tester le bon fonctionnement de suPHP, on peut utiliser le bout de code PHP suivant à la racine de chaque site :
echo "<?php echo exec('/usr/bin/whoami'); ?>" > suphp-test.php
Et l'appeler ensuite pour vérifier que l'uid utilisé est le bon :
http://localhost/suphp-test.php
7. En cas de problème
La mise en place du module suPHP n'est pas forcément évidente. C'est rare que cela marche du premier coup et il faut souvent revoir les droits d'accès et la configuration pour obtenir quelque chose qui fonctionne.
Donc en cas de problème, je vous conseille de regarder les messages dans les fichiers de logs :
tail -f /var/log/suphp.log /var/log/httpd/error_log
Vous pouvez aussi jouer sur l'option suivante du fichier /etc/suphp.conf, qui permet d'afficher des messages d'erreur dans le navigateur :
errors_to_browser=true
11 Commentaires pour "Cloisonnement de sites web avec suPHP pour Apache"
Flux des commentaires de cet article Ajouter un commentaireJe suis en générale a fond pour tout ce qui est ségrégation de droit et tout ça, mais je trouve que la commande suivante bien que nécessaire limite un peu l'effet escompté
usermod -G www-wordpress,www-forum apache
@Fab : le problème, c'est que si tu ne le fais pas, il y a plein de truc qui ne fonctionne pas. Apache à besoin d'avoir accès au fichiers .htaccess, css ... (si il existe une autre méthode, je suis preneur).
Pour moi, d'un point de vue sécurité cela tiens la route. Si un hacker pirate un site, il se retrouve avec l'identité www-xxx et ne pourra exécuter que des commandes sous celle-ci.
Pour passer Apache, il devra trouver une faille dans le module suPHP, ce qui rend les chose très compliqué.
Si la compartimentation est utile pour l'utilisateur et permet de limiter quelques problèmes de sécurité, elle pose aussi de gros soucis. Puisque le process PHP tourne avec l'id de l'utilisateur, en cas de faille XSS il sera possible d'écraser ou supprimer des fichiers de l'utilisateur partout sur le serveur (surtout si on ne chroot pas le process...).
Ou sinon le mode ITK pour Apache2 qui j'utilise depuis un bon moment, qui fonctionne très bien et qui ne necessite qu'une directive.
@ArnY : oui, c'est pour cela que l'on crée un utilisateur spéciale par site et qu'ils ne servent que pour le site et pas pour autre chose. C'est le but du cloisonnement, on peux effacer tous les fichiers de l'utilisateur, mais pas le reste. On reste donc dans les objectifs fixés à la base.
@Mogui : ça fonctionne avec PHP ? Tu peux m'en dire plus sur ce mode, ça m'intéresse.
Edit : j'ai trouvé ça.
@pti-seb : Salut. Il y a une petite coquille :
Par défaut, avec Apache + php (via mod_php), la connexion ne se fait pas via la CLI, mais directement via la SAPI mod_php. Donc c'est phrase "en mode CGI et non en mode CLI comme on le fait traditionnellement" est complètement fausse.
De plus, je pense que pour de meilleur perf, il faudrait mieux passer par Fast-CGI plutôt que CGI. CGI est assez lent, il ne peut gérer qu'une seule req HTTP, et comme apache fork par défault dès qu'une nouvelle requete arrive, et bien le site est très rapidement sous l'eau. Fast-CGI est en fait un gestionnaire de process CGI (pool) qui est bien plus performant que les forks d'apache.
@ArnY : Salut. Une faille XSS est une faille qui se trouve du coté client. C'est un problème qui arrive quand tu n'échappes pas ta vue (output escaping). Du coup, il est impossible de modifier le contenu d'un serveur avec une faille XSS.
@Greg : Une faille XSS est une faille coté serveur (une faille dans une page), pas une faille côté client (pas une faille dans le navigateur de l'utilisateur). Une variable mal controlée et utilisée dans un get_file_content, include, ou autre peut permettre d'injecter une page php distante à l'intérieur d'une page hébergée sur ton serveur et modifier les fichiers utilisateurs.
@ArnY : Salut. En fait le plus simple serait que tu ailles lire la fiche wikipedia (http://fr.wikipedia.org/wiki/Cross-site_scripting) qui récapitule bien les choses.
Sinon je vais essayer avec mes mots :
Le problème vient bien du serveur, car c'est sur le serveur qu'il y a le code php. Cependant la faille arrive sur le client, car le serveur n'a pas échappé la vue. Grosso - Modo, ça veut dire qu'il peut y avoir des choses comme ca dans le code php : et donc php va afficher directement ce qu'il y a dans la variable $_GET['post'] sans l'échapper. Donc toi, par exemple, tu peux "bidouiller" le lien de la page, et ajouter du js (par exemple, mais ca pourrait etre du html ou du css) : http://exemple.com/search?search=javascript:alert('Ceci est une faille XSS').
Du coup le problème est bien sur le client, et non pas sur le serveur.
Avec une faille XSS, tu ne peux pas profiter d'un "get_file_contents" (avec un S) ou d'un include mal codé.
Et puis, de façon plus général, je ne vois pas trop l’intérêt d'utiliser un include basé sur une variable directement récuperer de l'utiliser. C'est vraiment mal codé que de faire comme ca... Il y a de très bon framework maintenant ..
Personnellement, je me demande pourquoi certains configure les répertoires web avec des droits chmod en 755. On ne veut pas que des personnes étrangères au système puissent lire les fichiers de votre site. Une faille potentielle car si un autre service est détourné, il a un accès en lecture à ces données.
Pour ma part, je me fabrique un utilisateur Linux pour chaque virtualhost. Ces utilisateurs appartiennent tous au groupe Apache. Donc ils sont propriétaires de leurs fichiers pour chaque répertoires web. Mes répertoires web sont alors configurés en 750 et non en 755.
Autrement dit, seul le propriétaire ou le groupe Apache peuvent accéder à ces répertoires. Cependant, le code des répertoires web n'est pas totalement cloisonné pour autant, car n'importe qu'elle utilisateur appartement au groupe Apache à la possibilité de lire le code du répertoire.
Avec le système que tu nous présentes Sébastien, cela cloisonne réellement le répertoire web. C'est super !! Merci pour ce module. Il manque tout de même quelque chose à ça, ce serait la possibilité d'avoir un fichier php.ini pour chaque site. Cela cloisonnerai encore plus les sites des uns et des autres et augmenterait la sécurité avec une configuration PHP adapté pour chaque site.
Car personnellement, PHP regorge de fonctions dont certaines, si elles sont détournés, sont très dangereuses pour le serveur tel que system par exemple. Je pourrais très bien faire un filtre sur les fonctions à utiliser pour chacun des sites que j'utilise sur mon serveur avec un php.ini pour chaque virtualhost. Tu vois ou je veux en venir je pense ?
Bref, même avec un répertoire cloisonné de la sorte, votre site n'est pas pour autant à l’abri de tous les danger !! Mais en matière de sécurité, la faille la plus grosse et j'en sais bien quelque chose, c'est l'ignorance !! Autrement dit, RTFM à tous !!!
@ +
@Benjamin Fréva : suPHP propose l'utilisation de fichier php.inin différents selon le virtualhost.
Exemple :
Une autre technique, citée dans la FAQ officielle propose d'utiliser htscanner pour faire ça.
@pti-seb Merci pour l'astuce !! C'est trop bon !!