Artisan Numérique

/système/réseau/apache/sécurité/ Protéger ses données sur un serveur Apache

Protéger ses données exposées par apache n'est pas toujours évident et comporte quelques pièges qui donnent une illusion de sécurité alors qu'il n'en est rien. Ce tutoriel ne va pas, loin de là, couvrir tous les aspects du sujet, mais juste tenter d'apporter quelques bases permettant de se protéger à travers quelques cas pratiques.

Configuration

Pour tout ce qui suit, ou presque, existe deux méthodes de paramétrages. La plus simple consiste à placer dans le dossier web à protéger un fichier .htaccess qui contiendra une configuration spécifique à ce dossier.

La première méthode, sûrement la plus pérenne, est de modifier directement un des fichiers de configuration Apache et ainsi protéger sur une section Location ou Directory.

Les fichiers de configuration d'Apache se trouvent généralement en /etc/httpd. Vous pouvez soit modifier /etc/httpd/httpd.conf, soit créer votre propre fichier .conf dans le dossier /etc/httpd/conf.d. A noter que les localisations peuvent changer d'une distribution à l'autre.

Par exemple pour protéger un dossier :

<directory "/mon/dossier/a/proteger">
  AuthType Basic
  AuthName "Accès protégé"
  AuthUserFile "/mon_chemin_vers_mots_de_passe/passwd"
  require valid-user
</directory>
Exemple de fichier '/etc/httpd/conf.d/ma_protection.conf'

L'autre possibilité est d'utiliser .htaccess qui se trouve à la racine du dossier à protéger. C'est donc en quelque sorte un équivalent dynamique (effectif pour le prochain hit) à ce que nous avons vu plus haut.

AuthType Basic
AuthName "Accès protégé"
AuthUserFile "/mon_chemin_vers_mots_de_passe/passwd"
require valid-user
Exemple de fichier .htaccess

Cette méthode a le mérite de la simplicité et protège autant le dossier lui-même que les sous-dossiers qu'il contient, sauf si un autre fichier .htaccess y contrevient. Son inconvénient est qu'il n'est pas simple de maintenir une ribambelle de fichier .htaccess qui traînent partout.

Authentification simple

Il s'agit ici de mettre en place une authentification classique par mot de passe sur un dossier donné. Vous pouvez placer cela dans une balise Location, Directory ou encore dans un .htaccess et ressemble à cela :

AuthName "Bienvenue sur mon serveur web"
AuthType Basic
AuthUserFile "/var/secured/passwords"
require valid-user

AuthName est le message qui sera affiché dans la boite de dialogue de saisie de l'identifiant et du mot de passe. Gardez cela en tête pour ne pas y mettre de chose trop explicites comme "Bienvenue dans le site contenant tous mes numéros de carte bleue" ;-).

Le second paramètre, AuthType indique à Apache d'utiliser le protocole Basic pour authentifier l'utilisateur. Comme son nom le laisse présager, ce protocole est le plus simple, le plus compatibles avec tous les navigateurs, mais aussi le moins sécurisé comme nous le verrons un peu plus loin. D'autres type d'authentification existent sous Apache par l'intermédiaire de modules spécifiques, comme nous allons le voire aussi plus loin.

Ensuite nous avons AuthUserFile qui indique le chemin vers le fichier que nous avons créé dans le chapitre précédent, et contenant donc nos utilisateurs et leurs mots de passe.

Enfin, la règle d'authentification à proprement parler, require valid-user. Elle indique à Apache que tout utilisateur ayant réussi à rentrer son mot de passe est autorisé à rentrer dans le dossier. Si nous voulions spécifiquement autoriser Robert à rentrer, nous aurions mis require user robert, et pour Gaston et Martine, cela aurais été require user gaston martine.

Vu que nous avons créé un fichier de groupes d'utilisateur au chapitre précédent, autant en profiter. Et si voulions n'autoriser que le groupe webmaster à rentrer, nous aurions cette fois un fichier .htaccess comme ceci :

