
Analyse des causes racines, vérificateur de version passif et PoC de laboratoire pour CVE-2026-18322, une élévation de privilèges non authentifiée dans le plugin WordPress Smart Popup by Supsystic.
Recherche de sécurité sur CVE-2026-18322, une vulnérabilité d'élévation de privilèges
non authentifiée dans le plugin WordPress Smart Popup by Supsystic
(popup-by-supsystic). Ce dépôt contient une analyse de cause racine dérivée du diff
source amont, les correctifs extraits, un outil de détection (fingerprinter de version
passif) pour identifier les installations affectées et un laboratoire pour le PoC
d'exploitation.
La vulnérabilité est publique et corrigée. Ce travail est publié à des fins défensives : aider les opérateurs à trouver et remédier les hôtes affectés.
| Plage de versions | Statut |
|---|---|
< 1.13.0 | Vulnérable |
>= 1.13.0 | Corrigée |
1.13.0 (publiée le 31.07.2026) est la première version corrigée ; elle répare les trois maillons de la chaîne décrite ci-dessous. Voir §1 de l'analyse.
Trois faiblesses indépendantes se composent en une création d'administrateur non authentifiée :
1. Une collision de carte de permissions — havePermissions() combinait deux cartes de
permissions avec array_merge(). Les deux utilisent la clé chaîne PPS_USERLEVELS
('userlevels'), et array_merge() écrase les clés chaînes, donc la courte liste par
défaut du contrôleur de base remplaçait intégralement la liste du module popup — supprimant
silencieusement la restriction administrateur de save et de neuf autres méthodes :
$permissions = $mod->getController()->getPermissions(); // [... 'save' ...]
$permissionsBase = $mod->getController()->getBasePermissions(); // ['getListForTbl','removeGroup','clear']
$permissions = array_merge($permissions, $permissionsBase); // base wins — 'save' is gone
La vérification échoue en ouvert : une action absente de la carte n'est jamais refusée.
2. Un nonce réutilisable envoyé à des inconnus — save exigeait toujours un
pps_nonce, mais l'e-mail de confirmation d'abonnement intégrait exactement cette action
nonce. Pour les utilisateurs déconnectés, un nonce WordPress est indexé sur uid=0, donc le
jeton généré pour un abonné anonyme est valide pour n'importe quel attaquant anonyme.
3. Aucune liste blanche de rôles côté serveur — createWpSubscriber() passait le rôle
configuré directement à WP_User::set_role(). La liste de rôles sûrs du plugin (qui exclut
administrator) n'était appliquée que lors du rendu du menu déroulant d'administration — un
contrôle purement côté UI.
Enchaîné : s'abonner pour obtenir un nonce → le rejouer contre popup::save pour définir
sub_wp_create_user_role=administrator → déclencher le flux d'abonnement → compte
administrateur persistant.
Procédure complète avec références fichier/ligne : docs/ANALYSIS.md.
poc/cve_2026_18322_check.py détermine si un site exécute une version affectée. Il ne
tente jamais d'exploitation : il émet de simples GET HTTP et lit la version que
l'installation publie sur elle-même.
git clone <this-repo> && cd CVE-2026-18322
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
requests est recommandé mais optionnel — l'outil se rabat sur la bibliothèque standard.
# Single target
python3 poc/cve_2026_18322_check.py https://example.com
# With supporting evidence
python3 poc/cve_2026_18322_check.py https://example.com -v
# Many targets, concurrently, exporting results
python3 poc/cve_2026_18322_check.py -f targets.txt -t 20 --json results.json
python3 poc/cve_2026_18322_check.py -f targets.txt --csv results.csv --only-vulnerable
# Through a proxy, ignoring TLS errors (lab use)
python3 poc/cve_2026_18322_check.py https://staging.internal -k --proxy http://127.0.0.1:8080
Les cibles peuvent être fournies en arguments ou via -f (- lit stdin). Un nom d'hôte
nu est supposé être https://.
$ python3 poc/cve_2026_18322_check.py https://example.com -v
[VULNERABLE] https://example.com/ (Smart Popup by Supsystic 1.11.2) < 1.13.0 affected by CVE-2026-18322; upgrade to 1.13.0+
- asset-version: plugin asset enqueued with ?ver=1.11.2 (https://example.com/)
- homepage-reference: references /plugins/popup-by-supsystic/ (https://example.com/)
- readme: plugin readme.txt is publicly readable (https://example.com/wp-content/plugins/popup-by-supsystic/readme.txt)
- readme-stable-tag: Stable tag: 1.11.2 (.../readme.txt)
| Statut | Signification |
|---|---|
VULNERABLE | Plugin détecté en < 1.13.0 |
NOT_VULNERABLE | Plugin détecté en >= 1.13.0 |
PLUGIN_DETECTED_VERSION_UNKNOWN | Plugin présent, version indéterminable — vérifier manuellement |
PLUGIN_NOT_DETECTED | Aucune preuve du plugin (pas une preuve d'absence) |
ERROR | Cible injoignable ou malformée |
Codes de sortie : 0 = aucune cible vulnérable, 1 = au moins une vulnérable, 2 = erreur d'utilisation.
Deux signaux passifs, tous deux de simples GET HTTP sur des ressources publiques :
?ver=PPS_VERSION
(classes/frame.php:410,473), donc la page d'accueil divulgue souvent la version exacte.readme.txt — Stable tag: fait autorité et prend le pas sur un asset en cache
potentiellement obsolète ; le changelog sert de repli.Le scan de la page d'accueil découvre aussi les répertoires wp-content renommés (par ex.
/app/ de Bedrock) afin que la recherche du readme suive la disposition réelle du site.
L'outil de détection ne tente pas d'exploitation. Aucun exploit armé pour ce CVE n'est publié ici : le seul code qui exécute la chaîne complète est strictement restreint au laboratoire local (voir ci-dessous).
Le vérificateur passif n'effectue qu'un fingerprinting de version. Son HttpClient n'expose
aucun verbe HTTP autre que GET — imposé par construction et vérifié dans la suite de
tests, il ne peut donc pas émettre de requête modifiant l'état, même par erreur. Il n'appelle
jamais popup::save, n'envoie pas de paramètre sub_wp_create_user_role, ne soumet pas de
formulaire d'abonnement, ne déclenche pas d'e-mail de confirmation, et ne crée ni ne modifie
aucun utilisateur, popup ou réglage. Il lit deux ressources publiques — la page d'accueil et
readme.txt — et rien d'autre.
Cette sécurité a un coût, dit clairement : le verdict ne vaut que ce que valent les métadonnées de version que l'hôte publie. Compromis et modes de défaillance : §9.