Fichiers de sauvegarde exposés : récupération de code source et de secrets
Un fichier de sauvegarde créé manuellement ou par un éditeur de texte dans la racine web devient une ressource HTTP comme une autre. Le serveur ne l’exécute pas — il le sert en texte brut. Le code source, les identifiants de base de données et les clés API deviennent lisibles directement dans le navigateur.
Le problème
Section titled “Le problème”Quand un développeur modifie config.php en production, il crée souvent une copie avant d’intervenir :
cp config.php config.php.bak# oucp config.php config.php.oldL’éditeur de texte fait la même chose automatiquement :
| Éditeur | Fichier de sauvegarde créé |
|---|---|
| Vim | fichier.php~, .fichier.php.swp |
| Nano | fichier.php~ |
| Gedit | fichier.php~ |
| Emacs | fichier.php~, #fichier.php# |
Ces fichiers restent sur le serveur après l’intervention. Le serveur web les sert sans les traiter : config.php exécute le PHP et renvoie une page HTML vide — config.php.bak retourne le code source PHP en clair.
Extensions courantes à tester
Section titled “Extensions courantes à tester”index.php~index.php.bakindex.php.oldindex.php.saveindex.php.origindex.php.backupindex.php.copyindex.php.tmpindex.php.1index.php_bak.index.php.swp # fichier swap Vim#index.php# # sauvegarde EmacsCes patterns s’appliquent à tous les fichiers sensibles : config.php, wp-config.php, .htpasswd, database.php, settings.py, application.properties.
Détecter les fichiers exposés
Section titled “Détecter les fichiers exposés”Tester manuellement les extensions courantes
Section titled “Tester manuellement les extensions courantes”# Tester les variantes sur un fichier ciblefor ext in ~ .bak .old .save .orig .backup .copy .tmp .1 _bak; do code=$(curl -s -o /dev/null -w "%{http_code}" "https://cible.fr/config.php${ext}") echo "$code — config.php${ext}"doneUn code 200 indique que le fichier est accessible. Un 403 signifie qu’il existe mais que l’accès est bloqué. Un 404 signifie qu’il n’existe pas.
Automatiser avec gobuster
Section titled “Automatiser avec gobuster”# Utiliser une wordlist orientée fichiers de sauvegardegobuster dir \ -u https://cible.fr \ -w /usr/share/wordlists/dirb/common.txt \ -x bak,old,save,orig,backup,copy,tmp,~ \ -s 200,301
# Avec une wordlist de fichiers PHP courantsgobuster dir \ -u https://cible.fr \ -w /usr/share/seclists/Discovery/Web-Content/PHP.fuzz.txt \ -x bak,old,~Tester les fichiers swap Vim
Section titled “Tester les fichiers swap Vim”Les fichiers swap Vim ont un nommage spécifique : .nomfichier.ext.swp. Ils sont binaires mais contiennent le texte du fichier en clair dans leur structure.
curl -s https://cible.fr/.config.php.swp -o config.swp
# Récupérer le contenu texte du fichier swapstrings config.swpCe qu’on récupère concrètement
Section titled “Ce qu’on récupère concrètement”Un fichier config.php.bak typique contient :
<?phpdefine('DB_HOST', 'localhost');define('DB_NAME', 'production_db');define('DB_USER', 'app_user');define('DB_PASS', 'Sup3rS3cr3tP@ss');
define('API_KEY', 'sk-live-xxxxxxxxxxxxxxxxxxxxxxxxxxx');define('JWT_SECRET', 'monSecretJWT2024');?>Un fichier settings.py.bak Django :
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'production', 'USER': 'django', 'PASSWORD': 'motDePasseProduction', 'HOST': '10.0.0.5', }}
SECRET_KEY = 'django-insecure-xxxxxxxxxxxxxxxxxxxxxxxxxxx'AWS_ACCESS_KEY_ID = 'AKIAIOSFODNN7EXAMPLE'AWS_SECRET_ACCESS_KEY = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'Remédiation
Section titled “Remédiation”Bloquer les extensions de sauvegarde côté serveur
Section titled “Bloquer les extensions de sauvegarde côté serveur”Apache (.htaccess ou httpd.conf) :
<FilesMatch "(\.bak|\.old|\.save|\.orig|\.backup|\.copy|\.tmp|~|\.swp|_bak)$"> Require all denied</FilesMatch>Nginx :
location ~* \.(bak|old|save|orig|backup|copy|tmp|swp)$|~$ { deny all; return 404;}IIS (web.config) :
<configuration> <system.webServer> <security> <requestFiltering> <denyUrlSequences> <add sequence=".bak" /> <add sequence=".old" /> <add sequence=".save" /> <add sequence="~" /> </denyUrlSequences> </requestFiltering> </security> </system.webServer></configuration>Ne pas éditer en production
Section titled “Ne pas éditer en production”La source du problème est l’édition directe de fichiers sur le serveur. Un pipeline de déploiement supprime cette pratique :
- Modifier le fichier localement
- Passer par git et une CI/CD
- Déployer via rsync, Ansible, ou un outil de déploiement
Aucun éditeur de texte n’ouvre de fichier sur le serveur → aucun fichier ~ ou .swp ne se crée.
Conventions de nommage pour les sauvegardes légitimes
Section titled “Conventions de nommage pour les sauvegardes légitimes”Quand une sauvegarde est nécessaire, la stocker hors de la racine web :
# À éviter — dans la racine webcp /var/www/html/config.php /var/www/html/config.php.bak
# Correct — hors de la racine webcp /var/www/html/config.php /var/backups/config.php.$(date +%Y%m%d)La racine web est typiquement /var/www/html/ — tout ce qui s’y trouve est potentiellement accessible via HTTP. Les sauvegardes vont ailleurs.
Audit des fichiers existants
Section titled “Audit des fichiers existants”Identifier les fichiers de sauvegarde déjà présents dans la racine web :
find /var/www/html -name "*.bak" -o -name "*.old" -o -name "*.save" \ -o -name "*~" -o -name "*.swp" -o -name "*.orig" 2>/dev/nullSupprimer les occurrences trouvées et vérifier les règles de déploiement pour éviter qu’elles réapparaissent.
À retenir
Section titled “À retenir”Le serveur sert les fichiers .bak et ~ sans les exécuter. Un fichier PHP dont le contenu est confidentiel devient lisible en clair dès qu’une copie avec une extension non reconnue existe dans la racine web.
Bloquer ces extensions au niveau du serveur et stocker les sauvegardes hors de la racine web sont les deux mesures à appliquer ensemble — l’une sans l’autre laisse une surface d’attaque ouverte.