Le WordPress d'aujourd'hui, décodé pour les développeurs

Extensions

wp_ajax_nopriv_ correctement sécurisé : ce que doit vérifier un point d’entrée public

Un point d'entrée AJAX ouvert aux visiteurs non connectés reste une porte d'entrée sensible. Voici les vérifications à ne jamais sauter dans une extension.

Par WordPress Développement • 16 février 2020 • 4 min de lecture • Aucun commentaire
wp_ajax_nopriv_ correctement sécurisé : ce que doit vérifier un point d'entrée public

Un point d’entrée accessible via wp_ajax_nopriv_ répond à n’importe quel visiteur, connecté ou non, ce qui en fait par nature la surface la plus exposée d’une extension. Contrairement à un endpoint réservé aux membres, il ne peut s’appuyer sur aucune vérification de session pour filtrer les requêtes légitimes des tentatives malveillantes.

Beaucoup de développeurs découvrent ce hook en pensant qu’un simple nonce suffit à sécuriser l’action. C’est une erreur : le nonce protège contre les requêtes forgées depuis un autre site (CSRF), pas contre un visiteur qui envoie volontairement des données malformées ou hostiles directement vers l’URL d’admin-ajax.php.

Déclarer les deux hooks selon le public visé

Une action AJAX destinée aux visiteurs non connectés s’enregistre avec wp_ajax_nopriv_{action}. Si la même logique doit aussi répondre aux utilisateurs connectés, il faut déclarer en plus wp_ajax_{action} : les deux hooks sont indépendants, WordPress ne les fait pas cohabiter automatiquement.

add_action( 'wp_ajax_wpm_newsletter_inscription', 'wpm_traiter_inscription' );
add_action( 'wp_ajax_nopriv_wpm_newsletter_inscription', 'wpm_traiter_inscription' );

function wpm_traiter_inscription() {
    check_ajax_referer( 'wpm_newsletter_nonce', 'nonce' );

    $email = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : '';

    if ( empty( $email ) || ! is_email( $email ) ) {
        wp_send_json_error( array( 'message' => 'Adresse invalide.' ), 400 );
    }

    // Traitement métier ici.
    wp_send_json_success( array( 'message' => 'Inscription enregistrée.' ) );
}

Le nonce vérifie l’origine, pas le contenu

check_ajax_referer() confirme que la requête provient bien d’une page où le nonce a été correctement généré et affiché, généralement via wp_localize_script() ou wp_add_inline_script(). Cela empêche un autre site d’envoyer discrètement des requêtes au nom d’un visiteur qui a ouvert une page infectée dans un autre onglet.

L'essentiel à retenir : Un nonce ne remplace pas un contrôle de capacité ; Toujours valider ET nettoyer les données reçues ; Répondre avec wp_send_json pour un format cohérent

Mais un nonce valide n’apporte aucune garantie sur la nature des données envoyées. Rien n’empêche un visiteur d’inspecter la requête réseau, de copier le nonce affiché sur la page et de renvoyer manuellement des centaines de requêtes avec des valeurs différentes. C’est pourquoi la validation des données reste une étape distincte et obligatoire, quel que soit le résultat du nonce.

Sanitiser à l’entrée, échapper à la sortie

  • Utiliser sanitize_email(), sanitize_text_field() ou absint() selon le type de donnée attendu
  • Ne jamais faire confiance à un champ caché du formulaire pour un identifiant de contenu sensible : le revérifier côté serveur
  • Encapsuler systématiquement l’entrée brute avec wp_unslash() avant sanitisation, WordPress ajoutant des antislashs à $_POST

Limiter ce qu’un point d’entrée public peut réellement faire

Un point d’entrée ouvert aux visiteurs non connectés ne doit jamais permettre une action qui modifie un contenu existant, écrit un fichier arbitraire ou déclenche l’envoi massif d’e-mails sans limite de fréquence. Si la fonctionnalité l’exige malgré tout, une limitation de débit devient indispensable, ne serait-ce qu’un compteur simple stocké en transient par adresse IP.

$cle = 'wpm_limite_' . md5( $_SERVER['REMOTE_ADDR'] );
$compteur = (int) get_transient( $cle );

if ( $compteur >= 5 ) {
    wp_send_json_error( array( 'message' => 'Trop de tentatives, réessayez plus tard.' ), 429 );
}

set_transient( $cle, $compteur + 1, MINUTE_IN_SECONDS * 10 );

Répondre proprement, sans fuite d’information

Les fonctions wp_send_json_success() et wp_send_json_error() encapsulent la réponse dans un format JSON cohérent et appellent wp_die() en interne, ce qui évite d’oublier l’arrêt du script après l’envoi. Un message d’erreur générique côté visiteur évite en outre de révéler des détails d’implémentation utiles à un attaquant, comme la structure exacte d’une requête SQL sous-jacente.

VérificationRôleFonction WordPress
Origine de la requêteAnti-CSRFcheck_ajax_referer()
Format des donnéesAnti-injectionsanitize_email(), absint()
Fréquence des appelsAnti-abusget_transient(), set_transient()
RéponseFormat cohérentwp_send_json_success(), wp_send_json_error()

Sur un point d’entrée public, la question à se poser n’est jamais « est-ce que ce visiteur va bien se comporter » mais « que se passe-t-il si quelqu’un envoie exactement le contraire de ce que j’attends ».

Pour aller plus loin

Un point d’entrée AJAX public bien conçu combine toujours trois couches : la vérification d’origine par le nonce, la validation stricte du contenu, et une limite raisonnable sur la fréquence des appels. Aucune des trois ne remplace les deux autres, et c’est précisément leur combinaison qui transforme une action ouverte à tous en une fonctionnalité robuste plutôt qu’en une porte laissée entrouverte.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Partager :

À propos de l'auteur

WordPress Développement

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi