Security Blog
Posted By Gregory

Under the Hood of WP Cerber 9.9


English version: Under the Hood of WP Cerber 9.9


Cette mise à jour concerne principalement les aspects de WP Cerber que l'on ne remarque jamais jusqu'à ce qu'un problème survienne. Nous avons renforcé la sécurité du stockage et de la récupération des paramètres, la façon dont l'inspecteur de trafic interprète le JavaScript obfusqué et le comportement de l'extension sur les anciennes infrastructures d'hébergement. Nous avons également finalisé une migration de longue haleine depuis l'API des paramètres WordPress. Voici les changements internes et leurs conséquences pour vos sites.

Paramètres qui survivent à une base de données corrompue

WP Cerber stocke sa configuration dans une seule variable, CERBER_CONFIG . Jusqu'à présent, si cette valeur était corrompue et ne pouvait plus être désérialisée, l'extension transmettait directement le résultat erroné à array_merge() . Sous PHP 8, cela provoquait une erreur fatale TypeError au chargement de l'extension. Comme l'erreur survenait au chargement, c'est tout le site qui était inaccessible, et pas seulement l'interface d'administration de l'extension.

crb_get_settings() vérifie désormais la valeur renvoyée par crb_unserialize() ` avant de l'utiliser. Si les données stockées ne peuvent pas être analysées et converties en tableau, le plugin utilise ses paramètres par défaut au lieu de planter. L'erreur est consignée comme un problème critique persistant via CRB_Issues::add() sous le nom ` corrupted_settings . Ce problème est automatiquement résolu lors du prochain enregistrement des paramètres, exécuté par cerber_settings_update() .

Nous avons pris soin de distinguer deux cas qui se ressemblent, mais qui sont différents. Une valeur stockée vide ou fausse signifie simplement que les paramètres n'existent pas encore. Cette valeur n'est jamais désérialisée et le plugin utilise les valeurs par défaut sans générer d'alerte. Le signaler serait donc une fausse alerte. Le problème corrupted_settings se produit désormais uniquement lorsqu'une valeur stockée non vide ne peut pas être désérialisée en tableau, ce qui correspond exactement à la condition à l'origine du plantage en production.

Deux branches voisines de la même fonction ont subi la même modification. La fusion pour la compatibilité avec l'extension Cloudflare exige désormais un tableau avant de pouvoir être fusionnée. La branche CERBER_WP_OPTIONS renvoie systématiquement un tableau lorsqu'aucun paramètre spécifique n'est demandé.

Un système d'auto-réparation en plus du garde

Le retour aux paramètres par défaut permet au site de rester opérationnel, mais supprime la configuration de l'administrateur. C'est pourquoi nous avons ajouté une couche de récupération. Une nouvelle classe, CRB_Settings_Backup , conserve une copie valide de la dernière configuration connue de CERBER_CONFIG dans le système de stockage clé-valeur du plugin.

La sauvegarde est stockée au format JSON brut, accompagnée de l'identifiant de l'utilisateur qui l'a créée, d'un horodatage et du contexte de sa création. Elle est actualisée après une mise à jour ou une importation réussie des paramètres, après une mise à niveau d'un plugin et lors de la tâche de maintenance quotidienne. Seules une configuration valide et un code de contexte connu peuvent remplacer une sauvegarde existante.

Lorsque le détecteur de corruption détecte un CERBER_CONFIG illisible, la récupération s'effectue automatiquement. En cas de restauration réussie, l'extension affiche un avertissement que vous pouvez ignorer, expliquant le problème et vous invitant à vérifier et enregistrer vos paramètres. Cet avertissement disparaît une fois la vérification effectuée. Si aucune sauvegarde utilisable n'existe ou si la valeur restaurée ne peut être écrite, l'extension conserve son comportement de dernier recours et rétablit les paramètres par défaut. Dans ce cas, elle signale un problème critique au lieu de l'avertissement.

WP Cerber settings recovery flow. Missing settings quietly fall back to defaults, while corrupted settings trigger automatic recovery from the last-known-valid backup.

