V1.1
Refactorisation complète de installer.php
Contexte
installer.php est un script PHP statique, versionné en SVN, qui s'exécute entièrement en dehors du bootstrap WordPress. Il est intentionnellement accessible via HTTP et prend en charge le pipeline complet d'import une fois que class-importer::step_prepare() a écrit installer_config.json dans le répertoire de session protégé.
Ce commit documente et consolide l'architecture établie en v1.1.0.
Aucune régression fonctionnelle — tous les comportements existants sont préservés.
Modèle de sécurité
Quatre couches de défense appliquées dans un ordre strict, avant toute opération sensible.
1. Verrou de présence de config → HTTP 404
file_exists(installer_config.json) est vérifié avant les en-têtes CORS, avant l'authentification, avant tout traitement. En l'absence du fichier → 404 + exit. L'installeur est inerte par défaut et à tout moment en dehors d'une fenêtre d'import active de 15 minutes.
2. Expiration du TTL → HTTP 410 + auto-destruction
expires_at est vérifié immédiatement après le chargement de la config. TTL expiré → wpcm_self_destruct() supprime installer_config.json et retourne HTTP 410. La fenêtre de 15 minutes est intentionnellement courte.
3. Liste blanche d'origine CORS → HTTP 403
allowed_origin est gravé au moment de la génération par step_prepare() à partir de admin_url() — il n'est jamais reflété depuis la requête. Les clients non-navigateur (cURL, WP-Cron) qui n'envoient pas d'en-tête Origin sont autorisés à passer.
4. Authentification bcrypt à usage unique → HTTP 410 + auto-destruction
Le token client est généré par crypto.getRandomValues() dans le navigateur et envoyé une seule fois à step_prepare(). Le plugin WP ne stocke que bcrypt_hash(token) dans installer_config.json — le token brut n'est jamais stocké ni retourné. password_verify() est le seul chemin d'entrée valide. Un token incorrect déclenche immédiatement wpcm_self_destruct(), donnant à un attaquant exactement une tentative avant que l'installeur devienne définitivement inerte.
Chiffrement des identifiants DB
Les identifiants de base de données sont chiffrés dans class-importer::step_prepare() avec AES-256-CBC.
- Clé :
SHA-256(client_token)— 32 octets dérivés du token bcrypt vérifié - IV :
random_bytes(16) - Stockage :
base64(IV || ciphertext)dansinstaller_config.json
Le hash bcrypt et le blob AES sont inutilisables sans le token client original, que seul le navigateur détient.
Pipeline d'import — 4 étapes, toutes reprenables
database — Import SQL découpé dans le temps
- Budget de 8 secondes par appel AJAX, compatible avec les timeouts FastCGI et hébergement mutualisé (30 s).
- Suppression préalable de toutes les tables de destination au premier appel (batch
DROP TABLE IF EXISTS). - Reprise à la borne de l'instruction : l'offset en octets est sauvegardé immédiatement après chaque
;exécuté, évitant la réexécution infinie sur une même ligne multi-instructions. - Conscience de
max_allowed_packet: lesINSERT … VALUEStrop volumineux sont découpés en sous-chunks à la frontière de chaque ligne de données. - Récupération sur clé dupliquée : réessai automatique en
REPLACE INTOsurerrno 1062. - Chien de garde de connexion : reconnexion automatique sur
gone away/lost connection. - Mode maintenance (fichier
.maintenance) activé à l'entrée de l'étape et rafraîchi à chaque appel pour que les longs imports ne fassent jamais expirer la fenêtre.
files — Restauration complète de wp-content
- Nettoyage intégral de
wp-contentavant restauration. - Préservés : dossier clone-master,
wpcm-temp(session active),wpcm-backups,wpcm-logs. - Protection ZIP Slip : chaque entrée d'archive est normalisée et vérifiée pour rester dans le répertoire d'extraction temporaire avant tout appel à
ZipArchive::extractTo(). - Restaure : thèmes, plugins, uploads (par année/mois), mu-plugins, langues, drop-ins et fichiers de config racine (
.htaccess,robots.txt).
replace_urls — Remplacement serialization-safe découpé dans le temps
- Budget de 3 secondes par appel AJAX.
- Paires d'URLs couvrant : variantes
https/http, protocole-relatif (//domain), JSON-échappé (\/), URL-encodé et chemins absolus serveur. - Gestion des données PHP sérialisées (correction des longueurs d'octets après remplacement), blobs JSON, sérialisé-dans-JSON imbriqué, et fallback
__PHP_Incomplete_Classvia remplacement de chaîne brut. - Troncature des tables à index
UNIQUEconnus (Yoast, Redirection) pour éviter les erreurs de clé dupliquée. - Résolution
errno 1406(données trop longues) par fallback colonne par colonne, eterrno 1062(clé unique dupliquée) par suppression de la ligne conflictuelle avant une nouvelle tentative.
finalize — Nettoyage et durcissement post-migration
- Purge des transients,
rewrite_rules,auth_key, cache CSS Elementor, fichiers cache WP Rocket et W3TC. - Désactivation automatique des plugins de sécurité/WAF connus pour dysfonctionner sur un nouveau domaine (Wordfence, Sucuri, iThemes Security, Jetpack, plugins reCAPTCHA, etc.) — la liste est sauvegardée dans
wpcm_deactivated_pluginspour affichage en notice admin. - Neutralisation des entrées
auto_prepend_filede Wordfence WAF dans.user.ini,php.iniet.htaccess. - Application des
import_opts: changement de locale, réinitialisation des permaliens, blocage d'indexation (SQL + flagwpcm_noindex+ MU-plugin auto-supprimant). - Garantit que clone-master lui-même reste actif après l'import.
- Supprime le répertoire de session (y compris
installer_config.json) → l'installeur devient définitivement inerte. - Lève le mode maintenance.
Filet de sécurité sur erreur fatale
register_shutdown_function() intercepte E_ERROR, E_PARSE, E_COMPILE_ERROR, E_CORE_ERROR (non rattrapables par try/catch).
- Erreur fatale non-timeout →
wpcm_self_destruct()se déclenche, réponse JSON propre. - Timeout PHP → l'installeur reste actif, la boucle de polling JS peut reprendre depuis le dernier offset octet/ligne sauvegardé.
Fichiers modifiés
| Fichier | Détail |
|---|---|
| installer.php | Refactorisation complète — 1 664 lignes, 18 fonctions, 4 étapes d'import, 8 blocs phpcs:disable avec justifications de contexte standalone |