← Contextes /
securite-serveur-linux.md 437 lignes · 11.5 KB
Personnaliser Télécharger
# 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 <HOST>:.*\] AH01790: user .+: password mismatch
            \[client <HOST>:.*\] AH01794: user .+ in realm .+ not found
            \[client <HOST>:.*\] 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
<?php
$user = $_POST['username'] ?? '';
if (!preg_match('/^[a-z][a-z0-9_]{2,31}$/', $user)) {
    die('Invalid username format');
}
$escaped = escapeshellarg($user);
$output = shell_exec("sudo /usr/local/bin/create-user-dir.sh $escaped");
?>
```

---

## 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
<?php
ini_set('session.cookie_httponly', 1);     // Inaccessible depuis JavaScript
ini_set('session.cookie_secure', 1);       // HTTPS uniquement
ini_set('session.cookie_samesite', 'Lax'); // Protection CSRF partielle
ini_set('session.use_strict_mode', 1);     // Rejeter les IDs non générés par le serveur

session_start();
?>
```

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é
```