Sécurisation complète d'un serveur web : de vulnérable à blindé

Comment transformer un serveur web exposé sous Nginx en forteresse sécurisée : Fail2ban, ModSecurity, CSP, HTTPS, headers de sécurité. Résultats : -95% d'attaques, score A+ SSL Labs, 0 incident en 6 mois.

Client : PME e-commerce (secteur retail) Duree : 3 semaines

Résultats clés

Attaques bloquées
Avant ~1 200/j Après ~60/j
Score SSL Labs
Avant C Après A+
Incidents sécurité
Avant 3/mois Après 0 en 6 mois
Temps de réponse
Avant ~420 ms Après ~180 ms

Contexte

Une PME du secteur e-commerce héberge son site vitrine et sa plateforme de commande sur un serveur dédié sous Debian 12 avec Nginx. Le serveur gère environ 15 000 visiteurs uniques par jour et traite des données clients sensibles : coordonnées, historiques de commandes et informations de livraison.

L'équipe technique, composée de deux développeurs full-stack, n'avait pas d'expertise spécifique en sécurité système. Le serveur avait été mis en production deux ans plus tôt avec une configuration Nginx par défaut, sans aucune couche de protection supplémentaire. Les mises à jour système étaient appliquées de manière irrégulière, environ une fois par trimestre.

Le déclencheur de cette mission : une tentative d'intrusion détectée par hasard dans les logs Apache (ancien serveur), suivie de la découverte que le serveur actuel sous Nginx était dans un état similaire. La direction a alors décidé d'investir dans un audit et un durcissement complet.

Problème

L'audit initial a révélé une surface d'attaque considérable. Voici les principaux constats :

Exposition réseau excessive

Le serveur exposait 14 ports ouverts sur Internet, dont plusieurs services non nécessaires : un serveur FTP (vsftpd) encore actif, un service MySQL accessible depuis l'extérieur sur le port 3306, et un daemon de supervision Munin sans authentification. SSH était configuré sur le port par défaut (22) avec l'authentification par mot de passe activée.

Configuration Nginx non durcie

La configuration Nginx était quasi identique à celle par défaut. Les en-têtes de sécurité étaient absents : pas de Content-Security-Policy, pas de X-Frame-Options, pas de Strict-Transport-Security. Le serveur divulguait sa version exacte via le header Server. Les pages d'erreur personnalisées n'existaient pas, exposant des traces de stack en cas d'erreur 500.

Certificat SSL minimal

Un certificat Let's Encrypt était en place, mais la configuration TLS n'avait pas été optimisée. Les protocoles TLS 1.0 et 1.1 étaient encore acceptés. Les cipher suites incluaient des algorithmes obsolètes comme RC4 et 3DES. Le score SSL Labs était un C, ce qui est insuffisant pour un site traitant des données clients.

Absence de protection applicative

Aucun pare-feu applicatif (WAF) n'était en place. Les requêtes malveillantes (injections SQL, XSS, path traversal) atteignaient directement l'application PHP. Les logs révélaient en moyenne 1 200 requêtes malveillantes par jour, dont une part significative de scans automatisés par des botnets.

Pas de surveillance active

Aucun outil de détection d'intrusion n'était configuré. Les logs système n'étaient ni centralisés ni surveillés. Il n'y avait pas de mécanisme de blocage automatique des IP suspectes. Les tentatives de brute-force SSH étaient visibles dans les logs, mais personne ne les consultait.

Solution

L'approche a été structurée en 5 phases, déployées sur 3 semaines avec un suivi quotidien et des tests de non-régression à chaque étape.

Phase 1 : Réduction de la surface d'attaque

La première action a été de fermer tous les ports non nécessaires. Le firewall système a été configuré avec iptables pour n'autoriser que les ports 22 (SSH), 80 (HTTP) et 443 (HTTPS). Le service FTP a été désactivé et remplacé par des transferts SFTP via le serveur SSH existant. MySQL a été restreint à l'écoute sur 127.0.0.1 uniquement.

# Politique par defaut : tout bloquer
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Autoriser le loopback
iptables -A INPUT -i lo -j ACCEPT

# Autoriser les connexions etablies
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# SSH, HTTP, HTTPS uniquement
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# Sauvegarder les regles
iptables-save > /etc/iptables/rules.v4

Le port SSH a été déplacé sur un port non standard (au-dessus de 10000) pour réduire le bruit des scans automatisés. Cette mesure seule a réduit les tentatives de brute-force SSH de 85%.

Phase 2 : Durcissement de la configuration Nginx

