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

Sécurité

Anatomie d’une injection SQL via un paramètre de tri non validé

Un paramètre censé trier des annonces par prix ou par surface transportait en réalité une clause SQL entière, sans qu'aucun échappement ne s'applique.

Par WordPress Développement • 21 septembre 2024 • 5 min de lecture • Aucun commentaire
Anatomie d'une injection SQL via un paramètre de tri non validé

?tri=prix ASC se transforme en ?tri=prix; DROP TABLE wp_annonces-- : c’est cette manipulation, testée lors d’un audit sur un moteur de recherche immobilière développé en extension WordPress sur mesure, qui a révélé une injection SQL directement exploitable via un paramètre a priori anodin.

Le moteur de recherche permettait de trier les résultats selon plusieurs critères — prix, surface, date de publication — chacun associé à un paramètre d’URL transmis tel quel à la construction de la requête SQL, sans passer par aucune validation préalable de sa valeur.

Le point aveugle d’une requête pourtant préparée

Le reste de la requête utilisait correctement $wpdb->prepare() pour les critères de filtrage, comme le prix minimum ou la ville recherchée. Mais la clause ORDER BY était construite par simple concaténation, avec la conviction erronée qu’une requête « préparée » protégeait l’ensemble de l’instruction SQL, quelle que soit la façon dont chacune de ses parties était assemblée.

function rechercher_annonces( $ville, $tri ) {
    global $wpdb;
    $sql = $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}annonces WHERE ville = %s ORDER BY " . $tri,
        $ville
    );
    return $wpdb->get_results( $sql );
}

Cette confusion technique est fréquente : $wpdb->prepare() paramètre des valeurs, jamais des identifiants comme un nom de colonne ou un mot-clé de direction de tri. Un paramètre concaténé directement dans la chaîne SQL, même entouré d’un appel à prepare() pour d’autres parties de la requête, reste exposé exactement comme s’il n’y avait aucune protection.

Ce que l’attaque a réellement permis

Lors du test contrôlé en environnement de recette, la valeur transmise dans le paramètre tri a permis d’exécuter une sous-requête arbitraire, révélant la structure complète de la table des utilisateurs par une injection de type UNION SELECT. Le moteur de base de données ne fait aucune distinction entre une clause de tri légitime et une instruction malveillante insérée au même endroit : il exécute ce qu’on lui soumet.

  • Le paramètre de tri provenait directement de l’URL, sans aucune liste de valeurs acceptées.
  • Aucune fonction d’échappement adaptée aux identifiants SQL n’était appliquée.
  • La présence de $wpdb->prepare() ailleurs dans la requête donnait une fausse impression de sécurité globale.
L'essentiel à retenir : prepare ne peut pas paramétrer un nom de colonne ou une direction de tri ; Une liste blanche de valeurs autorisées remplace efficacement l'échappement ; Le tri est souvent le seul paramètre de recherche non contrôlé par un formulaire

La correction : une liste blanche, pas un échappement

Un nom de colonne ou une direction de tri ne se paramètrent pas comme une valeur ordinaire. La seule approche fiable consiste à valider la valeur reçue contre une liste explicite de possibilités autorisées, avant toute construction de la requête, en rejetant silencieusement toute valeur qui n’y figure pas.

function rechercher_annonces( $ville, $tri_demande ) {
    global $wpdb;

    $colonnes_autorisees = array(
        'prix_asc'  => 'prix ASC',
        'prix_desc' => 'prix DESC',
        'surface'   => 'surface DESC',
        'recent'    => 'date_publication DESC',
    );

    $tri = $colonnes_autorisees[ $tri_demande ] ?? 'date_publication DESC';

    $sql = $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}annonces WHERE ville = %s ORDER BY $tri",
        $ville
    );
    return $wpdb->get_results( $sql );
}

La valeur insérée dans la requête finale ne provient plus jamais directement de l’utilisateur : elle provient d’un tableau défini dans le code, indexé par une clé fournie par l’utilisateur. Toute valeur absente de ce tableau retombe sur un tri par défaut, sans jamais atteindre la construction SQL.

Un défaut qui dépasse ce seul moteur de recherche

Ce type de paramètre — tri, direction, parfois nom de colonne d’affichage — apparaît fréquemment dans les interfaces de recherche ou de tableau de données, précisément parce qu’il semble éloigné des données sensibles habituellement associées à l’injection SQL, comme un identifiant ou un mot de passe. Cet éloignement apparent explique pourquoi il échappe souvent aux revues de sécurité centrées sur les champs de saisie évidents.

ParamètreTraitable par prepareNécessite une liste blanche
Valeur de filtre (ville, prix)OuiNon
Nom de colonne de triNonOui
Direction de tri (ASC / DESC)NonOui

Repère retenu de cet audit : dès qu’un paramètre influence la structure d’une requête plutôt que sa valeur, il ne relève plus du même mécanisme de protection, et mérite une vérification explicite avant toute construction de la clause concernée.

Notre verdict

La présence de $wpdb->prepare() dans une requête ne garantit rien sur les parties de cette même requête construites par ailleurs. Chaque fragment assemblé mérite d’être examiné individuellement : un identifiant de colonne ou une direction de tri ne se protègent jamais de la même façon qu’une valeur littérale, et une liste blanche explicite reste la seule réponse fiable à ce type de paramètre.

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