Renforcer la sécurité WordPress, du développement des plugins à la configuration serveur

Il y a peu, dans le cadre de recherches sur des plugins WordPress, notre pentester Vincent Fourcade a découvert plusieurs injections, SQL et Javascript, dans des modules du célèbre CMS (CVE-2024-1983, CVE-2024-2956, CVE-2024-3265). L’occasion de revenir aujourd’hui sur la sécurité, au sens large, dans WordPress, que ce soit au niveau de la couche applicative, de la configuration du serveur ou du bureau. C’est parti !

Illustration de sécurité WordPress avec logo central et éléments numériques de sécurité
Quelles mesures de protection avancées pour les sites utilisant WordPress ?

La partie applicative : Sécuriser ses modules

Développer ses propres modules WordPress présente plusieurs avantages. D’une part, vous obtenez un produit sur-mesure, adapté à vos besoins. D’autre part, d’un point de vue sécurité, vous avez un produit non référencé. C’est-à-dire dont le code source n’est pas accessible publiquement (en partant du principe que votre module n’est pas diffusé).

Cependant, la logique de développement de ces outils est propre à WordPress et, à l’instar de tout environnement de travail, pour obtenir une application fonctionnelle et sécurisée, il est nécessaire de respecter certaines règles de développement.

Communiquer avec sa base de données

WordPress embarque sa propre couche d’abstraction pour communiquer avec la base de données : WPDB.

L’objet embarque les méthodes courantes qu’on attend de lui, « prepare, insert, update, select, delete… » et est plutôt simple d’utilisation.

Exemple d’utilisation de WPDB

// Créer une nouvelle instance de la classe wpdb

$wpdb = new wpdb( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST );

$wpdb->prefix = ‘wp_’;

$query = $wpdb->prepare(

« SELECT * FROM {$wpdb->prefix}posts WHERE post_title = %s »,

$title

);

$results = $wpdb->get_results( $query );

Côté sécurité, comme avec PDO, il est nécessaire de correctement utiliser les requêtes préparées pour éviter l’injection SQL.

L’affichage côté client

WordPress fournit également plusieurs fonctions afin de permettre l’affichage de données côté client sans risquer l’injection de code. En effet, en tant qu’application de gestion de contenus, les vecteurs d’injection de code Javascript sont légion. Texte, images, formulaires, données du WYSYZG…

Aussi, il convient de connaitre et d’utiliser à bon escient les fonctions suivantes :

Fonctions d’échappements propres à WordPress 

echo esc_html( $unsafe_data ); // échappe les données avant de les afficher dans des balises HTML.

echo ‘<a href= »‘ . esc_attr( $unsafe_url ) . ‘ »>Link</a>’; // échappe les attributs HTML

echo ‘<a href= »‘ . esc_url( $unsafe_url ) . ‘ »>Link</a>’; // spécifique pour les URL

echo ‘<script>var data = ‘ . esc_js( $unsafe_data ) . ‘;</script>’; // spécifique pour du code javascript

echo ‘<textarea>’ . esc_textarea( $unsafe_text ) . ‘</textarea>’; // spécifique pour les données propres au textaera

echo wp_kses( $unsafe_html, array( ‘a’ => array( ‘href’ => array() ) ) ); // prend une liste blanche de balise HTML et nettoie la chaîne.

Les bonnes pratiques de développement

Bien entendu, il convient également de suivre les bonnes pratiques de développement propre à n’importe quel projet web.

N’utiliser que des fonctions/méthodes à jour, contrôler efficacement l’accès aux ressources, désactiver l’affichage des erreurs en production, procéder à une journalisation des erreurs et évènements, utiliser des fonctions de hachage et de chiffrement adaptées pour les données sensibles réversibles et non réversibles, classifier ces mêmes données.

Enfin, un audit de code du module, si celui-ci est important et représente une pierre angulaire de votre entreprise, permet de potentiellement détecter des failles passées sous le radar lors du recettage de l’application.

Organiser les utilisateurs côté backoffice

Au-delà du développement, il est également primordial de bien configurer les droits des utilisateurs du CMS.