WP Cerber settings recovery flow. Missing settings quietly fall back to defaults, while corrupted settings trigger automatic recovery from the last-known-valid backup.

Pour que la récupération fonctionne correctement, il est essentiel de respecter le cache WordPress. La fonction recover() ` supprime l'option CERBER_CONFIG corrompue avant d'écrire la valeur restaurée. Sans cette étape, un cache d'options obsolète pourrait amener update_site_option() à considérer la valeur comme inchangée et à ignorer l'écriture dans la base de données. La sauvegarde elle-même est lue et écrite sans passer par le cache d'objets ; seul l'enregistrement cerber_sets qui est permanent, est donc fiable. L'ensemble des codes de contexte acceptés est fermé. La fonction d'écriture, sync() , et le validateur de charge utile n'acceptent que les quatre codes déclarés, ce qui permet à chaque charge utile stockée de passer la même validation que celle effectuée ultérieurement par la récupération.

Plus d'erreurs fatales sur les hôtes sans mysqlnd

WP Cerber accède à la base de données via mysqli. Une méthode de récupération, CRB_Database::fetch_result_set() , utilise mysqli_result::fetch_all() pour récupérer l'ensemble des résultats en un seul appel. Cette méthode n'existe que lorsque l'extension mysqli de PHP est compilée avec le pilote mysqlnd. Sur les systèmes où mysqli est compilé avec l'ancienne bibliothèque libmysqlclient, la méthode est absente et son appel provoque une erreur fatale.

La correction suit un modèle déjà utilisé ailleurs dans cerber-common.php . Avant d'emprunter le chemin rapide, le code vérifie désormais function_exists('mysqli_fetch_all') . Si cette fonction est indisponible, il lit le jeu de résultats ligne par ligne avec mysqli_result::fetch_array() , pour les formats MYSQLI_ASSOC et MYSQLI_NUM . L'ordre des lignes, la structure des résultats, le nettoyage et le contrat de retour Revalt existant sont préservés. Le comportement par défaut renvoie les mêmes données que le chemin rapide.

Nous avons également fourni aux administrateurs un moyen de savoir quand le système de secours est activé. Un nouveau détecteur dans CRB_Issue_Monitor signale un problème d'avis, db_driver_no_mysqlnd , lorsque l'extension mysqli est chargée mais que mysqli_fetch_all() est manquante. Cette notification apparaît dans le widget « État du système ». Elle est informative : elle confirme que le plugin continue de fonctionner en mode de secours et recommande d'activer mysqlnd pour une meilleure compatibilité et de meilleures performances.

Détection plus précise du JavaScript obfusqué

CRB_JS_Detector analyse les champs des requêtes dans le cadre de l'Inspecteur de trafic, activé par défaut. Il recherche le code JavaScript obfusqué afin de masquer des primitives telles que eval , script et XMLHttpRequest . L'objectif est d'obtenir une détection fiable avec un faible taux de faux positifs. Le détecteur ne décode que les entrées qui sont clairement des chaînes encodées et ignore tout le reste. Cette version inclut plusieurs modifications : correction d'une faille de sécurité et extension des types de données analysées par le détecteur.

Une régression qui laissait passer les chaînes de caractères entièrement échappées en hexadécimal

Cette version inclut un correctif. L'heuristique d'échappement hexadécimal normalisait chaque chaîne correspondante avec trim( $m, "\\'\"" ) . trim() de PHP interprète son deuxième argument comme un ensemble de caractères à supprimer, et non comme un préfixe littéral. La barre oblique inverse présente dans cet ensemble supprimait la barre oblique inverse initiale du premier échappement \xNN ainsi que le guillemet ouvrant. La chaîne restante avait une longueur impaire de 2N + 1 Un mécanisme de vérification de longueur impaire concluait alors que la chaîne n'était pas correctement encodée en hexadécimal et ignorait complètement le décodage. Concrètement, une chaîne composée uniquement d'échappements \xNN passait inaperçue, et les primitives échappées en hexadécimal telles que eval , script et XMLHttpRequest n'étaient pas détectées sur le chemin par défaut des champs de requête. Ce correctif rétablit le décodage correct des chaînes entièrement échappées en hexadécimal.

Couverture étendue des séquences d'échappement et des codes de caractères

Nous avons ensuite étendu les capacités du détecteur. L'heuristique d'échappement, qui ne reconnaissait auparavant que \xNN , prend désormais en charge les séquences d'échappement \uNNNN et \u{...} , y compris les chaînes combinant ces formats, tout en conservant la règle selon laquelle seules les chaînes entièrement échappées sont analysées. Le décodage a été déplacé vers une fonction d'assistance dédiée, cerber_decode_js_escapes() , qui décode les points de code ASCII et préserve le reste. En cas d'erreur PCRE interne, cette fonction renvoie l'entrée d'origine plutôt qu'une chaîne vide, de sorte qu'un échec de décodage ne peut pas effacer la valeur vérifiée.

L'heuristique de codage des caractères a également été modifiée. Auparavant, elle examinait uniquement les URL externes et les adresses IP dans la sortie décodée. Désormais, elle recherche aussi les primitives d'exécution et DOM. Elle décode les nombres uniquement à partir d'une construction explicite fromCharCode(...) et non plus à partir de n'importe quel tableau numérique rencontré. Les deux heuristiques partagent désormais un même modèle de primitive prenant en compte les jetons et délimitant les identifiants. Cette modification empêche les mots courants tels que description et evaluation de correspondre aux sous-chaînes eval ou script qu'ils contiennent.

Littéraux entiers encapsulés

fromCharCode est définie pour appliquer la conversion `ToUint16` à chaque argument. Cela signifie qu'un code ASCII suivi d'un multiple de 65536 produit le même caractère. Une version antérieure de l'heuristique n'acceptait que les littéraux jusqu'à six chiffres ; une valeur à sept chiffres (contrôlée sur plusieurs chiffres) pouvait donc passer inaperçue. Par exemple, 1048677 est décodé en unité de code 101, la lettre ` e . Le détecteur accepte désormais les littéraux décimaux non signés et hexadécimaux jusqu'à Number.MAX_SAFE_INTEGER et les convertit en leur unité de code `ToUint16`, indépendamment de la taille des entiers PHP. Il rejette les décimaux avec zéro non significatif, car ils sont ambigus avec l'octal hérité. Il limite le travail par chiffre afin qu'un littéral très long dans une requête publique ne puisse pas entraîner un calcul illimité.