AuthName "Bienvenue sur mon serveur web"
AuthType Basic
AuthUserFile "/var/secured/passwords"
AuthGroupFile "/var/secured/groups"
require group webmaster

Plus complet, les deux chemins vers les deux fichiers mots de passe et groupes sont indiqués (apparition du mot clef AuthGroupFile).

Enfin il est possible de panacher tout cela avec des choses du genre : require user robert, group webmaster. Ceci indique à apache que les membres du groupe webmaster sont autorisés, mais aussi Robert qui lui n'appartient pas à ce groupe.

Maintenant, si nous voulons qu'Apache protège notre dossier, il nous faut lui fournir une liste d'utilisateurs, voir de groupes d'utilisateurs. L'approche la plus simple est d'utiliser l'infrastructure de mots de passe d'Apache lui-même.

Nous allons donc d'abord créer un répertoire qui va recevoir nos mots de passe, puis en ajouter avec la commande Apache htpasswd :

mkdir /var/secured
touch /var/secured/passwords
chown apache:apache /var/secured -Rc
htpasswd -c /var/secured/passwords gaston
Création du fichier et ajout d'un utilisateur

Pour l'ajout des utilisateurs suivants, le fichier étant déjà créé, le paramètre -c n'est plus nécessaire :

htpasswd  /var/secured/passwords  martine
htpasswd  /var/secured/passwords  robert
Ajout de l'utilisateur 'martine'

Un utilisateur préalablement créé, pourra être retiré de la manière suivante :

htpasswd -D /var/secured/passwords  martine

Maintenant si nous vidons le contenu du fichier /var/secured/passwords, nous aurons ceci :

gaston:v/nTde818O.jQ
martine:CsE8VK.qdaFj.
robert:wKahxQnVtqyS6

Si vous n'avez pas accès à votre serveur en ligne de commande, rien ne vous empêche d'utiliser htpasswd à partir d'une autre machine et de télécharger ensuite le fichier résultant. Il existe aussi pas mal de scripts PHP sur le net permettant de fabriquer sur votre serveur une page de génération de mots de passe (la partie de droite). En revanche les générateurs de mot de passe en ligne sont simplement à proscrire pour d'évidentes raisons de sécurité.

Nous avons donc maintenant les utilisateurs, leur mot de passe, il ne nous reste plus qu'à définir des groupes, même si c'est optionnel. Si nous voulons indiquer que l'utilisateur gaston appartient au groupe webmasters il suffit de créer un second fichier /var/secured/groups contenant les données suivantes :

webmasters: gaston martine
visiteurs : robert

Voilà, notre dossier est maintenant protégé par une authentification basic et puise ses mots de passe dans l'infrastructure Apache. Mais il est possible de varier les types de sources d'authentification.

Authentification LDAP

Il y a de nombreux modules liés à l'authentification disponibles pour Apache sous la forme de modules. Par exemple si vous vouliez utiliser un protocole Digest au lien du simple Basic, vous utiliseriez le module mod_auth_digest.

De même différent modules permettent de vérifier les utilisateurs, groupes et mots de passe sur une base de donnée plutôt qu'un fichier texte, ou encore un serveur ldap par le biais du module mod_authnz_ldap. Il dépends du module Apache d'accès à LDAP, mod_ldap qu'il faut aussi installer. Ce module permet une authentification Basic adossée à un base LDAP. Un bloc de configuration pour un tel système se présente comme cela

AuthType Basic
AuthName "Bienvenue sur mon serveur web"
AuthBasicProvider ldap
AuthLDAPURL ldap://localhost:389/dc=mon-domaine,dc=com
AuthLDAPGroupAttribute memberUid
AuthLDAPGroupAttributeIsDN off
Require ldap-group cn=webmasters,ou=Group,dc=mon-domaine,dc=com
.htaccess via LDAP

