
Reproduit CVE-2026-1581, une injection SQL non authentifiée basée sur le temps dans wpForo Forum <=2.4.14, avec un laboratoire Docker et un PoC pour démontrer la vulnérabilité et vérifier le correctif.
| Champ | Détail |
|---|---|
| ID CVE | CVE-2026-1581 |
| Plugin | wpForo Forum |
| Versions affectées | <= 2.4.14 |
| Version corrigée | 2.4.15 |
| Type de vulnérabilité | Injection SQL basée sur le temps non authentifiée (ORDER BY) |
| Score CVSS | 7.5 (Élevé) |
| Vecteur CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
CVE-2026-1581 est une vulnérabilité d'injection SQL basée sur le temps non authentifiée dans le plugin wpForo Forum (<= 2.4.14). Le paramètre wpfob est utilisé dans une clause ORDER BY avec uniquement une assainissement de texte appliqué, permettant à un attaquant non authentifié d'injecter des expressions SQL arbitraires et de lire des données depuis la base de données.
L'éditeur a corrigé ce problème dans la version 2.4.15 en remplaçant sanitize_text_field() par wpforo_sanitize_orderby(), qui applique une liste blanche contextuelle.
À exécuter uniquement en localhost + Docker Compose.
Le PoC est une preuve de temporisation basée sur le temps pour démontrer la différence entre les versions vulnérable et corrigée.
Ne pas utiliser contre un système sans autorisation explicite.
Preuve de version : La page /community/ charge /wp-content/plugins/wpforo/assets/js/frontend.js?ver=2.4.14 (vulnérable) vs 2.4.15 (corrigée).
Preuve de code : sanitize_text_field(WPF()->GET['wpfob']) → wpforo_sanitize_orderby(..., context, default)
Preuve de comportement : wpfob=modified,(SELECT SLEEP(5)) provoque un délai d'environ 5 s sur la version vulnérable ; la version corrigée répond presque au niveau de référence.
L'avis CVE indique uniquement qu'il s'agit d'une injection SQL basée sur le temps via le paramètre wpfob, corrigée dans la version 2.4.15. Au moment de l'analyse, aucun PoC public n'était disponible.
Cette analyse a donc été construite par différenciation du code source entre les versions 2.4.14 et 2.4.15, en traçant le paramètre depuis l'entrée HTTP jusqu'à l'assainissement, puis jusqu'au point où il est utilisé pour construire la requête SQL — afin de comprendre la cause racine et de reproduire le problème.

wpfobEn commençant par une recherche de wpfob dans le code source, il a été constaté que la page Recent prend la valeur directement d'un paramètre GET et l'assigne comme argument orderby.

Vulnérable (2.4.14) — themes/classic/recent.php :
32 | $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'modified';
74 | $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'created';
Corrigé (2.4.15) — même fichier, assainisseur remplacé :
32 | $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'topics', 'modified' ) : 'modified';
74 | $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'posts', 'created' ) : 'created';
Pourquoi se concentrer sur
recent.php? Parce que c'est une route déclenchable oùwpfobest assigné directement à$args['orderby'].
ORDER BY ...Une fois $args['orderby'] défini, il circule dans le constructeur de requêtes de wpForo pour construire la clause ORDER BY.
Concaténation ORDER BY (vulnérable 2.4.14)
classes/Topics.php :

classes/Posts.php :

Explication
sanitize_text_field() ne fait que supprimer/nettoyer la chaîne — il n'applique pas de liste blanche des noms de colonnes autorisés.orderby est concaténé directement dans ORDER BY <orderby>, un attaquant peut injecter des expressions SQL arbitraires dans la position ORDER BY.Référence : https://developer.wordpress.org/reference/functions/sanitize_text_field/
recent.php32c32
< $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'modified';
---
> $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'topics', 'modified' ) : 'modified';
74c74
< $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'created';
---
> $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'posts', 'created' ) : 'created';
wpforo.php1036c1036
< $args['orderby'] = sanitize_text_field( $get['wpfob'] );
---
> $args['orderby'] = wpforo_sanitize_orderby( $get['wpfob'], 'search', 'relevancy' );
1077c1077
< $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'modified';
---
> $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'topics', 'modified' ) : 'modified';
1153c1153
< $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? sanitize_text_field( WPF()->GET['wpfob'] ) : 'created';
---
> $args['orderby'] = ( ! empty( WPF()->GET['wpfob'] ) ) ? wpforo_sanitize_orderby( WPF()->GET['wpfob'], 'posts', 'created' ) : 'created';
wpforo_sanitize_orderby()La version 2.4.15 introduit un assainisseur de liste blanche contextuelle qui renvoie la valeur par défaut si l'entrée n'est pas dans la liste autorisée :

wp_vuln (WordPress + wpForo 2.4.14) → http://localhost:8081wp_patched (WordPress + wpForo 2.4.15) → http://localhost:8082db_vuln / db_patched (MariaDB)seed_vuln / seed_patched — utilise wp-cli pour installer WordPress, installer le plugin, créer la page /community/ avec le shortcode [wpforo], configurer les permaliens, générer .htaccess, et créer des artefacts de vérification.En lisant le code source, wpfob est utilisé explicitement sur la page recent :
http://localhost:8081/community/recent/?view=openedhttp://localhost:8082/community/recent/?view=openedAu moins 1 sujet et 1 message doivent exister avant le test.