En effet, il arrive souvent qu’au sein d’une entreprise, les collaborateurs aient tous un même niveau de droit élevé sur le WordPress. Il devient alors possible d’installer des modules, modifier le DOM et donc d’obtenir un webshell sur le serveur – un module de téléversement de fichier ou de modification de code suffit amplement – ou d’injecter du code Javascript impactant l’ensemble des utilisateurs. L’idéal pour un compte rédacteur est de n’avoir accès qu’à la partie « articles », par exemple.

Il convient dès lors de définir correctement les rôles et leurs droits. Plusieurs options s’offrent alors à nous.

User Role Editor

L’utilisation de modules spécifiques et reconnus permettent une gestion plus fine des rôles et des droits associés.  Il est généralement recommandé par la communauté d’utiliser « User Rôle Editor ».

WP-CLI

À l’instar du développement maison de module, si l’ajout de plugins annexes n’est pas une option, il est possible, via des vecteurs plus techniques, d’affiner les rôles et réduire leurs droits.

Par exemple, WP-CLI, (WordPress Command Line Interface) permet la gestion de votre CMS en ligne de commande. Outil extrêmement puissant pour qui est habitué aux environnements CLI, il propose, entre autres, des manières ergonomiques de créer, définir ou supprimer des rôles dans WordPress.

Ci-dessous des exemples de commandes WP-CLI pour définir un rôle et y attribuer des droits :

wp role create custom_role « Custom role »

wp role add-cap custom_role custom_capabilities

wp role add-cap editor publish_posts

WP-CLI peut également servir à mettre à jour modules, thèmes et core. Ces commandes sont pratiques et facilement exécutables dans une tâche CRON, par exemple.

#!/bin/bash

wp_path= »/var/www/htmll/LeWordpress »

wp core update –path= »$wp_path » –quiet

(script bash pour mettre à jour wp)

Dans le PHP directement

Enfin, si l’on ne souhaite ni installer de module annexe, ni utiliser WP-CLI (on peut ne pas, par exemple, avoir un accès sur la machine), il est toujours possible de définir des fonctions PHP. Celles-ci seront appelées lors de l’initialisation et produiront le même résultat. Il faut tout de même avouer que cette manière de faire semble est moins pratique et moins élégante.

Fonction PHP pour ajouter un nouveau rôle :

function ajouter_role_nouveau() {

add_role(

     ‘nom_du_role’,

     __( ‘Nom du rôle’, ‘text_domain’ ),

     array(

         // Liste des capacités du rôle

         ‘read’ => true,

         ‘edit_posts’ => true,

         // Ajoutez d’autres capacités nécessaires

     )

);

}

add_action( ‘init’, ‘ajouter_role_nouveau’ );

Fonction PHP pour ajouter des capacités à un rôle :

function ajouter_capacites_role_existants() {

$role = get_role( ‘nom_du_role’ );

$role->add_cap( ‘edit_custom_post_type’ );

}

add_action( ‘init’, ‘ajouter_capacites_role_existants’ );

Le point sécurité à retenir pour la définition de rôles personnalisés est l’existence de ces deux « capabilities » unfiltered_html et unfiltered_js.

En effet, les rôles possédant ces capacités peuvent modifier le DOM à leur convenance, côté site, mais parfois aussi dans le backoffice (les développeurs de modules semblent beaucoup se reposer sur la notion des droits pour l’affichage de certaines données, côté backoffice). Aussi, il conviendra de correctement supprimer ces droits pour toute personne n’ayant pas de légitimité à procéder à du développement front.

Il est également intéressant de noter que, dans le cadre d’une injection de code Javascript/HTML, le POC devra se réaliser avec un utilisateur n’ayant pas ces « capabilities », au risque de voir sa découverte refusée.

Fonction PHP supprimant les capacités d’écrire du Javascript et de l’HTML

function supprimer_capacite_publier_javascript_html() {

// Récupérer le rôle souhaité

$role = get_role( ‘nom_du_role’ );

// Supprimer la capacité de publier des contenus potentiellement dangereux

$role->remove_cap( ‘unfiltered_html’ );

$role->remove_cap( ‘unfiltered_js’ );

}

add_action( ‘init’, ‘supprimer_capacite_publier_javascript_html’ );

Les bonnes pratiques côté serveur

Désactiver l’accès au fichier xmlrpc.php et potentiellement certains appels à l’API de WordPress sont également des actions à mener pour affiner le niveau de sécurité de votre site.

