Securiser WordPress : le guide complet anti-hack

Credit : Logo officiel

Securiser WordPress : le guide complet anti-hack

Dylan D. — Agent Support Technique Serveur Securite 2019 mots 11 min de lecture

1847 tentatives de connexion en 24 heures

Je regarde les logs Fail2ban d'un site WordPress de PME que je gere. Sur les dernieres 24 heures : 1847 tentatives de brute-force sur wp-login.php, 312 sur xmlrpc.php, et 89 scans de chemins suspects (/wp-admin/install.php, /old/, /.env). C'est un site totalement quelconque, pas un acteur cible. WordPress represente 43 % du web en 2026, et c'est de loin le CMS le plus attaque automatiquement.

La bonne nouvelle : la majorite des piratages reussis sont des fautes basiques, pas des attaques sophistiquees. Plugin pas a jour, mot de passe faible, permissions fichiers laxistes, absence de 2FA. En appliquant methodiquement les mesures de ce guide, vous bloquez 99 % des attaques automatisees. Les 1 % restants sont des attaques ciblees pilotees par humain, et elles sont rares en pratique.

J'ai vu des sites se faire defacer parce que le mot de passe admin etait admin123. Et je l'ai vu trois fois en 2025. Serieusement.

Le .htaccess : premiere ligne de defense (Apache)

Si vous etes sur Apache, le .htaccess a la racine du site permet de verrouiller plein de chemins critiques en quelques lignes :

# Proteger wp-config.php
<Files wp-config.php>
    Require all denied
</Files>

# Bloquer xmlrpc.php (vecteur de brute-force majeur)
<Files xmlrpc.php>
    Require all denied
</Files>

# Pas de listing de repertoires
Options -Indexes

# Proteger .htaccess lui-meme
<Files .htaccess>
    Require all denied
</Files>

# Bloquer l'execution de PHP dans les uploads (CRITIQUE)
<Directory "/var/www/monsite/wp-content/uploads">
    <FilesMatch "\.(php|phtml|phar){{CONTENT}}quot;>
        Require all denied
    </FilesMatch>
</Directory>

# Bloquer les fichiers sensibles
<FilesMatch "^(\.env|wp-config-sample\.php|readme\.html|license\.txt)">
    Require all denied
</FilesMatch>

# Bloquer les user-agents louches
RewriteCond %{HTTP_USER_AGENT} (libwww|wget|curl|python-requests) [NC]
RewriteRule .* - [F,L]

Equivalent Nginx

Dans le bloc server de votre vhost :

location = /xmlrpc.php { deny all; }
location = /wp-config.php { deny all; }
location = /readme.html { deny all; }
location = /license.txt { deny all; }

location ~* /wp-content/uploads/.*\.(php|phtml|phar)$ {
    deny all;
}

location ~ /\.(?!well-known) {
    deny all;
    access_log off;
    log_not_found off;
}

location ~ /(\.env|composer\.(json|lock)|package(-lock)?\.json) {
    deny all;
}

Le blocage de PHP dans wp-content/uploads/ est le point critique. Le vecteur d'attaque le plus courant en 2026 reste l'upload d'un fichier PHP deguise via une faille de plugin. Une fois ce fichier uploade, l'attaquant l'execute via son URL et obtient un shell sur votre serveur. Bloquer PHP dans uploads neutralise 80 % de ces tentatives.

Les headers HTTP de securite

Les headers de securite indiquent au navigateur comment se comporter face a votre site. Configurez-les dans Nginx :

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.googleapis.com *.gstatic.com; style-src 'self' 'unsafe-inline' *.googleapis.com; img-src 'self' data: https:; font-src 'self' data: *.gstatic.com; connect-src 'self';" always;

Le X-XSS-Protection est obsolete depuis 2024, ne le mettez plus. Le Content-Security-Policy est le plus strict : commencez par une politique permissive et restreignez progressivement, sinon vous casserez votre site.

Testez votre configuration sur securityheaders.com. Visez le grade A minimum.

Plugins de securite : Wordfence et limit-login

cd /var/www/monsite
wp plugin install wordfence --activate
wp plugin install limit-login-attempts-reloaded --activate

Configuration Wordfence

Dans Wordfence > All Options :

Configuration limit-login

Limitez sans plugin tiers via Wordfence si vous l'avez. Sinon, limit-login-attempts-reloaded fait le job en standalone.

Le 2FA : ce n'est plus optionnel en 2026

wp plugin install two-factor --activate
# ou alternative plus complete
wp plugin install wp-2fa --activate

Forcez le 2FA pour tous les comptes administrateur. Methodes recommandees, par ordre de securite :

  1. Cles materielles U2F/WebAuthn (YubiKey, Solo Key) : le plus sur, et c'est des 25 EUR
  2. TOTP (Google Authenticator, Authy, Aegis) : standard, gratuit
  3. Codes de secours : 10 codes a usage unique a imprimer et stocker dans un coffre
  4. SMS : evitez si possible, le SIM swap est un vrai risque

Un client a refuse le 2FA en 2024 parce que "trop complique". Trois semaines apres, son site etait defacé via un brute-force qui avait trouve son mot de passe (Boutique2023! malheureusement courant). Maintenant il l'a, et il commence chaque reunion en evangelisant ses fournisseurs.

Les permissions fichiers

Les permissions Linux mal configurees laissent des angles morts exploitables. La regle :

# Repertoires : 755 (ou 750 si encore plus strict)
find /var/www/monsite -type d -exec chmod 755 {} \;

# Fichiers : 644
find /var/www/monsite -type f -exec chmod 644 {} \;

# wp-config.php : 600 ou 640
chmod 600 /var/www/monsite/wp-config.php

# Le proprietaire doit etre www-data (Debian/Ubuntu) ou apache (RHEL)
chown -R www-data:www-data /var/www/monsite

Desactivez l'editeur de code WordPress integre dans wp-config.php :

define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', false); // mettez true si vous gerez les MaJ via WP-CLI/CI

L'editeur integre permet a un compte admin compromis de modifier n'importe quel fichier PHP du site directement depuis le navigateur. C'est une porte d'entree massive pour escalader une compromission.

Les petits plus qui font la difference

Changer le prefixe des tables

A l'installation d'un nouveau site, modifiez wp-config.php :

$table_prefix = 'wp_a7b3_';

Ca ne stoppe pas les attaquants determines mais ca casse les exploits SQL automatises qui hardcodent wp_users. Si le site existe deja, c'est plus complexe (renommer toutes les tables et mettre a jour wp_options et wp_usermeta).

Masquer la version de WordPress

Dans le functions.php de votre theme enfant :

remove_action('wp_head', 'wp_generator');
add_filter('the_generator', '__return_empty_string');

// Cacher aussi la version dans les URLs CSS/JS
add_filter('style_loader_src', function($src) {
    return remove_query_arg('ver', $src);
}, 9999);
add_filter('script_loader_src', function($src) {
    return remove_query_arg('ver', $src);
}, 9999);

Ca empeche les bots d'identifier rapidement les versions vulnerables a cibler.

Deplacer wp-config.php au-dessus du document root

mv /var/www/monsite/wp-config.php /var/www/wp-config.php

WordPress detecte automatiquement le fichier en remontant d'un niveau. Comme il est en dehors du webroot, il est inaccessible via HTTP meme en cas de mauvaise configuration Apache/Nginx.

Bloquer XML-RPC ou le restreindre

XML-RPC est utilise pour les apps mobile WordPress et certains plugins (Jetpack). Si vous ne l'utilisez pas, bloquez-le completement. Si vous en avez besoin, restreignez-le par IP :

location = /xmlrpc.php {
    allow 203.0.113.0/24;  # IP de Jetpack ou de votre app
    deny all;
}

SSL/TLS strict avec HSTS preload

Assurez-vous que votre site est en HTTPS partout, redirige HTTP vers HTTPS, et envoie le header HSTS avec preload :

server {
    listen 80;
    server_name monsite.fr www.monsite.fr;
    return 301 https://monsite.fr$request_uri;
}

server {
    listen 443 ssl http2;
    server_name monsite.fr;

    ssl_certificate /etc/letsencrypt/live/monsite.fr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/monsite.fr/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5:!3DES;
    ssl_prefer_server_ciphers on;

    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # ... reste de la config
}

Soumettez votre domaine sur hstspreload.org une fois que vous etes sur de votre config HTTPS. Une fois la, le navigateur refuse toute connexion HTTP a votre domaine, meme en saisie directe.

Sauvegardes : le filet de securite ultime

La meilleure defense reste un bon backup. Si tout echoue, vous restaurez :

#!/bin/bash
# /usr/local/bin/wp-backup.sh
set -e

DATE=$(date +%Y%m%d-%H%M)
BACKUP_DIR="/backup/wordpress"
SITE_DIR="/var/www/monsite"

mkdir -p $BACKUP_DIR

# Base de donnees
wp db export $BACKUP_DIR/db-$DATE.sql --path=$SITE_DIR
gzip $BACKUP_DIR/db-$DATE.sql

# Fichiers (hors cache et uploads tres volumineux a votre choix)
tar --exclude=$SITE_DIR/wp-content/cache \
    --exclude=$SITE_DIR/wp-content/uploads/cache \
    -czf $BACKUP_DIR/files-$DATE.tar.gz -C /var/www $(basename $SITE_DIR)

# Rotation : garder 30 jours
find $BACKUP_DIR -name 'db-*.sql.gz' -mtime +30 -delete
find $BACKUP_DIR -name 'files-*.tar.gz' -mtime +30 -delete