Un correctif ultérieur a permis de combler une lacune similaire. Le fait de renvoyer une chaîne vide pour le premier argument non pris en charge entraînait l'annulation de l'appel décodé. Un attaquant pouvait ainsi ajouter un jeton hors plage à une charge utile normalement détectable et empêcher sa détection. Un tel jeton est 9007199254741024 , que JavaScript convertit en espace via ToUint16. Désormais, un jeton structurellement valide mais non pris en charge est remplacé par un tiret bas comme sentinelle, et le reste de l'appel est toujours décodé. Le tiret bas étant un caractère alphanumérique, il empêche également la formation d'une limite d'identifiant factice à côté d'un mot-clé.

arguments séparés par des commentaires

La dernière méthode de contournement utilisait les commentaires JavaScript. L'heuristique compactait l'entrée en supprimant les espaces, laissant ainsi les commentaires intacts. Un commentaire inséré entre des arguments numériques empêchait la correspondance avec la liste numérique ; un appel comme String.fromCharCode(101,/*x*/118,97,108,40,49,41,59) échappait donc à la détection. Le détecteur supprime désormais les commentaires de bloc et de ligne de la liste d'arguments fromCharCode avant de la valider et de la décoder, tout en préservant le contenu des chaînes entre guillemets. La capture s'exécute également en mode `dotall`, de sorte qu'une liste d'arguments s'étendant sur plusieurs lignes est lue comme un seul appel.

Suppression de l'API des paramètres WordPress

Les pages de paramètres de WP Cerber étaient construites sur l'API de paramètres WordPress. Les formulaires étaient envoyés à /wp-admin/options.php , et l'extension s'appuyait sur register_setting() , add_settings_section() , add_settings_field() , settings_fields() et do_settings_sections() pour les enregistrer et les afficher. Cela fonctionnait, mais l'interface d'administration de l'extension était liée à un sous-système procédural de WordPress et à ses conventions. Cette version finalise la migration vers un moteur de formulaires géré par l'extension, et nous l'avons effectuée par étapes plutôt qu'en une seule modification.

Nous avons d'abord délimité une zone. Tous les appels directs à l'API Settings ont été déplacés vers une classe statique unique, CRB_Legacy_Settings_Manager . L'analyse du code source a révélé que seules cinq des dix fonctions de l'API Settings étaient utilisées, réparties sur six points d'appel dans deux fichiers. Les cinq autres n'étant jamais appelées, la classe de délimitation ne contient volontairement aucune méthode pour elles. Cette étape a permis de préserver le comportement initial à l'identique. Les noms des options, les groupes d'options, les identifiants de section et de champ, les fonctions de rappel, le timing des hooks et les résultats sont restés inchangés.

Nous avons ensuite créé la classe de remplacement. Une nouvelle classe, CRB_Settings_Renderer , génère les sections et les lignes de champs directement à partir de la configuration déclarative renvoyée par cerber_settings_config() . Les formulaires de paramètres sont désormais envoyés à la page d'administration du plugin au lieu d' options.php . Les soumissions sont traitées par le pipeline existant lors de l' admin_init , puis suivies d'une requête POST, d'une redirection et d'une requête GET vers la page des paramètres. Le balisage généré est identique, octet par octet, à celui produit auparavant do_settings_sections() , jusqu'aux titres, blocs de section et lignes du tableau de formulaire.

La vérification du nonce a été modifiée. Au lieu d'utiliser check_admin_referer() sur un groupe d'options de l'API Settings, l'extension vérifie désormais son propre champ cerber_nonce . Ce champ était déjà présent dans tous les formulaires de paramètres, y compris ceux des versions précédentes. Un nonce invalide ou expiré n'interrompt plus la requête avec wp_die() . Une notification d'administrateur est alors affichée et vous êtes redirigé vers le formulaire.

La suppression du options.php a également permis d'éliminer un comportement hérité discret. L'ancien chemin d'accès enregistrait une copie brute de chaque groupe de formulaires dans une option par groupe nommée cerber-{group} . Ces copies n'étaient lues que lors de la migration antérieure à la version 9.3.4 et lors du nettoyage après désinstallation, jamais à l'exécution. Elles ne sont plus écrites.

Cerber.Hub et sites gérés

