Mise à jour du 14/11/2024.
Avec une certaine expérience des serveurs d'EJB (JBoss, WebLogic, etc.) et du serveur Tomcat, il nous est vite apparu que l'aspect sécuritaire est souvent négligé dans la conception amont sans doute pour permettre une mise en oeuvre rapide et fiable des développements. Ceci conduit à mettre ensuite en place des "rustines" ou des modèles de sécurité souvent complexes à maitriser. Nous en avons tiré une règle : plus la sécurité est proche du flux reçu et plus elle est efficace. En effet, plus on laisse l'analyse de ce flux remonter les couches et plus les failles de sécurité potentielles seront nombreuses. C'est pourquoi le serveur PAS intègre un agent de sécurité qui intervient dès que le flux de la requête est livré avant même d'être orienté au sein du serveur. Cet agent de sécurité joue un rôle clef dans la protection des applications.
La librairie webapp fournit le code d'un agent de sécurité par défaut via la classe SecurityAgent. Cet agent remplit les fonctions décrites dans l'article Organisation du code de lancement du serveur d'application.
Cet agent de sécurité trace les requêtes refusées dans un fichier dédié et le serveur se limite à indiquer au correspondant le refus d'exécution (sous forme d'une réponse de type MIME text/plain) assortie d'un statut 406 (requête innaceptable). Il est néanmoins possible à l'hôte qui a émis une requête refusée d'en réémettre d'autres : l'agent de sécurité proposé ici n'effectue aucun blocage.
La trace est destinée à être lue par une personne. A ce titre, elle est volontairement synthétique ce qui entraine une perte de données dont les en-têtes HTTP. Si vous avez déjà observé un journal du serveur Apache, vous avez sans doute constaté que nombre de requêtes cherchent à passer sous le "radar". Pour cela, elle évitent d'ajouter des paramètres visualisables via la méthode GET et donc enregistrables dans le journal. Ces paramètres sont transmis dans le bloc HTTP (en-têtes ou corps) transporté par la requête généralement via une méthode GET ou POST.
C'est pour cela qu'en plus des traces destinées aux administrateurs, le système peut tenir à jour une table qui référence des fichiers. Chaque entrée de la table contient l'horodatage de l'enregistrement, l'adresse IP de l'hôte distant, l'URL complète reçue et une valeur entière. La valeur entière est le suffixe avant extension de deux fichiers :
L'ensemble de ces données permet la réinterprétation fine des échanges a posteriori. Comme ce mécanisme est gourmand en ressources, il n'est activé que sur demande via le fichier de configuration.
L'agent de sécurité est déclaré dans le fichier de configuration du serveur. La paramètre principal est agent.class qui déclare le nom de la classe de l'agent de sécurité. Si ce paramètre est omis, c'est la classe SecurityAgent de la librairie webapp qui sera utilisé. Cet agent a besoin d'autres paramètres pour affiner son fonctionnement. Ces paramètres figurent dans le tableau qui suit :
| Nom du paramètre | Desciption de la valeur |
|---|---|
| agent.lib | Nom du fichier qui contient la librairie dynamique supportant l'agent de sécurité. Si vous utiliser l'agent de sécurité interne à la librairie webappce paramètre ne doit pas être défini. |
| agent.class | Ce paramètre donne le nom de la classe dérivée de IInstance qui représente l'agent de sécurité. Si ce paramètre n'est pas défini, c'est la classe SecurityAgentqui sera utilisée comme agent de sécurité. |
| agent.maxsize | Paramètre spécifique à la classe SecurityAgent. Taille maximale en octets d'un fichier journal. |
| agent.kept | Paramètre spécifique à la classe SecurityAgent. Nombre de fichiers conservés (mécanisme de rotation). |
| agent.filter | Paramètre spécifique à la classe SecurityAgent. Niveau de filtrage des traces : {debug, info, warning, error}. |
| agent.timeout | Paramètre spécifique à la classe SecurityAgent. Nombre de jours d'enregistrement avant rotation si agent.maxsizenon atteint. |
| agent.tee | Paramètre spécifique à la classe SecurityAgent. Affichage sur console en plus de l'enregistrement : {0, 1}. |
| agent.banner.title | Paramètre spécifique à la classe SecurityAgent. Titre de la bannière du journal inscrite à chaque ancement de l'application. |
| agent.rule.{N} | Paramètre spécifique à la classe SecurityAgent. Nième règle de filtrage (cf. règles de filtrage ci-dessous). |
Une règle de filtrage est constituée de 3 parties séparées par un double-point :
Par exemple pour filter les requêtes émises par zgrab, vous pouvez construire la règle :
agent.rule.1=zgrab:zgrab:headers
La variable N indique le Numéro de la règle. La première a le numéro 1 et les numéros doivent se suivre au pas de 1. A la première interruption de séquence, la lecture des règles est interrompue mais les règles déjà lues sont conservées.
Le plus simple est sans doute de le faire dériver de la classe SecurityAgent. Ce n'est cependant pas une obligation. La seule véritable obligation est de le faire hériter de l'interface IInstance afin de pouvoir l'instancier sans sa connaisance préalable par l'infrastructure (classe AppServer).
Son chargement sera effectif si le nom de sa classe est déclarée dans le fichier de configuration du serveur via le paramètre agent.class. Vous pouvez ensuite placer les paramètres de configuration propres à votre agent dans ce même fichier. En effet, lors de son instanciation via la méthode newInstance(), l'ensemble des paramètres de configuration du serveur lui seront transmis.
Développer son propre agent est probablement nécessaire dans deux cas :
Dans les autres cas (sans doute le cas général), il est sans doute préférable d'écrire une application qui met en place un "chien de garde" (watchdog) qui s'active chaque fois qu'un ou plusieurs fichiers journaux sont modifiés.
Lors de cette activation, l'adresse IP dont le comportement douteux a été identifié peut alors être bloqué provisoirement ou définitivement selon le cas par l'application. Cette dernière peut par exemple jouer directement sur les règles du pare-feu.
Sous Linux, l'application peut dynamiquement modifier les règles de filtrage des adresses via la commande iptables ou encore mieux via un jeu dédié de la commande ipset, jeu activé dans les règles iptables. Cette dernière façon de faire évite de modifier les règles de la commande iptablesparfois complexes à interpréter.
Rédaction par Jean-Marie Piatte (1983-2021)