Drupal — ModSecurity et OWASP CRS : réglage et exclusions
Déploiement de ModSecurity v3 avec l'OWASP Core Rule Set devant Drupal : paranoia level, anomaly scoring, exclusions ciblées et passage en mode blocage.
Mise en place de ModSecurity v3 avec l’OWASP Core Rule Set en frontal d’une instance Drupal, du mode détection au blocage avec exclusions ciblées.
Contexte
Un WAF applicatif fournit une couche de défense en profondeur contre les injections (SQLi, XSS) avant qu’elles n’atteignent PHP. L’OWASP CRS utilise un modèle de scoring d’anomalie : chaque règle déclenchée ajoute un score, la requête étant bloquée au-delà d’un seuil. Drupal génère légitimement des requêtes complexes (formulaires, JSON:API) qui produisent des faux positifs nécessitant des exclusions.
Détection
Activation en mode détection seule (DetectionOnly) pour collecter les déclenchements sans bloquer :
SecRuleEngine DetectionOnly
SecAuditEngine RelevantOnly
SecAuditLog /var/log/modsec/audit.log
SecAuditLogParts ABIJDEFHZ
Analyse des règles déclenchées et du score d’anomalie cumulé :
grep -aoE 'id "([0-9]+)"' /var/log/modsec/audit.log | sort | uniq -c | sort -rn | head -20
grep -a 'Inbound Anomaly Score Exceeded' /var/log/modsec/audit.log | wc -l
grep -aE 'msg "[^"]+"' /var/log/modsec/audit.log | sed -E 's/.*msg "([^"]+)".*/\1/' | sort | uniq -c | sort -rn
Corrélation entre une URI Drupal et la règle CRS responsable du faux positif :
grep -a -B5 'id "942100"' /var/log/modsec/audit.log | grep -a 'Request:'
Durcissement
Configuration du CRS avec un paranoia level progressif et un seuil de blocage explicite :
SecAction "id:900000,phase:1,nolog,pass,t:none,setvar:tx.blocking_paranoia_level=1"
SecAction "id:900110,phase:1,nolog,pass,t:none,setvar:tx.inbound_anomaly_score_threshold=5,setvar:tx.outbound_anomaly_score_threshold=4"
SecAction "id:900200,phase:1,nolog,pass,t:none,setvar:'tx.allowed_methods=GET HEAD POST PUT PATCH DELETE OPTIONS'"
Exclusions ciblées par règle et par chemin Drupal connues pour générer des faux positifs, à placer avant le chargement des règles CRS :
SecRule REQUEST_URI "@beginsWith /admin/structure" \
"id:1000010,phase:1,pass,nolog,ctl:ruleRemoveById=942430,ctl:ruleRemoveById=942440"
SecRule REQUEST_URI "@beginsWith /jsonapi" \
"id:1000020,phase:1,pass,nolog,ctl:ruleRemoveTargetById=942100;ARGS:filter"
SecRule REQUEST_FILENAME "@endsWith /node/add" \
"id:1000030,phase:2,pass,nolog,ctl:ruleRemoveById=941160"
Passage en mode blocage une fois les exclusions stabilisées :
SecRuleEngine On
SecDefaultAction "phase:2,deny,status:403,log,auditlog"
Limitation de la taille du corps et du body inspecté, pour contenir les uploads malveillants :
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyLimitAction Reject
Vérification
Test d’un payload SQLi générique, qui doit être bloqué (403) sans atteindre Drupal :
curl -s -o /dev/null -w '%{http_code}\n' "https://drupal.exemple.fr/?id=1%27%20OR%20%271%27=%271"
curl -s -o /dev/null -w '%{http_code}\n' "https://drupal.exemple.fr/?q=<script>alert(1)</script>"
Confirmation qu’une requête légitime d’administration n’est pas bloquée :
curl -s -o /dev/null -w '%{http_code}\n' -b cookie.txt "https://drupal.exemple.fr/admin/structure/types"
Le log d’audit ne doit plus comporter de déclenchements sur les chemins exclus, et le taux de 403 sur trafic légitime doit être nul après stabilisation.
Vous avez un projet sur ces sujets ?
Nous contacter →