La configuration Nginx a été entièrement révisée. Les en-têtes de sécurité ont été ajoutés globalement dans le bloc http :

# /etc/nginx/conf.d/security-headers.conf

# Masquer la version du serveur
server_tokens off;

# En-tetes de securite
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "0" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

# Content-Security-Policy stricte
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$csp_nonce'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'" always;

# HSTS avec preload
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

La directive server_tokens off empêche Nginx de divulguer sa version. Les pages d'erreur personnalisées ont été créées pour éviter les fuites d'information. La taille maximale des corps de requête a été limitée à 10 Mo via client_max_body_size.

Phase 3 : Configuration TLS optimale

La configuration TLS a été durcie pour atteindre un score A+ sur SSL Labs. Les protocoles obsolètes ont été désactivés et les cipher suites ont été restreintes aux algorithmes modernes :

# /etc/nginx/conf.d/ssl.conf

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;

# Session cache pour les performances
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

Le certificat Let's Encrypt a été reconfiguré avec certbot pour utiliser des clés ECDSA (P-256), plus performantes que les clés RSA classiques. Le renouvellement automatique a été vérifié et un timer systemd dédié a été mis en place pour s'assurer que le certificat ne puisse jamais expirer sans alerte.

Phase 4 : Déploiement du WAF (ModSecurity)

ModSecurity a été installé comme module dynamique pour Nginx avec le jeu de règles OWASP Core Rule Set (CRS) v4. Cette couche intercepte et bloque les requêtes malveillantes avant qu'elles n'atteignent l'application PHP.

# Installation de ModSecurity pour Nginx
apt install libmodsecurity3 libmodsecurity-dev

# Activation du CRS OWASP
git clone https://github.com/coreruleset/coreruleset /etc/modsecurity/crs
cp /etc/modsecurity/crs/crs-setup.conf.example /etc/modsecurity/crs/crs-setup.conf

# Configuration Nginx
modsecurity on;
modsecurity_rules_file /etc/modsecurity/main.conf;

