Vendredi 23 novembre 2018

Comment accueillir plusieurs sites Web en toute sécurité avec Nginx Et Php-fpm

Article original : How To Host Multiple Websites Securely With Nginx And Php-fpm On Ubuntu 14.04

introduction

Il est bien connu que la pile LEMP (Linux, nginx, MySQL, PHP) offre une vitesse et une fiabilité inégalées pour l’exécution des sites PHP. Autres avantages tels que la sécurité et l’isolement sont moins populaires, cependant.

Dans cet article, nous allons vous montrer les avantages de la sécurité et de l’isolement de sites sur LEMP avec différents utilisateurs de Linux en cours d’exécution. Cela se fera par la création de différents pools de php-fpm pour chaque bloc de serveur nginx (site ou hôte virtuel).

Conditions préalables

Ce guide a été testé sur Ubuntu 14.04. L’installation et la configuration décrite seraient similaires sur d’autres versions du système d’exploitation ou OS, mais les commandes et l’emplacement des fichiers de configuration peuvent varier.

Il suppose également que vous avez déjà nginx et php-fpm mis en place. Si non, s’il vous plaît suivre la première étape et la troisième étape de l’article Comment faire pour installer Linux, nginx, MySQL, PHP (LEMP) pile sur Ubuntu 14.04.

Toutes les commandes de ce tutoriel doit être exécuté en tant qu’utilisateur non root.
Si l’accès root est nécessaire pour la commande, il sera précédé par sudo.
Si vous ne possédez pas sz serveur mis en place, suivez ce tutoriel: Configuration initiale du serveur avec Ubuntu 14.04.

Vous aurez également besoin d’un nom de domaine complet (fqdn) qui pointe vers le Droplet pour les tests en plus de la localhost par défaut. Si vous ne l’avez pas à portée de main, vous pouvez utiliser site1.example.org.
Modifier le fichier /etc/hosts avec votre éditeur préféré comme ceci sudo nano /etc/hosts et ajoutez cette ligne (remplacez site1.example.org avec votre fqdn si vous l’utilisez): / etc / hosts

… 127.0.0.1 site1.example.org …

Raisons pour plus sécuriser LEMP

Dans le cadre d’une configuration commune LEMP il n’y a qu’un seul **php-fpm pool ** qui exécute tous les scripts PHP pour tous les sites sous le même utilisateur. Cela pose deux problèmes majeurs:

  • Si une application sur le bloc de serveur d’nginx, à savoir sous-domaine ou site distinct web, est compromis, tous les sites sur ce Droplet seront touchés aussi. L’attaquant est capable de lire les fichiers de configuration, y compris les détails de base de données, des autres sites ou même modifier leurs fichiers.
  • Si vous voulez donner un accès utilisateur à un site sur votre Droplet, vous lui donnaerez accès à tous les sites. Par exemple, votre développeur a besoin de travailler sur l’environnement de la mise en scène. Cependant, même avec les permissions de fichiers très strictes, vous serez toujours obligé de lui donner accès à tous les sites, y compris votre site principal, sur le même Droplet.

Les problèmes ci-dessus sont résolus en php-fpm en créant un pool différent qui fonctionne sous un utilisateur différent pour chaque site.

Étape 1 - Configuration de php-fpm

Si vous avez couvert les conditions préalables, alors vous devriez déjà avoir un site web fonctionnel sur le Droplet. Sauf si vous avez spécifié un fqdn pour cela, vous devriez être en mesure d’y accéder sous localhost ou par l’adresse IP.

Maintenant nous allons créer un deuxième site (site1.example.org) avec son propre php-fpm pool et l’utilisateur Linux.

Commençons par la création de l’utilisateur nécessaire. Pour une meilleure isolation, le nouvel utilisateur doit avoir son propre groupe. Donc, d’abord créer le groupe d’utilisateurs site1 :
sudo groupadd site1
Puis créer un utilisateur site1 appartenant à ce groupe:
sudo useradd -g site1 site1

Le nouvel utilisateur site1 n’a pas de mot de passe et ne peut pas ouvrir une session dans le Droplet. Si vous avez besoin de fournir une personne avec un accès direct aux fichiers de ce site, vous devez créer un mot de passe pour cet utilisateur avec la commande sudo passwd site1. Avec la nouvelle combinaison utilisateur/mot de passe ,un utilisateur peut se connecter à distance par ssh ou sftp. Pour plus d’information et de sécurité ,vérifier la configuration de l’article Setup a secondary SSH/SFTP user with limited directory access.
Ensuite, créez un nouveau pool php-fpm pour site1.
Un pool de php-fpm dans son essence même est juste un processus Linux ordinaire, qui fonctionne sous certains utilisateur/groupe et écoute sur un socket Linux. Il pourrait également écouter sur une adresse IP: combinaison de port aussi, mais cela nécessiterait plus de ressources , et ce n’est pas la méthode préférée.