Comme vous le voyez l'esprit est à peu prés le même que précédemment. La ligne AuthBasicProvider indique à apache de se baser sur un serveur LDAP plutôt qu'un fichier. Les coordonnées du serveur apparaissent avec la ligne AuthLDAPURL qui indique le nom de la machine, le port en écoute, ainsi que le domaine pris en charge.

Les deux attributs suivant permettent au module apache de se repérer dans les enregistrements LDAP et d'y trouver les utilisateurs. Le paramétrage que j'utilise ici se base sur la disposition standard d'une base LDAP sous Unix et est à adapter pour un ActiveDirectory sous Windows, par exemple.

Enfin la dernière ligne indique à apache de n'autoriser à rentrer que le group ldap spécifié (ici webmasters). L'utilisateur de ldap-user (ex. Require ldap-user Gaston) permettrait de n'autoriser qu'un utilisateur spécifique. Et ici aussi, le panachage est autorisé en séparant les éléments par des virgules.

Utiliser les mêmes mots de passe pour un service web publique et des utilisateurs locaux présente un risque car le protocole Basic ne permet pas de se protéger d'une attaque en "brut force". C'est-à-dire qu'un vilain peu faire autant de tentatives qu'il le désire sans qu'apache ne l'en empêche et ainsi trouver un mot de passe critique, même s'il met des jours a le faire. Le risque n'est pas énorme mais doit être pris en compte dans la qualité des mots de passe utilisables sur le WEB.

A ce stade, nous avons déjà pas mal d'outils pour protéger nos données mais si nous nous arrêtions à cela, cela ne servirait à rien... En effet, ce type de sécurité est aussi valable qu'une porte blindée sur des murs en cartons. Car le gros inconvénient du mode basic est que les mots de passes circulent en clair. Protéger par mot de passe un dossier accessible avec le protocole HTTP est donc très dangereux. Une simple écoute active (mode Promisc) du réseau, si par exemple vous utilisez cela de votre bureau, dévoile aussi sûrement vos secrets que si vous les aviez envoyé à tout le monde par courriel. Pour parfaire notre protection il est donc obligatoire de mettre en œuvre le protocole HTTPS, ce que j'aborderais dans un autre article.

Authentification via PAM

A mettre à jour:

12   <Directory /var/www/mon_site>
13     AllowOverride None
14     AuthBasicAuthoritative Off
15     AuthType Basic
16     AuthName "Access protected"
17     AuthUserFile /dev/null
18     AuthPAM_Enabled On
19     AuthPAM_FallThrough Off
20     require user gaston
21     require user robert
22
23     Order allow,deny
24     Allow from all
25   </Directory>

Ceci ne fonctionne que si l'utilisateur www-data est membre du groupe shadow !!!

Il y a deux modules qui permettent de s'authentifier via PAM à partir d'apache. Un module qui semble "fait pour", mod_auth_pam et pam_auth_external.

Le problème est que le module "fait pour" est en fin de vie, n'est pas compatible avec les dernières évolutions d'apache 2.2 et comporte quelques risques de sécurité (accès à /etc/shadow). En somme le choix se réduit à mod_authnz_external qui permet une authentification par un programme externe, tant pour l'utilisateur que pour le groupe. Ce module est actif et présent dans les dépôts (ce qui n'est pas le cas pour Mandriva de mod_auth_pam). Enfin il est fournit (via dépendance) avec ce qu'il faut pour s'authentifier contre les utilisateurs/groupes UNIX via PAM.

Pour l'installer

urpmi apache-mod_authnz_external

L'installation ajoute /etc/httpd/modules.d/10_mod_authnz_external.conf qui permet de charger le module. Cependant si comme moi vous n'utilisez pas ce système et préférez gérer ce genre de chose dans vos propres fichiers de configuration, il faut ajouter dans /etc/httpd/conf/httpd.conf (ou un include associé) :

LoadModule authnz_external_module       extramodules/mod_authnz_external.so

