# CLAUDE.md — Sécurisation serveur Linux Debian > Contexte spécialisé pour Claude Code. Coller ce fichier à la racine du projet pour auditer et sécuriser un serveur Debian dédié (SSH, fail2ban, firewall, auditd, Apache, monitoring automatisé). --- ## Section 1 : SSH hardening Configuration minimale à appliquer dans `/etc/ssh/sshd_config` : ```bash # Désactiver le login root par mot de passe (garder les clés pour urgence) PermitRootLogin prohibit-password # Logging SFTP détaillé dans auth.log Subsystem sftp internal-sftp -l INFO -f AUTH # Recharger sans coupure de service sshd -t && systemctl reload sshd ``` ### Groupe sftponly avec ChrootDirectory Isoler les utilisateurs SFTP dans leur home — ils ne peuvent ni exécuter de shell, ni accéder à d'autres répertoires. ```bash # Créer le groupe groupadd sftponly # Ajouter les utilisateurs SFTP usermod -aG sftponly alice usermod -aG sftponly bob ``` Bloc à ajouter **à la fin** de `/etc/ssh/sshd_config` (après toute directive `Match` existante) : ```bash Match Group sftponly ForceCommand internal-sftp -l INFO -f AUTH ChrootDirectory %h AllowTcpForwarding no X11Forwarding no PermitTunnel no AllowAgentForwarding no ``` **Piège du chroot :** `ChrootDirectory` exige que le répertoire racine appartienne à `root:root` en `755`. Si le home appartient à l'utilisateur, SSH refuse la connexion silencieusement. ```bash # Structure correcte pour chroot SFTP chown root:root /home/alice # Obligatoire chmod 755 /home/alice # Sous-dossier accessible en écriture mkdir -p /home/alice/uploads chown alice:alice /home/alice/uploads chmod 755 /home/alice/uploads ``` --- ## Section 2 : fail2ban — Protection brute force ```bash apt install fail2ban systemctl enable fail2ban ``` Toutes les personnalisations dans `/etc/fail2ban/jail.local` — **ne jamais modifier `jail.conf`** (écrasé lors des mises à jour). ```bash # /etc/fail2ban/jail.local [DEFAULT] bantime = 86400 ; 24h findtime = 600 ; fenêtre : 10 minutes maxretry = 5 banaction = iptables-multiport [sshd] enabled = true port = ssh logpath = %(sshd_log)s backend = %(sshd_backend)s maxretry = 5 [proftpd] enabled = true port = ftp,ftp-data,ftps,ftps-data logpath = /var/log/proftpd/proftpd.log maxretry = 6 bantime = 86400 ``` ### Filtre custom pour Apache HTTP Digest Le filtre `apache-auth` par défaut ne détecte pas les erreurs HTTP Digest (`AH01790` / `AH01794`). Créer un filtre custom : ```bash # /etc/fail2ban/filter.d/apache-auth.local [Definition] failregex = \[client :.*\] AH01790: user .+: password mismatch \[client :.*\] AH01794: user .+ in realm .+ not found \[client :.*\] AH01788: .+nonce from .+ received on .+ - not found ``` ```bash # /etc/fail2ban/jail.local — jail correspondante [apache-auth] enabled = true filter = apache-auth port = http,https logpath = /var/log/apache2/error.log maxretry = 8 bantime = 86400 findtime = 300 ``` Tester le filtre avant activation : ```bash fail2ban-regex /var/log/apache2/error.log /etc/fail2ban/filter.d/apache-auth.local --print-all-matched ``` Commandes de diagnostic : ```bash # Vue d'ensemble fail2ban-client status # Détail d'une jail fail2ban-client status apache-auth fail2ban-client status sshd # Débannir une IP (si tu te bans toi-même) fail2ban-client set sshd unbanip 1.2.3.4 # Logs en temps réel tail -f /var/log/fail2ban.log ``` --- ## Section 3 : Audit post-incident — vérifier l'absence d'intrusion ```bash # Connexions SSH réussies (chercher des logins inattendus) grep "Accepted" /var/log/auth.log | tail -50 # Tentatives depuis une IP spécifique grep "198.51.100.1" /var/log/auth.log # Timestamps des fichiers sensibles stat /etc/passwd /etc/shadow /etc/sudoers ls -la /root/.ssh/ # Fichiers modifiés dans /etc (dernières 24h) touch -d "24 hours ago" /tmp/ref_file find /etc -newer /tmp/ref_file -ls 2>/dev/null # Compter les tentatives auth Digest par IP grep "AH0179" /var/log/apache2/error.log | grep -oP '\[client \K[0-9.]+' | sort | uniq -c | sort -rn | head -20 # Identifier une IP attaquante curl -s https://ipinfo.io/198.51.100.1/json ``` --- ## Section 4 : auditd — Détection d'intrusion en continu ```bash apt install auditd audispd-plugins systemctl enable auditd ``` Créer `/etc/audit/rules.d/security.rules` : ```bash # Modifications sudoers -w /etc/sudoers -p wa -k sudoers -w /etc/sudoers.d/ -p wa -k sudoers # Configuration SSH -w /etc/ssh/sshd_config -p wa -k sshd_config # Clés SSH root -w /root/.ssh/authorized_keys -p wa -k root_keys # Comptes système -w /etc/passwd -p wa -k accounts -w /etc/shadow -p wa -k accounts -w /etc/group -p wa -k accounts # Exécution su et sudo -w /usr/bin/su -p x -k su_exec -w /usr/bin/sudo -p x -k sudo_exec # Crontab -w /etc/crontab -p wa -k crontab -w /etc/cron.d/ -p wa -k crontab -w /var/spool/cron/ -p wa -k crontab # Configuration applicative sensible -w /var/www/html/config.php -p rwa -k app_config -w /etc/apache2/ -p wa -k apache_config -w /etc/fail2ban/ -p wa -k fail2ban ``` ```bash # Charger sans redémarrer augenrules --load # Vérifier les règles actives auditctl -l # Consulter les événements (dernières 24h) ausearch -ts yesterday -te now | aureport -f -i | head -50 ``` Outils complémentaires : ```bash # Process accounting — qui a exécuté quoi et quand apt install acct accton on lastcomm --user alice | head -30 lastcomm --user root | head -50 # Rootkit scanner apt install rkhunter rkhunter --update && rkhunter --propupd # Initialiser la baseline rkhunter --check --skip-keypress # Scan complet # Après apt upgrade, toujours régénérer la baseline : apt upgrade && rkhunter --propupd ``` --- ## Section 5 : Apache hardening ### Bloquer les répertoires sensibles Utiliser `RewriteRule` plutôt que `Require all denied` — la RewriteRule s'exécute **avant** l'authentification dans le pipeline Apache. ```apacheconf RewriteEngine On # Bloquer l'accès direct aux données internes RewriteRule ^/data/internal - [F,L] RewriteRule ^/uploads/private - [F,L] # Bloquer les fichiers de config exposés par erreur RewriteRule \.(env|log|sql|bak)$ - [F,L] ``` ### Permissions sur les fichiers htdigest et config ```bash # htdigest — root:www-data 640 chown root:www-data /etc/apache2/.htdigest chmod 640 /etc/apache2/.htdigest # Config PHP avec credentials chown root:www-data /var/www/html/config.php chmod 640 /var/www/html/config.php ``` ### Principe du moindre privilège pour sudo Ne jamais mettre `www-data` en sudo général. Créer `/etc/sudoers.d/www-data` : ```bash # /etc/sudoers.d/www-data — toujours chemins absolus www-data ALL=(ALL) NOPASSWD: /usr/sbin/apachectl graceful www-data ALL=(ALL) NOPASSWD: /usr/local/bin/maintenance-script.sh ``` ```bash chmod 440 /etc/sudoers.d/www-data visudo -c -f /etc/sudoers.d/www-data # Vérifier la syntaxe avant d'enregistrer ``` Validation côté PHP pour tout paramètre passé à shell_exec : ```php ``` --- ## Section 6 : Mise à jour automatique et sécurité PHP ```bash apt install unattended-upgrades dpkg-reconfigure --priority=low unattended-upgrades ``` Sessions PHP sécurisées — à configurer AVANT `session_start()` : ```php ``` Ou globalement dans `/etc/php/8.x/apache2/php.ini` : ```bash session.cookie_httponly = 1 session.cookie_secure = 1 ``` --- ## Section 7 : Monitoring nocturne avec filtrage IA Script cron qui collecte les logs, les fait analyser par Claude CLI, et n'envoie un mail que si un incident réel est détecté. ```bash #!/bin/bash # /root/scripts/security-audit.sh # Crontab root : 0 3 * * * /root/scripts/security-audit.sh REPORT_DIR="/var/log/security-audit" TODAY=$(date +%Y-%m-%d) REPORT="$REPORT_DIR/$TODAY.txt" mkdir -p "$REPORT_DIR" chmod 700 "$REPORT_DIR" { echo "=== RAPPORT DE SECURITE - $TODAY ===" ausearch -ts yesterday -te now 2>/dev/null | aureport -f -i 2>/dev/null | tail -100 fail2ban-client status sshd 2>/dev/null grep "Accepted" /var/log/auth.log | grep "$(date +%b)" || echo "aucune connexion SSH reussie" ss -tlnp } > "$REPORT" 2>&1 chmod 600 "$REPORT" /root/scripts/security-analyze.sh "$REPORT" ``` Script d'analyse avec fallback robuste — si l'IA est indisponible, le rapport brut est envoyé quand même : ```bash #!/bin/bash # /root/scripts/security-analyze.sh REPORT="${1:-}" ADMIN_EMAIL="admin@example.com" TODAY=$(date +%Y-%m-%d) PROMPT="Analyse ce rapport de securite Linux. Les tentatives SSH depuis IPs aleatoires = bruit normal. Incident reel = connexion SSH reussie depuis IP inconnue, modification fichier sensible dans auditd, port inattendu. JSON UNIQUEMENT sans markdown : {\"alert\": true/false, \"summary\": \"2-3 phrases\", \"details\": [\"point1\"]}" AI_RESPONSE="" AI_ERROR=0 if command -v claude >/dev/null 2>&1; then AI_RESPONSE=$(timeout 60 claude --print --model claude-haiku-4-5 "$PROMPT" < "$REPORT" 2>/dev/null) || AI_ERROR=1 else AI_ERROR=1 fi # Fallback : toujours envoyer le rapport si l'analyse IA échoue if [ "$AI_ERROR" -eq 1 ] || [ -z "$AI_RESPONSE" ]; then mail -s "[SECURITE] Rapport $TODAY - IA indisponible" "$ADMIN_EMAIL" < "$REPORT" exit 0 fi ALERT=$(echo "$AI_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('alert', False))" 2>/dev/null || echo "parse_error") if [ "$ALERT" = "parse_error" ]; then mail -s "[SECURITE] Rapport $TODAY - reponse IA invalide" "$ADMIN_EMAIL" < "$REPORT" exit 0 fi if [ "$ALERT" = "True" ]; then SUMMARY=$(echo "$AI_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('summary', 'N/A'))" 2>/dev/null || echo "N/A") { echo "IA : $SUMMARY"; echo ""; cat "$REPORT"; } | mail -s "[ALERTE SECURITE] $TODAY - Incident detecte" "$ADMIN_EMAIL" fi # Si alert=False : rapport archivé dans REPORT_DIR, pas de mail. ``` --- ## Section 8 : Checklist sécurité complète ``` SSH [ ] PermitRootLogin prohibit-password dans sshd_config [ ] Groupe sftponly avec ChrootDirectory pour utilisateurs SFTP [ ] Home dirs sftponly : root:root 755, sous-dossiers user:user [ ] sshd -t && systemctl reload sshd après chaque modification fail2ban [ ] jail.local créé (ne pas toucher jail.conf) [ ] Jail sshd : bantime 86400, maxretry 5 [ ] Filtre apache-auth.local custom pour erreurs Digest AH0179x [ ] fail2ban-regex testé sur vrais logs avant activation [ ] fail2ban-client status pour valider toutes les jails auditd [ ] Règles sur sudoers, ssh config, root keys, comptes système [ ] Règles sur su, sudo, crontab, config applicative [ ] augenrules --load après modification [ ] acct installé (lastcomm) + rkhunter baseline initialisée Apache [ ] RewriteRule [F,L] sur répertoires sensibles [ ] htdigest : root:www-data 640 [ ] sudoers www-data : chemins absolus, visudo -c validé PHP [ ] session.cookie_httponly = 1 [ ] session.cookie_secure = 1 [ ] session.use_strict_mode = 1 Monitoring [ ] security-audit.sh en cron 0 3 * * * (root) [ ] chmod 700 REPORT_DIR, chmod 600 rapports [ ] Fallback mail si Claude CLI indisponible [ ] unattended-upgrades configuré ```