Par défaut, dans Ubuntu 14.04 chaque pool de php-fpm doit être configuré dans un fichier situé dans le répertoire /etc/php5/fpm/pool.d. Dans ce répertoire ,chaque fichier avec les extensions .conf est automatiquement chargé dans la configuration globale php-fpm.

Donc, pour notre nouveau site, nous allons créer un nouveau fichier /etc/php5/fpm/pool.d/site1.conf . Vous pouvez le faire avec votre éditeur préféré comme ceci:
sudo nano /etc/php5/fpm/pool.d/site1.conf

Ce fichier /etc/php5/fpm/pool.d/site1.conf doit contenir:

[site1]
user = site1
group = site1
listen = /var/run/php5-fpm-site1.sock
listen.owner = www-data
listen.group = www-data
php_admin_value[disable_functions] = exec,passthru,shell_exec,system
php_admin_flag[allow_url_fopen] = off
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chdir = /

Dans la configuration ci-dessus noter ces options spécifiques:

  • [Site1] est le nom du pool. Pour chaque pool, vous devez spécifier un nom unique.
  • user et group utilisés pour le nouveau pool qui sera exécuté
  • listen devra pointer vers un emplacement unique pour chaque pool.
  • listen.owner et listen.group définissent la propriété de l’auditeur, à savoir le socket du nouveau pool de php-fpm. Nginx doit être capable de lire ce socket. Voilà pourquoi le socket est créé avec l’utilisateur et groupe sous lequel nginx fonctionne - www-data. * php_admin_value vous permet de définir les valeurs de configuration personnalisée PHP. Nous l’avons utilisé pour désactiver les fonctions qui peuvent exécuter des commandes Linux - exec, passthru, shell_exec, système. * php_admin_flag est similaire à php_admin_value, mais il est juste un commutateur pour les valeurs booléennes, à savoir sur et en dehors. Nous allons désactiver la fonction PHP allow_url_fopen qui permet à un script PHP d’ouvrir des fichiers à distance et pourrait être utilisé par l’attaquant.

Remarque: Les valeurs de php_admin_value et php_admin_flag ci-dessus pourraient également être appliquées à l’échelle mondiale. Cependant, un site peut avoir besoin, et voilà pourquoi par défaut, ils ne sont pas configurés. La beauté des pools de php-fpm est qu’il vous permet d’affiner les paramètres de sécurité de chaque site. De plus, ces options peuvent être utilisées pour tous les autres paramètres de php, en dehors du périmètre de sécurité, afin de personnaliser l’environnement d’un site.

Les options pm sont en dehors du thème de la sécurité en cours, mais vous devez savoir qu’ils vous permettent de configurer les performances du pool.

L’option chdir devrait être / qui est la racine du système de fichiers. Cela ne devrait pas être modifié sauf si vous utilisez une autre option importante de chroot.

L’option chroot n’est pas incluse dans la configuration ci-dessus à dessein. Elle permettrait d’exécuter un pool dans un environnement emprisonné (jailed environment), à savoir enfermé dans un répertoire. Il est parfait pour la sécurité parce que vous pouvez verrouiller le pool à l’intérieur de la racine web du site. Cependant, cette sécurité ultime causera de graves problèmes pour toute application PHP qui se fonde sur les binaires du système et des applications telles que Imagemagick ne seront pas disponibles. Si vous êtes plus intéressés par ce sujet s’il vous plaît lire l’article How To Use Firejail to Set Up a WordPress Installation in a Jailed Environment.

Une fois que vous avez terminé avec la configuration ,redémarrer php-fpm pour que les nouveaux paramètres entrent en vigueur:
sudo service php5-fpm restart
Vérifiez que le nouveau pool est bien en cours d’exécution par la recherche de ses processus:
ps aux | grep site1
Si vous avez suivi les instructions , vous devriez voir une sortie similaire à:

site1   14042  0.0  0.8 133620  4208 ?        S    14:45   0:00 php-fpm: pool site1
site1   14043  0.0  1.1 133760  5892 ?        S    14:45   0:00 php-fpm: pool site1

En début de ligne l’utilisateur sous lequel le processus ou le pool de php-fpm fonctionne - site1.

En outre, nous désactivons la mise en cache de php par défaut fourni par opcache. Cette extension de la mise en cache particulière pourrait être grande pour la performance, mais pas pour la sécurité comme nous le verrons plus tard.
Pour le désactiver modifier le fichier /etc/php5/fpm/conf.d/05-opcache.ini avec les privilèges de super-utilisateur et ajoutez la ligne:
sudo nano /etc/php5/fpm/conf.d/05-opcache.ini