Côté configuration, il n'y a rien de très compliqué. Il nous faut juste ajouter dans la configuration globale d'apache (ou dans un vhost) le paramétrage des programmes externes à utiliser :

# authentification d'un utisateur
AddExternalAuth pwauth /usr/bin/pwauth
SetExternalAuthMethod pwauth pipe

# authentification d'un group
AddExternalGroup unixgroup /usr/bin/unixgroup
SetExternalGroupMethod unixgroup environment

Le concept est assez simple, AddExternalAuth et AddExternalGroup permet d'ajouter un nouveau fournisseur d'authentification externe (ici pwauth et unixgroup). SetExternalAuthMethod règle la méthode permettant de "discuter" avec l'application externe. Dans le cas de pwauth le mode pipe indique que le login/mot de passe de l'utilisateur sont redirigé sur un programme, /usr/bin/pwauth qui va renvoyer un succès ou une erreur.

Pour les groupes, la méthode utilisé est le réglage de variables d'environnement et l'exécution dans ce contexte de /usr/bin/unixgroup.

Ceci fait, il faut ajouter le paramétrage d'authentification pour un dossier (voir ce tutoriel), ici pour le webmail :

Alias /webmail /var/www/squirrelmail/
<Directory "/var/www/squirrelmail/">
  Order allow,deny
  Allow from all

  AuthBasicProvider external
  AuthExternal pwauth
  GroupExternal unixgroup

  AuthType Basic
  AuthName "Courriels"

  require group utilisateurs-du-webmail
</Directory>

Ici la validation se fait sur l'appartenance à une group, nous aurions pu utiliser require user ..., require valid-user ou encore require valid-group.

AuthBasicProvider permet à Apache de sélectionner le bon module d'authentification. Les deux directives suivantes font référence aux deux définitions de fournisseurs externes que nous avons définit plus haut. Le reste est du grand classique.

Dans le cas d'une validation simplement du groupe, on se dit légitimement qu'il n'y a pas d'intérêt à définir un fournisseur pour les utilisateurs. Enlever le et vous obtiendrez une erreur serveur avec comme message dans les logs No AuthExternal name has been set.

La raison en est assez simple, les deux définitions ne sont pas des fournisseurs autonomes mais deux étapes d'un processus. AuthExternal est le programme qui va authentifier le login/mot de passe reçu et fournir un utilisateur valide. GroupExternal permet de valider un des groupes de cet utilisateur. Du coup, si AuthExternal est obligatoire et GroupExternal n'est nécessaire que si vous utilisez require group.

Et c'est donc AuthExternal via pwauth qui va effectivement travailler avec PAM par le service /etc/pam.d/pwauth

#%PAM-1.0
auth       include      system-auth
auth       required     pam_nologin.so
account    include      system-auth
password   include      system-auth
session    include      system-auth
session    optional     pam_console.so

ici nous voyons bien que l'authentification utilise le tronc commun system-auth. Le service fonctionne donc exactement comme n'importe quelle application utilisant PAM.

mod_authnz_external est un outil intéressant à plus d'un titre car il apporte beaucoup de souplesse à l'auhentification Apache, ouvrant ainsi une porte sur des applications custom de validation. unixgroup est d'ailleurs un très simple script rédigé en Perl.

Pour le reste, PAM continue de m'épater par son universalité. La suppression du serveur LDAP a du me prendre une dizaine de minutes sans qu'aucun changement ne soit à faire dans les applications. Sauf pour Apache... qui n'utilisait pas encore PAM ;-)

Filtrage par variables

Apache dispose d'un système de directives très complet pour étudier les variables liées à une requête (User Agent, Referer, etc) et en créer de nouvelles nous permettant de filtrer les accès dans des cas très particuliers.

Empêche le téléchargement de vos images