Le déploiement a été fait en mode DetectionOnly pendant 5 jours pour identifier les faux positifs. Trois règles ont dû être ajustées pour ne pas bloquer les opérations légitimes du back-office (upload d'images et sauvegarde de produits avec du HTML). Après ajustement, le mode On a été activé.

Phase 5 : Détection et blocage automatique

Fail2ban a été déployé avec des jails personnalisées pour Nginx, SSH et ModSecurity. La configuration cible les comportements suspects : tentatives d'authentification échouées, scans de répertoires et requêtes bloquées par le WAF.

# /etc/fail2ban/jail.d/nginx-custom.conf

[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 3600

[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 86400

[modsecurity]
enabled = true
port = http,https
filter = modsecurity
logpath = /var/log/modsecurity/audit.log
maxretry = 2
bantime = 86400

Un script de reporting quotidien a été mis en place pour envoyer un résumé des IP bannies, des règles WAF déclenchées et des tentatives d'intrusion détectées. Ce rapport est envoyé par email à l'équipe technique chaque matin.

Résultats

Les mesures déployées ont produit des résultats tangibles et mesurables dès les premières semaines.

Réduction drastique des attaques

Le nombre de requêtes malveillantes atteignant l'application est passé de 1 200 par jour à environ 60. Les 60 restantes sont des requêtes qui passent les filtres du WAF mais sont neutralisées par la CSP et les autres mécanismes de défense en profondeur. Le taux de blocage effectif de ModSecurity est de 95,2% sur les 6 premiers mois.

Score SSL Labs A+

La configuration TLS durcie a fait passer le score SSL Labs de C à A+. Le serveur supporte uniquement TLS 1.2 et 1.3 avec des cipher suites modernes. L'OCSP Stapling est fonctionnel et le header HSTS avec preload est en place. Le site a été soumis à la liste de preloading HSTS de Chromium.

Zéro incident en 6 mois

Depuis le déploiement, aucun incident de sécurité n'a été enregistré. Avant l'intervention, l'équipe faisait face à 2-3 incidents par mois (défacements de pages, injections dans la base de données, tentatives de phishing via des fichiers uploadés). La combinaison WAF + CSP + headers de sécurité a éliminé ces vecteurs d'attaque.

Amélioration des performances

Effet secondaire positif : le temps de réponse moyen est passé de 420 ms à 180 ms. L'élimination du trafic malveillant a libéré des ressources serveur. L'activation de l'OCSP Stapling a supprimé la latence liée à la vérification OCSP côté client. Le cache de sessions TLS évite les renégociations coûteuses.

Leçons apprises

La sécurité par défaut n'existe pas

Les configurations par défaut de Nginx, comme celles de la plupart des serveurs, sont optimisées pour la compatibilité, pas pour la sécurité. Chaque nouveau serveur devrait passer par une phase de durcissement avant la mise en production. Un template de configuration sécurisée, maintenu et versionné, est un investissement qui se rentabilise dès la première instance déployée.

Le mode détection avant le mode blocage

Déployer ModSecurity directement en mode blocage aurait causé des interruptions de service. Les 5 jours en mode DetectionOnly ont permis d'identifier 3 règles générant des faux positifs sur des opérations métier légitimes. Ce temps d'observation est non négociable pour tout déploiement de WAF en production.

La défense en profondeur est la seule stratégie viable

Aucune mesure isolée ne suffit. Fail2ban seul ne suffit pas. Un WAF seul ne suffit pas. C'est l'empilement de couches complémentaires (firewall réseau, WAF, headers HTTP, TLS durci, détection d'intrusion, monitoring) qui crée une posture de sécurité robuste. Chaque couche couvre les faiblesses de la précédente.

Automatiser la surveillance

La mise en place de rapports quotidiens automatisés a changé la dynamique de l'équipe. Au lieu de découvrir les incidents à posteriori, l'équipe technique reçoit chaque matin un tableau de bord clair des menaces détectées et bloquées. Cette visibilité a aussi eu un effet pédagogique : les développeurs ont commencé à intégrer des pratiques de sécurité dans leur code applicatif.

Documenter chaque changement

Chaque modification de configuration a été documentée dans un fichier changelog versionné. Cette documentation s'est révélée précieuse lors d'un incident de performance 3 mois plus tard : l'équipe a pu rapidement identifier qu'une mise à jour du CRS OWASP avait introduit une règle trop agressive, et la désactiver en quelques minutes grâce au changelog.

Stack technique finale

Composant Outil Rôle
OS Debian 12 Système d'exploitation serveur
Serveur web Nginx 1.24 Reverse proxy + serveur HTTP
WAF ModSecurity 3 + CRS v4 Filtrage des requêtes malveillantes
IPS Fail2ban Blocage automatique des IP suspectes
Firewall iptables Filtrage réseau (ports 22, 80, 443)
TLS Let's Encrypt (ECDSA) Certificats avec renouvellement auto
Monitoring Scripts custom + email Rapports quotidiens de sécurité

FAQ

Combien coute une telle sécurisation ?

Tous les outils utilisés sont open source et gratuits. Le coût réel est le temps d'expertise : 3 semaines de travail pour l'audit, le déploiement et les tests. Pour une PME, c'est un investissement modeste comparé au coût d'une violation de données (en moyenne 150 000 euros pour une PME selon l'ANSSI).

ModSecurity ralentit-il le serveur ?

L'impact sur les performances est mesurable mais négligeable dans la plupart des cas. Sur ce projet, l'ajout de ModSecurity a ajouté environ 5 ms de latence par requête. Ce surcoût est largement compensé par la réduction du trafic malveillant, qui libérait des ressources serveur. Le temps de réponse global a en fait diminué.

Pourquoi ne pas utiliser un CDN avec WAF intégré ?

Les solutions CDN comme Cloudflare offrent une protection efficace mais introduisent une dépendance à un tiers. Pour cette PME, la maîtrise complète de l'infrastructure était une priorité. De plus, un WAF local permet un ajustement fin des règles adapté à l'application spécifique, ce que les WAF génériques ne permettent pas toujours.

Comment maintenir cette sécurisation dans le temps ?

La sécurisation n'est pas un événement ponctuel, c'est un processus continu. Les mises à jour du CRS OWASP sont appliquées mensuellement. Les règles Fail2ban sont ajustées trimestriellement en fonction des tendances d'attaque observées. Un audit complet est planifié tous les 6 mois. Les rapports quotidiens permettent de détecter toute anomalie en temps réel.

Ces mesures sont-elles suffisantes pour la conformité RGPD ?

Ces mesures constituent une base solide de sécurité technique, mais la conformité RGPD va au-delà de la sécurité serveur. Elle inclut la gestion des consentements, la politique de rétention des données, les procédures de notification en cas de violation, et la documentation des traitements. La sécurisation du serveur est une brique nécessaire mais pas suffisante.

Commentaires

Morgann Riu

Expert en cybersécurité et administration Linux. J'aide les entreprises a sécuriser et optimiser leurs infrastructures critiques.

Toutes les etudes de cas