opcache.enable = 0

Puis redémarrer php-fpm :
sudo service php5-fpm restart

Étape 2 - Configuration de nginx

Une fois que nous avons configuré le pool de php-fpm pour notre site, nous allons configurer le bloc de serveur nginx. Créer un nouveau fichier /etc/nginx/sites-available/site1 avec votre éditeur préféré comme ceci:
sudo vim /etc/nginx/sites-available/site1
Ce fichier doit contenir:

server {
    listen 80;

    root /usr/share/nginx/sites/site1;
    index index.php index.html index.htm;

    server_name site1.example.org;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php5-fpm-site1.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Le code ci-dessus montre une configuration commune pour un bloc de serveur nginx. Notez les pièces intéressantes mises en évidence:

  • Web racine est /usr/share/nginx/sites/site1.
  • Le nom du serveur utilise le site1.example.org fqdn qui est celui mentionné dans les conditions du présent article.
  • fastcgi_pass spécifie le gestionnaire pour les fichiers php. Pour chaque site, vous devez utiliser un socket unix différent tel que /var/run/php5-fpm-site1.sock.

Créez le répertoire racine web:

    sudo mkdir /usr/share/nginx/sites
    sudo mkdir /usr/share/nginx/sites/site1

Pour activer le site ci-dessus, vous devez créer un lien symbolique vers le dans le répertoire /etc/nginx/sites-enabled/. Cela peut être fait avec la commande:
sudo ln -s /etc/nginx/sites-available/site1 /etc/nginx/sites-enabled/site1
Enfin, redémarrez nginx pour la modification prenne effet comme ceci:
sudo service nginx restart

Étape 3 - Essais

Pour l’exécution des tests, nous allons utiliser la fonction phpinfo bien connue qui fournit des informations détaillées sur l’environnement php. Créer un nouveau fichier sous le nom info.php qui ne contient que la ligne . Créer ce fichier à la racine /usr/share/nginx/html/. A cet effet, vous pouvez utiliser un éditeur comme ceci:
sudo nano /usr/share/nginx/html/info.php
Après cela, copiez le fichier à la racine web de l’autre site (site1.example.org) comme ceci: sudo cp /usr/share/nginx/html/info.php /usr/share/nginx/sites/site1/
Maintenant, vous êtes prêt à exécuter le test le plus élémentaire pour vérifier l’utilisateur du serveur. Vous pouvez effectuer le test avec un navigateur ou un terminal et lynx, le navigateur de ligne de commande. Si vous ne disposez pas de lynx sur votre Droplet encore, installez-le avec la commande:
sudo apt-get install lynx.

Vérifiez d’abord le fichier info.php à partir de votre site par défaut. Il devrait être accessible sous localhost comme ceci:
ynx --dump http://localhost/info.php |grep 'SERVER\["USER"\]'

Dans la commande ci-dessus nous filtrons la sortie avec grep seulement pour la variable SERVER[“USER”] qui représente l’utilisateur du serveur. Pour le site par défaut la sortie doit montrer l’utilisateur par défaut www-data comme ceci:
_SERVER["USER"] www-data
Vérifier ensuite l’utilisateur du serveur site1.example.org: lynx --dump http://site1.example.org/info.php |grep 'SERVER\["USER"\]'
Vous devriez voir cette fois dans la sortie l’utilisateur site1: _SERVER["USER"] site1
Si vous avez effectué des réglages PHP personnalisé sur une base par pool de php-fpm, alors vous pouvez également vérifier les valeurs correspondantes de la manière ci-dessus en filtrant la sortie qui vous intéresse.

Jusqu’à présent, nous savons que nos deux sites fonctionnent sous différents utilisateurs, mais maintenant nous allons voir comment sécuriser une connexion. Pour illustrer le problème de la sécurité que nous résolvons dans cet article, nous allons créer un fichier avec des informations sensibles. Habituellement, un tel fichier contient la chaîne de connexion à la base de données inclus utilisateur et mot de passe. Si quelqu’un découvre l’information, on imagine la suite!!!.

Avec votre éditeur préféré créer un nouveau fichier dans votre site principal /usr/share/nginx/html/config.php.
sudo nano /usr/share/nginx/html/config.php
Ce fichier doit contenir:

<?php
$pass = 'secret';
?>

Dans le fichier ci-dessus, nous définissons une variable appelée pass qui cpntient la valeur secret. Naturellement, nous voulons restreindre l’accès à ce fichier, donc nous allons définir ses autorisations à 400, qui donnent accès en lecture seule au propriétaire du fichier.

Pour modifier les autorisations à 400 exécutez la commande:
sudo chmod 400 /usr/share/nginx/html/config.php

En outre, notre site principal fonctionne sous l’utilisateur www-data qui doit être en mesure de lire ce fichier. Ainsi, changer la propriété du fichier à cet utilisateur comme ceci: sudo chown www-data:www-data /usr/share/nginx/html/config.php

Dans notre exemple, nous allons utiliser un autre fichier appelé /usr/share/nginx/html/readfile.php pour lire l’information secrète et l’afficher. Ce fichier doit contenir le code suivant:
sudo nano /usr/share/nginx/html/readfile.php

<?php
include('/usr/share/nginx/html/config.php');
print($pass);
?>

Modifiez la propriété de ce fichier à www-data ainsi:
sudo chown www-data:www-data /usr/share/nginx/html/readfile.php

Pour confirmer toutes les autorisations et les propriétaires sont corrects dans la racine web exécutez la commande ls -l /usr/share/nginx/html/. Vous devriez voir une sortie similaire à:

-r-------- 1 www-data www-data  27 Jun 19 05:35 config.php
-rw-r--r-- 1 www-data www-data  68 Jun 21 16:31 readfile.php

Maintenant accéder au dernier fichier sur votre site par défaut avec la commande :
lynx --dump http://localhost/readfile.php
Vous devriez être en mesure de voir s’afficher secret en sortie qui indique que le fichier avec des informations sensibles est accessible dans le même site, ce qui est le comportement correct attendu.

Maintenant, copiez le fichier /usr/share/nginx/html/readfile.php sur votre second site, site1.example.org comme ceci:
sudo cp /usr/share/nginx/html/readfile.php /usr/share/nginx/sites/site1/

Pour maintenir les relations site/utilisateur, assurez-vous que dans chaque site, les fichiers appartiennent à l’utilisateur du site respectif. Pour ce faire, changer groupe et utilisateur du fichier nouvellement copié :
sudo chown site1:site1 /usr/share/nginx/sites/site1/readfile.php

Pour confirmer que vous avez défini les autorisations et la propriété du fichier correct, s’il vous plaît lister le contenu du web racine site1 avec la commande:
ls -l /usr/share/nginx/sites/site1/
Tu devrais voir:

-rw-r - r-- 1 place1 place1 80 21 juin 16h44 readfile.php

Ensuite, essayez d’accéder au même fichier à partir de site1.example.com avec la commande:
lynx --dump http://site1.example.org/readfile.php
Vous ne verrez que l’espace vide retourné. De plus, si vous recherchez des erreurs dans le journal des erreurs de nginx avec la commande sudo grep error /var/log/nginx/error.log vous verrez:

2015/06/30 15:15:13 [error] 894#0: *242 FastCGI sent in stderr: "PHP message: PHP Warning:  include(/usr/share/nginx/html/config.php): failed to open stream: Permission denied in /usr/share/nginx/sites/site1/readfile.php on line 2

Remarque: Vous verriez aussi une erreur semblable dans la sortie de lynx si vous avez display_errors set to On dans la configuration /etc/php5/fpm/php.ini

L’avertissement indique qu’un script à partir du site site1.example.org ne peut pas lire le fichier config.php sensible à partir du site principal. Ainsi, les sites fonctionnant sous différents utilisateurs ne peuvent pas compromettre la sécurité de l’autre.

Nous avons désactivé la mise en cache par défaut fourni par opcache. Si vous êtes curieux de savoir pourquoi, essayez d’activer à nouveau opcache en définissant avec les privilèges de super-utilisateur opcache.enable = 1 dans le fichier php5-fpm /etc/php5/fpm/conf.d/05-opcache.ini et redémarrer avec la commande sudo Service php5-fpm redémarrage.

Étonnamment, si vous exécutez à nouveau les étapes de test dans exactement le même ordre, vous serez en mesure de lire le fichier sensible indépendamment de sa propriété et de l’autorisation. Ce problème dans opcache a été rapporté depuis longtemps, mais le temps de cet article, il n’a pas encore été résolu.

Conclusion

D’un point de vue de la sécurité, il est essentiel d’utiliser des pools de php-fpm avec un utilisateur différent pour chaque site sur le même serveur web Nginx. Même s’il est livré avec une petite pénalité de performance, le bénéfice d’un tel isolement pourrait prévenir les violations graves de sécurité.

L’idée décrite dans cet article est unique, et il est présent dans d’autres technologies d’isolement de PHP similaires tels que SuPHP. Cependant, la performance de toutes les autres solutions de rechange est bien pire que celle de php-fpm.