ModSecurity avec le jeu de règles OWASP CRS s’active en dix minutes et se désactive trois jours plus tard — après que les articles ont cessé d’être enregistrés dans l’administration, que le téléversement de fichiers a cassé et qu’un client n’a pas pu passer commande parce que son adresse contenait une apostrophe. La conclusion « le WAF empêche de travailler » vient d’elle-même, et elle est fausse : presque tous ces blocages se règlent avec trois ou quatre exceptions précises, et toute l’affaire consiste à les trouver correctement.
N’activez pas le blocage tout de suite
La première semaine est consacrée à l’observation seule. Dans /etc/modsecurity/modsecurity.conf :
SecRuleEngine DetectionOnly
Dans ce mode, le WAF consigne tout ce qu’il aurait bloqué et ne bloque rien. Une semaine de trafic réel — y compris votre propre travail dans l’administration, le téléversement d’images et une commande — donne la liste des vrais faux positifs plutôt que des hypothétiques. Passer à On n’a de sens qu’une fois cette liste traitée.
Comment fonctionne le CRS : non pas une règle, mais une somme
C’est la clé de tout ce qui suit. Le CRS ne bloque presque jamais une requête avec une seule règle. Chaque règle déclenchée ajoute des points d’anomalie, et le blocage survient quand la somme dépasse un seuil. C’est pourquoi le journal ne montre pas une ligne mais plusieurs — et la dernière est la règle d’identifiant 949110, celle qui fait le total.
Conséquence pratique : l’exception doit viser la règle qui a marqué les points, et non 949110. Désactivez la règle de totalisation et vous désactivez le jeu entier, ne laissant du WAF qu’une ligne dans un fichier de configuration.
Seconde conséquence : le niveau de paranoïa. Il vaut un par défaut, et c’est le bon choix. Les niveaux 2 et 3 ajoutent des règles qui, par construction, produisent des faux positifs sur des sites ordinaires ; les activer n’a de sens qu’une fois le niveau un entièrement réglé.
Trouver la règle fautive
Tout le nécessaire se trouve dans le journal d’audit (/var/log/modsec_audit.log) et dans le journal d’erreurs du serveur web. Cherchez par l’heure du blocage :
sudo grep -o 'id "[0-9]*"' /var/log/modsec_audit.log | sort | uniq -c | sort -rn | head
C’est une liste des règles déclenchées par fréquence. Ensuite, pour un identifiant précis, regardez sur quoi exactement elle s’est déclenchée :
sudo grep -A5 'id "942100"' /var/log/modsec_audit.log | head -40
Trois éléments sont nécessaires : l’identifiant de la règle, le nom du paramètre (ARGS:content, ARGS:comment) et le chemin de la requête. C’est de là que se construit l’exception.
Les suspects habituels
La liste se répète d’un site à l’autre :
- 942100 — injection SQL. Se déclenche sur les champs de texte au contenu long : corps d’article, description de produit, commentaire. Les guillemets, les parenthèses et des mots comme
selectparaissent suspects au détecteur même dans de la prose ordinaire ; - 941100 et la famille 941xxx — XSS. Elles arrivent avec l’éditeur visuel : les balises HTML dans un champ sont précisément le sens de son travail ;
- 920420 — Content-Type non autorisé. Casse les API et le téléversement : le jeu de types permis par défaut est étroit, et dans les versions anciennes
application/jsonn’en faisait pas partie ; - 913100 — scanner d’après le User-Agent. Attrape, en même temps que les scanners, des outils légitimes : la surveillance de disponibilité, curl dans vos propres scripts ;
- 200002, 200004 — erreurs d’analyse du corps de la requête. Elles signifient d’ordinaire non pas une attaque mais une limite de taille dépassée, c’est-à-dire le téléversement d’un gros fichier.
Trois façons de créer une exception
Par ordre de brutalité croissante. Toutes vont dans votre propre fichier (par exemple /etc/modsecurity/crs/REQUEST-900-EXCLUSION-RULES.conf) et non dans les fichiers du CRS : le jeu de règles est mis à jour, et vos modifications disparaîtraient avec lui.
Retirer un paramètre d’une règle. La variante la plus précise, et celle qu’il faut viser :
SecRuleUpdateTargetById 942100 "!ARGS:content"
Désactiver une règle sur un seul chemin. Convient quand une page précise fait du bruit — un éditeur, un import, un formulaire d’avis :
SecRule REQUEST_URI "@beginsWith /admin/post" \
"id:1001,phase:1,pass,nolog,ctl:ruleRemoveById=942100"
Désactiver la règle entièrement. Dernier recours et presque toujours le signe que la cause n’a pas été trouvée :
SecRuleRemoveById 942100
La différence entre la première et la troisième est considérable. Dans le premier cas, un champ — le texte de l’article — cesse d’être contrôlé pour l’injection SQL par une règle ; dans les deux autres, c’est tout le site qui cesse d’être contrôlé. La différence d’effort est d’environ cinq minutes.
Après les modifications, vérifiez la configuration et rechargez en douceur :
sudo apachectl configtest && sudo systemctl reload apache2
sudo nginx -t && sudo systemctl reload nginx
Un ordre qui fait gagner une semaine
- Une semaine en
DetectionOnlyavec un travail normal sur le site, administration et téléversements compris. - La liste des règles par fréquence depuis le journal d’audit. À traiter de haut en bas — les trois ou quatre premières font quatre-vingt-dix pour cent du bruit.
- Pour chacune : déterminer quel paramètre sur quelle page. Créer l’exception par paramètre, pas par règle.
- Seulement maintenant,
SecRuleEngine On. - Une fois par mois, jeter un œil aux blocages : le site a changé, donc de nouveaux faux positifs sont apparus.
C’est sur ce dernier point que tout achoppe d’ordinaire : le journal d’audit représente des gigaoctets de texte que personne ne lira à la main. L’intérêt d’un tableau de bord est d’avoir sous les yeux la liste des règles déclenchées, les requêtes bloquées et le jeu de règles actif, au lieu de les extraire avec grep. À quoi cela ressemble, la page de démonstration ci-dessous le montre.