# Sync offsite
rsync -az $BACKUP_DIR/ backup-user@offsite.example.com:/backups/monsite/

Dans /etc/cron.d/wp-backup :

0 3 * * * root /usr/local/bin/wp-backup.sh >> /var/log/wp-backup.log 2>&1

Testez la restauration au moins une fois par trimestre. Un backup non teste est un backup qui n'existe pas. Plein de gens decouvrent que leur backup est corrompu le jour ou ils en ont besoin.

Renforcer la base de donnees

La base WordPress contient les hashs de mot de passe, les sessions, et les donnees clients. Verrouillez l'acces :

-- Creer un user dedie avec droits limites au strict necessaire
CREATE USER 'wp_user'@'localhost' IDENTIFIED BY 'mot-de-passe-fort-aleatoire';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX, REFERENCES 
ON wordpress_db.* TO 'wp_user'@'localhost';
FLUSH PRIVILEGES;

N'utilisez jamais root dans wp-config.php, et n'accordez pas GRANT ALL. Si une faille SQL injection survient, vous limitez les degats.

Dans /etc/mysql/mariadb.conf.d/50-server.cnf, restreignez aux connexions locales :

bind-address = 127.0.0.1
skip-name-resolve

Et activez le binlog avec retention courte pour les forensics :

log_bin = /var/log/mysql/mysql-bin.log
binlog_expire_logs_seconds = 604800

En cas de compromission, le binlog vous permet de retracer toutes les modifications faites pendant l'incident.

Plan de reponse a incident

Malgre toutes ces protections, un site peut etre compromis. Avoir un plan precoupe vous fait gagner des heures critiques :

  1. Isoler : couper l'acces public (mode maintenance, ou shutdown Nginx)
  2. Faire un snapshot : LVM snapshot ou copie complete des fichiers et de la DB pour analyse forensique
  3. Identifier le vecteur : find /var/www -mtime -7 -type f -name '*.php' liste les PHP modifies recemment, suspects en premier
  4. Restaurer : depuis un backup PROPRE anterieur a l'intrusion (verifiez les dates des fichiers louches)
  5. Patcher : mise a jour de tous les plugins/themes/core, regeneration des secrets dans wp-config.php, rotation de tous les mots de passe utilisateurs
  6. Notifier : si donnees personnelles exposees, notification CNIL sous 72h obligatoire (RGPD)

Regenerer les cles d'authentification :

# Recuperer un nouveau set de cles via WP-CLI
wp config shuffle-salts --path=/var/www/monsite

Force la deconnexion de toutes les sessions actives, neutralise les cookies de session voles.

Erreurs courantes et leur fix

"Site casse apres l'ajout du Content-Security-Policy". Vous etes trop strict des le depart. Commencez en mode Content-Security-Policy-Report-Only qui rapporte les violations sans les bloquer. Affinez progressivement la politique avec les domaines reellement utilises.

Les utilisateurs ne peuvent plus se connecter apres activation du 2FA. Vous avez force le 2FA sans laisser le temps aux utilisateurs de configurer leur authenticator. Rollback temporaire, communication par email, puis reactivation. Prevoyez toujours des codes de secours imprimes pour les comptes superadmin.

Wordfence bloque les developpeurs en deploiement. L'IP de votre serveur de CI/CD declenche le brute-force protection. Whitelistez l'IP dans Wordfence > Firewall > Allowlisted IP addresses ou utilisez un compte applicatif avec mot de passe d'application.

xmlrpc.php bloque mais Jetpack ne marche plus. Whitelistez les IPs Jetpack publiees sur jetpack.com/support/hosting-faq/. Refusez tout le reste.

Site infecte malgre toutes ces mesures. Verifiez d'abord les plugins nulled (versions piratees) que parfois un client installe en pensant economiser. Ils contiennent quasiment toujours des backdoors. Reinstallez WordPress core, themes et plugins depuis les sources officielles, et changez tous les mots de passe.

Pour aller plus loin

Un WordPress blinde, ca prend une apres-midi

Rien dans ce guide ne demande des semaines de travail ni des outils payants. En une apres-midi methodique, vous appliquez le .htaccess durci, les headers de securite, le 2FA, les permissions correctes et le script de backup. Resultat : votre site est dans le top 5 % des installations WordPress en termes de securite. Aucune protection n'est infaillible (rien ne l'est dans la nature), mais vous etes maintenant invisible pour les bots automatises et tres couteux pour un attaquant manuel. C'est l'objectif.

EDIT : Wordfence a integre du machine learning pour la detection d'intrusion en 2026, et la version premium a vraiment progresse pour reperer les comportements anormaux (par exemple un admin qui se connecte d'un pays inhabituel a 3h du matin). Pour les sites qui hebergent des donnees sensibles, ca vaut largement les 99 EUR/an.

# Articles similaires

Sur les memes sujets et plus loin