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

Sécurité

« LIKE ‘%motcle%’ » qui remonte des résultats inattendus : l’injection de joker dans une recherche WordPress

Une recherche filtrée qui remonte trop de résultats n'est pas toujours un bug d'indexation. Le caractère joker d'une clause LIKE peut être la cause, et un vecteur d'exposition.

Par WordPress Développement • 26 janvier 2021 • 4 min de lecture • Aucun commentaire
« LIKE '%motcle%' » qui remonte des résultats inattendus : l'injection de joker dans une recherche WordPress

Une recherche interne remonte des résultats appartenant à d’autres clients dans une extension multi-tenant, alors que la requête SQL utilise bien $wpdb->prepare() et qu’aucune injection SQL classique par guillemets n’est détectée dans les tests habituels. Le diagnostic pointe ailleurs : vers le caractère joker lui-même.

Symptôme : une recherche censée être restreinte qui ne l’est pas

Le code incriminé ressemble à ceci, dans une fonction qui recherche des commandes par référence pour un client donné :

$reference = sanitize_text_field($_GET['ref'] ?? '');

$sql = $wpdb->prepare(
    "SELECT * FROM {$wpdb->prefix}commandes WHERE reference LIKE %s AND client_id = %d",
    '%' . $reference . '%',
    $client_id
);

$resultats = $wpdb->get_results($sql);

$wpdb->prepare() échappe correctement les guillemets et empêche toute injection SQL au sens classique du terme. Mais si $reference contient elle-même un caractère % ou _, ces caractères conservent leur signification spéciale dans la clause LIKE, indépendamment de l’échappement effectué par prepare(), qui protège la syntaxe SQL mais pas la sémantique du motif de correspondance.

Diagnostic : pourquoi prepare() ne suffit pas ici

$wpdb->prepare() traite %s comme un simple espace réservé de chaîne, sans connaissance du fait que cette chaîne sera utilisée dans une clause LIKE. Un utilisateur qui saisit % comme référence de commande obtient une clause LIKE '%%%', qui correspond à n’importe quelle valeur non vide. Combinée à un filtre par client_id mal isolé ailleurs dans le code, ou à une faille annexe permettant de faire varier ce paramètre, cette recherche peut exposer bien plus de lignes que prévu.

L'essentiel à retenir : Le caractère % dans une clause LIKE reste actif même après échappement des guillemets ; Un joker injecté élargit une recherche censée être restreinte ; wpdb::esc_like() neutralise spécifiquement ce caractère

Le symptôme observable en test reste souvent discret : une recherche avec le motif % seul remonte l’intégralité de la table au lieu d’un message d’absence de résultat, ce qui n’attire l’attention que si quelqu’un pense explicitement à tester ce caractère précis.

Correctif : neutraliser le joker avant de construire le motif

WordPress fournit une méthode dédiée exactement à ce problème : $wpdb->esc_like(), qui échappe les caractères % et _ pour qu’ils soient traités littéralement dans une clause LIKE, sans perdre leur échappement SQL au passage par prepare() ensuite :

$reference = sanitize_text_field($_GET['ref'] ?? '');
$reference_echappee = $wpdb->esc_like($reference);

$sql = $wpdb->prepare(
    "SELECT * FROM {$wpdb->prefix}commandes WHERE reference LIKE %s AND client_id = %d",
    '%' . $reference_echappee . '%',
    $client_id
);

$resultats = $wpdb->get_results($sql);

L’ordre des opérations compte : esc_like() s’applique d’abord sur la valeur brute, puis le résultat est inséré dans le motif avec les caractères % de délimitation, et l’ensemble passe enfin par prepare() pour l’échappement SQL standard. Inverser cet ordre laisserait les caractères de délimitation eux-mêmes se faire échapper à tort.

Prévention

  • Systématiser $wpdb->esc_like() dès qu’une valeur utilisateur entre dans une clause LIKE, même si elle passe déjà par prepare() pour l’échappement SQL général.
  • Ajouter un test unitaire qui envoie explicitement % et _ comme valeur de recherche et vérifie que le nombre de résultats reste borné, plutôt que de remonter l’ensemble de la table.
  • Sur une recherche exposée publiquement, limiter également le nombre de résultats retournés par requête, indépendamment de la correction du joker, pour réduire l’impact d’un contournement non anticipé.

Un cas voisin : le tri dynamique par colonne

Une recherche n’est pas le seul endroit où ce type d’oubli se glisse. Un tri de résultats par colonne, dont le nom provient d’un paramètre d’URL et se retrouve inséré dans la clause ORDER BY, pose un problème différent mais tout aussi révélateur d’une confusion entre échappement SQL et validation sémantique : prepare() ne peut pas paramétrer un nom de colonne de la même façon qu’une valeur, ce qui impose de valider ce nom contre une liste blanche de colonnes autorisées plutôt que de l’échapper.

En résumé

Une clause LIKE mal préparée pour son usage spécifique reste vulnérable même quand $wpdb->prepare() est utilisé correctement par ailleurs, parce que l’échappement SQL et l’échappement sémantique du motif de recherche répondent à deux problèmes distincts. $wpdb->esc_like() existe précisément pour combler cet angle mort.

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