« xmlrpc.php » est un point d’accès initialement conçu pour faciliter la communication, via le langage XML, entre WordPress et des applications clientes. Cependant, l’utilisation de XML-RPC a été associée à des problèmes de sécurité, car certaines attaques ont été facilitées par cette fonctionnalité, notamment la possibilité d’attaques par force-brute.

<Files xmlrpc.php>

Order Deny,Allow

Deny from all

</Files>

(désactiver xmlrpc.php sur apache2)

L’API de WordPress quant à elle est utilisée par de nombreux modules et thèmes, il est donc difficile de la désactiver sans effets de bord. Cependant, elle peut grandement faciliter la recherche d’informations sensibles (comptes utilisateurs, rôles associés, etc.) sur votre application, notamment via le point d’accès « /wp/v2/users ». Il est possible de désactiver ce point, via l’ajout d’une fonction dans le fichier « functions.php ».

Désactiver l’accès à /wp/v2/users

add_filter( ‘rest_endpoints’, ‘desactiver_api_liste_utilisateurs’ );

function desactiver_api_liste_utilisateurs( $endpoints ) {

if ( isset( $endpoints[‘/wp/v2/users’] ) ) {

     unset( $endpoints[‘/wp/v2/users’] );

}

return $endpoints;

}

Enfin, il conviendrait aussi de filtrer l’accès à la mire d’authentification. Le schéma idéal est d’utiliser un VPN afin de se connecter au service d’administration de votre blog, ainsi il devient extrêmement difficile pour un attaquant d’en obtenir l’accès, même s’il parvenait à récupérer des accès valides (via des fuites d’informations issues de précédent hack, par exemple). Le principe basique de la cyber : multiplier les couches de sécurité.

Filtrage par IP de wp-login.php (Apache)

#.htaccess

<Files « wp-login.php »>

order deny,allow

deny from all

allow from votre_adresse_ip

</Files>

Filtrage par IP de wp-login (nginx)

#/etc/nginx/sites-available/

location = /wp-login.php {

allow votre_adresse_ip;

deny all;

}

L’hygiène de sécurité en général

Enfin, au-delà de WordPress, qu’importe le langage, l’application des règles habituelles en matière de cybersécurité sera autant de freins à un potentiel attaquant. Cela peut être l’ajout d’une authentification multifacteur, la mise en place des journaux de suivis, l’installation d’un pare-feu applicatif, la limitation des tentatives de connexion…

La maintenance est un des points clefs pour une application, que ce soit au niveau de sa disponibilité ou de sa sécurité. Les deux notions sont d’ailleurs liées, voire intriquées. En effet, une indisponibilité résulte d’un bug, d’un défaut de configuration, provoquant une erreur fatale empêchant l’application de fonctionner normalement. Une vulnérabilité n’est rien de plus qu’un bug exploité par des tiers pour obtenir de l’application un comportement qu’elle n’aurait pas dû avoir.

Faites vous accompagner

La sécurisation de WordPress nécessite une vigilance constante. Les pratiques et configurations évoquées ici constituent une première base solide pour protéger votre site contre les menaces courantes. Mais vous l’aurez compris, chaque site est unique et peut nécessiter une approche personnalisée. Si vous avez des questions sur la sécurisation de votre site WordPress ou si vous souhaitez bénéficier d’un accompagnement pour assurer la sécurité de votre CMS, notre équipe se tient à votre disposition. Nous proposons des audits de sécurité, des formations sur mesure, et un accompagnement expert pour répondre à vos besoins spécifiques. Pour en discuter, c’est par ici.

Je partage

Derniers articles

Conformité DORA : Tout savoir pour une mise en œuvre efficace

Comprendre et appliquer DORA est essentiel pour maintenir la conformité et la résilience des institutions financières. Cet article détaille la portée de DORA, propose un guide pour un plan de conformité efficace, et explore les défis et stratégies pour les surmonter.

Lire l'article
Visa de sécurité ANSSI.

Digitemis réussit une nouvelle fois l’audit de surveillance PASSI (ANSSI)

Nous sommes fiers d’annoncer que nous avons brillamment réussi notre audit de surveillance (PASSI), qui intervient tous les 18 mois.

Lire l'article