Un problème classique est de se retrouver avec une bande passante "volée" parce qu'un Malotru a eu l'indélicatesse de mettre vos images en référence directe sur son site. Ici, pas question de mettre un mot de passe qui bloquerait les utilisateurs légitimes. Les techniques précédentes sont donc inutiles. A la place nous allons utiliser les variables dont nous disposons au sein d'Apache et particulièrement du Referer.

Pour ceux qui ne le savent pas, un navigateur WEB a pour obligation de transmettre au serveur l'adresse WEB de la page qui contient le lien qui a permis d'arriver sur notre site. Cette indiscrétion est déjà bien connu et c'est elle qui permet de savoir d'où viennent nos visiteurs. Cette adresse est le fameux Referer.

Ce qui est un peu moins connu c'est que chaque élément lié à une page de notre site (ex. une image ou une feuille de style), est demandée au serveur distant avec lui aussi un Referer. Sauf que cette fois, c'est l'adresse de notre propre site qui est passé par le navigateur, ce qui est somme toute très logique.

En d'autres termes, toute demande d'image n'ayant pas notre site pour Referer, est sûrement l'objet d'un vol de bande passante… C'est cette caractéristique que nous allons exploiter avec ce code qui peut aussi bien être, comme toujours, dans un .htaccess qu'un fichier de configuration Apache.

SetEnvIfNoCase Referer "^http://www\.monsite\.net/" acces_local=1
<FilesMatch ".(gif|jpg|png|pdf|css|js)">
  Deny from all
  Allow from env=acces_local
</FilesMatch>

Ce qui se passe c'est que si le Referer est bien notre site, nous positionnons une variable acces_local à 1. Ensuite nous ajoutons une règle qui interdit le téléchargement des images (Deny from all) sauf pour les requêtes qui ont la variable acces_local définie. Voilà, c'est tout. La directive FilesMatch est elle là pour n'opérer cette sécurité que sur les fichiers images, feuilles de style, scripts, etc. Il serait en effet un peu idiot d'appliquer cette règles au fichier .php par exemple, cela interdirait systématique l'accès du site à tout le monde….

Empêcher certains navigateurs

Vous aurez remarqué dans l'exemple précédent la syntaxe un peu étrange du paramètre de la directive SetEnvIfNoCase. On appelle cela une expression régulière. Une expression régulière est une sorte de motif permettant de décrire une chaîne de caractère attendue. On peut ainsi créer des expressions régulières décrivant un numéro de téléphone ou une adresse courriel. Dans l'exemple précédent, elle signifiait "toute les URL qui commencent par (symbole ^) http://www.monsite.net/". Les \. étaient là pour indiquer que l'on voulait que le vrai caractère . soit utilisé, car sinon, ce symbole a un sens particulier dans une expression régulière.

Maintenant imaginons qu'un vilain spammeur qui me fasse des misères et que son adresse IP varie de 81.95.144.X à 81.95.147.X. Je peux le bloquer par la configuration suivante :

SetEnvIf Remote_Addr "^81\.95\.14[4-7]\." bad_bot
Deny from env=bad_bot

C'est à peu prés le même exemple que précédemment sauf que cette fois on utilise, non pas Referer mais Remote_Addr qui est l'adresse IP du navigateur client. Je ne vais pas faire ici un cours sur les expressions régulière, il y a de magnifique tutoriaux sur ce sujet.

Juste un dernier exemple, si cette fois c'est la chaîne d'identification (User Agent) d'un navigateur que je veux bloquer, par exemple un bot malveillant :

BrowserMatchNoCase ".*HTTrack*" bad_bot
Deny from env=bad_bot

Là, j'utilise une directive un peu spéciale qui a le même rôle que les précédentes mais cette fois sur la variable User_Agent sans distinction de majuscules/minuscules.

Bien évidement, vous pouvez mettre une liste complète de définitions avant d'insérez votre directive Deny :