Deux contextes ont conservé l'ancien chemin quelque temps, car tous deux dépendaient du format de transmission de l'API Settings. Le premier était le rendu distant de Cerber.Hub, où le protocole entre un site principal et ses sites gérés véhiculait les champs option_page et ` _wpnonce . Le second était l'écran d'édition des sites gérés, qui enregistrait les données via un filtre pre_update_option déclenché par options.php . Nous avons migré chacun d'eux successivement.

Le formulaire de modification du site géré est désormais rendu via CRB_Settings_Renderer et enregistré par une nouvelle fonction, nexus_save_client_data_form() . Cette fonction reproduit fidèlement le traitement des données de l'ancien rappel, y compris la résolution des groupes, la suppression strip_tags de propriétaire et le nettoyage des groupes inutilisés. Elle conserve le même niveau de sécurité grâce à des protections explicites nexus_is_main() et is_super_admin() . Le client Cerber.Hub a ensuite abandonné l'émulation de son API Settings. Enfin, CRB_Legacy_Settings_Manager et plusieurs utilitaires désormais obsolètes ont été supprimés.

Les formulaires de paramètres distants n'émettent plus les champs cachés de l'API Settings, tels que option_page , action=update , _wpnonce ou _wp_http_referer . Ils contiennent désormais les mêmes champs internes que les formulaires locaux, auxquels s'ajoute le sceau Nexus dans le contexte distant. L'authentification du transport Nexus reste inchangée et constitue toujours la limite extérieure pour les requêtes du site géré. nexus_is_valid_request() , nexus_is_granted() et l'échange cerber_nexus_seal fonctionnent comme auparavant. Une soumission transférée vérifie toujours le nonce du plugin avant tout traitement des paramètres. Nous avons également ajouté une vérification du point d'entrée. Un écran de paramètres soumis qui ne correspond à aucun écran connu est désormais fermé avec une WP_Error avant le début du traitement, et le message d'erreur inclut la valeur soumise à des fins de diagnostic.

Une transition brutale, et une chose à savoir après la mise à jour

Il s'agissait d'une migration radicale et délibérée. Nous n'avons pas conservé de solution transitoire pour la gestion des deux formats, et aucun traitement du options.php n'est effectué. Cette décision entraîne un cas particulier qu'il convient de préciser : si un formulaire de paramètres a été généré par la version précédente de WP Cerber et que vous le soumettez après la mise à jour, il est possible qu'il ne soit pas enregistré. Dans ce cas, rouvrez la page des paramètres et soumettez-la à nouveau. Le formulaire nouvellement généré intègre le nouveau contrat interne et s'enregistre normalement. Il en va de même pour les formulaires de paramètres distants sur les sites gérés.

Un nettoyage des noms a accompagné la migration. Le terme surchargé group a été renommé « settings_screen_id partout où il identifiait un écran de paramètres, y compris dans la constante, la valeur du champ de formulaire caché, les signatures des fonctions du pipeline et les clés de configuration. Le code d'erreur distant a été renommé de unknown_settings_group à unknown_settings_screen . Un détail de compatibilité est important pour les développeurs d'extensions : la charge utile de l'événement update_settings contient toujours l'ancienne clé group comme alias de settings_screen_id , car la fonction cerber_add_handler() est publique et peut être lue par des gestionnaires externes.

Préparation de l'interface utilisateur

Dans l'interface d'administration, WP Cerber génère le HTML via une fabrique d'interface utilisateur interne plutôt qu'avec du balisage en ligne. Cette version ajoute deux composants structurels : crb_ui_fragment() produit une collection ordonnée et mixte d'éléments enfants sans balise d'encapsulation, crb_ui_element_set() fait de même pour une collection homogène avec un type enfant imposé. Nous avons renommé le constructeur fluide CRB_UI_Fragment_Builder en CRB_UI_Content_Builder afin d'éviter toute confusion avec le nouveau nœud de fragment, et nous avons conservé un alias de classe pour assurer la compatibilité du code existant.

Suite à cela, certains sites d'appel ont été simplifiés. La crb_ui_message_box() accepte désormais les chaînes de caractères et les nombres bruts et les encapsule dans des éléments de paragraphe échappés, ce qui évite aux appelants de les construire manuellement. L'affichage dupliqué des erreurs de récupération sur le tableau de bord et l'écran du journal de trafic a été déplacé vers une seule fonction d'assistance partagée. Le panneau de diagnostic de l'environnement a été reconstruit sur les nouveaux nœuds. Dans chaque cas, le code HTML rendu reste inchangé. Il s'agit là des bases d'une interface d'administration indépendante du moteur de rendu, invisible pour le moment.

Après la mise à niveau

Liste de contrôle succincte pour les administrateurs :

  • Si un formulaire de paramètres ne s'enregistre pas lors de votre première soumission juste après la mise à jour, rouvrez la page des paramètres et enregistrez-le à nouveau.
  • Si WP Cerber rétablit ses paramètres par défaut ou les restaure à partir d'une sauvegarde, vous en serez informé par un message d'erreur dans l'administration. Vérifiez vos paramètres et enregistrez-les pour faire disparaître ce message.
  • Si votre hébergeur exécute PHP sans le pilote mysqlnd, consultez l'avertissement dans le widget « État du système ». Le plugin continue de fonctionner et l'avertissement explique la modification recommandée.

I'm a team lead in Cerber Tech. I'm a software & database architect, WordPress - PHP - SQL - JavaScript developer. I started coding in 1993 on IBM System/370 (yeah, that was amazing days) and today software engineering at Cerber Tech is how I make my living. I've taught to have high standards for myself as well as using them in developing software solutions.

View Comments
There are currently no comments.