BrowserMatchNoCase ".*HTTrack*" bad_bot
BrowserMatchNoCase ".*ICC-Crawler*" bad_bot
BrowserMatchNoCase ".*Nutch*" bad_bot
SetEnvIf Remote_Addr "^82\.246\.40\.31" bad_bot # un gars de free...
SetEnvIf Remote_Addr "^195\.69\.171\.86" bad_bot # mirotel.net
Deny from env=bad_bot

Cette méthode n'est pas aussi fiable que je le voudrais car les adresses change, et les chaînes d'identification de navigateur aussi. Mais au moins est-ce un moyen de contrer efficacement une attaque ponctuelle. Pour une liste complète des directives touchant à la définition de variables, je vous renvoie à la documentation d'Apache.

Filtrage par adresse IP

Même si cela fonctionne, passer par les variables pour interdire une IP avec des expressions régulières est un peu sauvage pour un usage courant. Si vous voulez pour votre dossier, ne soit accessible que pour une seule IP, ou un groupe d'IP, nous pouvons plus simplement utiliser la directive Deny From. Par exemple ici, pour n'autoriser que l'IP 192.168.0.10 nous utiliserons la configuration suivante :

Deny from all
Allow from 192.168.0.10

Les masques sont utilisables ici pour étendre à tout un sous réseau. Ainsi pour autoriser le réseau 192.168.X.X :

Deny from all
Allow from 192.168.0.0/16

Nous pouvons assouplir la règle en enlevant un group de chiffre à la fin de l'IP. Par exemple Allow from 192.168.0 autorise les adresses allant de 192.168.0.0 à 192.168.0.255, à se connecter.

Protections mixtes

Pour finir, un petit mélange. Imaginons que nous protégions notre dossier par mot de passe via LDAP mais qu'en même temps nous ayons besoins que les clients se connectant d'un adresse IP spécifique, puise le faire sans mot de passe. Cela nous donnerais le code suivant :

Allow from 192.168.0.10
AuthType Basic
AuthName "Acces au dossier"
AuthBasicProvider ldap
AuthLDAPURL ldap://localhost:389/dc=mon-domaine,dc=com
AuthLDAPGroupAttribute memberUid
AuthLDAPGroupAttributeIsDN off
Require ldap-group cn=webmaster,ou=Group,dc=mon-domaine,dc=com
Satisfy Any

Rien d'inconnu maintenant dans ces syntaxes, à l'exception de la dernière ligne, qui fait toute la différence. En effet, elle indique à Apache que l'une ou l'autre des conditions suffit à autoriser l'accès : soit l'IP, soit le mot de passe.

Satisfy Any peut aussi nous aider dans des situations beaucoup moins classiques. Imaginons par exemple que nous désirions protéger la racine d'un site par une authentification, mais que nous voulions rendre accessible à tous un dossier particulier. Cela se fait très simplement en autorisant toutes les IP comme ceci :

<directory "/">
  AuthType Basic
  AuthName "Accès protégé"
  AuthUserFile "/mon_chemin_vers_mots_de_passe/passwd"
  require valid-user
</directory>

<directory "DossierLibre">
  Allow from 0.0.0.0/32
  Satisfy Any
</directory>

Ici nous voyons que pour apache, les protections des dossiers parents s'appliquent aux dossiers enfant. Nous ajoutons donc une nouvelle règle à notre DossierLibre qui autorise toutes les connections.

Conclusion

L'article est déjà long et nous sommes loin d'avoir totalement couvert le sujet. J'espère seulement que cela vous donnera assez de pistes pour trouver la solution qui convient le mieux à vos besoins. L'agressivité et l'automatisation des attaques comme en témoignent souvent les fichiers de traces, prouvent que ce genre de précaution est simplement obligatoire. Il n'y a à ma connaissance pas de moyens systématique de valider qu'un serveur est correctement fermé, et ce qui est réellement ouvert. Google permet par le biais d'une requête de type site:mon-site.com d'avoir la liste de ce qu'il voit mais cela se limite à ce qui a été lié, quelque part, sur le Web, ce qui est loin d'être suffisant.