Skip to content
KitploitKITPLOIT
OutilsExploitsBlog
Soumettre
OutilsExploitsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-18322 — 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. | Kitploit
Outils/GitHubGitHub/i3it/cve-2026-18322
Escalade de PrivilègesScanners de VulnérabilitésAnalyse des VulnérabilitésExploitationExploitation d'Applications WebVirtualisation de SécuritéSécurité WebTests d'IntrusionArticles et RechercheLabs et Pratique
GitHub
2il y a 26 joursPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager
i3it/cve-2026-18322

CVE-2026-18322

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.

Voir le dépôt

CVE-2026-18322 — Élévation de privilèges dans Smart Popup by Supsystic

CVE Severity Affected Fixed in CWE Python Tests License

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.


Versions affectées

Plage de versionsStatut
< 1.13.0Vulnérable
>= 1.13.0Corrigé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.


Résumé de la vulnérabilité

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 :

root@kitploit:~
$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.


Détection — le vérificateur passif

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.

Installation

root@kitploit:~
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.

Utilisation

root@kitploit:~
# 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://.

Exemple

root@kitploit:~
$ 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)

Statuts

Codes de sortie : 0 = aucune cible vulnérable, 1 = au moins une vulnérable, 2 = erreur d'utilisation.

Comment il détecte

Deux signaux passifs, tous deux de simples GET HTTP sur des ressources publiques :

  1. Cache-busters d'assets — le plugin met en file ses CSS/JS avec ?ver=PPS_VERSION (classes/frame.php:410,473), donc la page d'accueil divulgue souvent la version exacte.
  2. 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.


Sécurité

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.

Le PoC du laboratoire ne s'exécute que dans le laboratoire

lab/exploit_full_chain.py est le seul morceau de code ici qui réalise l'attaque réelle, et il crée un administrateur WordPress. Il est publié pour que l'analyse puisse être reproduite plutôt que prise sur parole — une affirmation sur un bug d'autorisation qui ne peut être démontrée est une assertion, pas une découverte. Il est écrit pour la pile jetable dans lab/ et pour rien d'autre :

  • Les cibles non locales sont refusées. Le nom d'hôte cible est résolu et chaque adresse retournée doit être loopback ou privée RFC1918. Un nom DNS public est rejeté même s'il résout actuellement vers une adresse privée — c'est un moyen bien connu de pointer un outil « de labo » vers la production.
  • --lab-confirm est obligatoire. Les deux vérifications s'exécutent dans assert_lab_target() avant la première requête HTTP, donc une invocation rejetée ne touche pas du tout la cible.
  • Il ne peut lire que la boîte mail du laboratoire. La phase 1 récupère le pps_nonce réutilisable depuis l'e-mail de confirmation d'abonnement via l'API Mailpit du laboratoire (http://localhost:8025/api/v1/…). Il n'existe aucun chemin de code pour récupérer cet e-mail ailleurs, donc la chaîne n'a pas de première étape contre un hôte hors du laboratoire.
  • Le laboratoire qu'il cible est injoignable depuis l'extérieur. Chaque service dans lab/docker-compose.yml se lie à 127.0.0.1.

Tel que livré, il ne peut donc pas être pointé vers un site réel — il s'arrête à REFUSING TO RUN avant d'émettre une requête :

root@kitploit:~
$ python3 exploit_full_chain.py https://example.com --lab-confirm
REFUSING TO RUN: example.com resolves to non-local address(es) 104.20.23.154, ...
This script creates a WordPress administrator and is for the local lab only.
To test a real site, use the non-destructive checker in ../poc/.

Ces garde-fous sont porteurs plutôt que décoratifs : le script est la chaîne réelle, et ce qui en fait une démonstration est qu'il ne s'exécutera pas hors d'une pile locale jetable. Veuillez ne pas les retirer, et voir SECURITY.md — les modifications qui les affaiblissent, ou un exploit autonome destiné à s'exécuter hors du laboratoire, sont hors périmètre pour ce dépôt. Pour savoir si un site réel est affecté, utilisez poc/ — le vérificateur répond à la même question sans rien toucher.

Pour une réponse faisant autorité sur un hôte que vous contrôlez :

root@kitploit:~
grep -n "array_merge(\$permissions, \$permissionsBase)" \
     wp-content/plugins/popup-by-supsystic/classes/frame.php

Toute sortie signifie que l'hôte est vulnérable — cette ligne est précisément ce que 1.13.0 a remplacé.

À utiliser uniquement contre des systèmes que vous possédez ou êtes explicitement autorisé à tester. Voir SECURITY.md.


Reproduction — le laboratoire de démonstration

lab/ contient une pile Docker jetable qui exécute la vulnérabilité de bout en bout, afin que l'analyse puisse être vérifiée plutôt que prise sur parole :

root@kitploit:~
cd lab
./setup.sh                                                    # WordPress + plugin 1.12.0
../.venv/bin/python exploit_full_chain.py http://localhost:8080 --lab-confirm
./verify.sh                                                   # confirm from the database
./teardown.sh

Sa sortie la plus utile est la comparaison de versions — requêtes identiques contre trois versions :

root@kitploit:~
./setup.sh --plugin-version 1.11.2    # exploit succeeds
./setup.sh --plugin-version 1.12.0    # exploit succeeds
./setup.sh --plugin-version 1.13.0    # exploit fails at phase 2

Le laboratoire est intentionnellement vulnérable et son script d'exploit crée un administrateur WordPress. Tout se lie à 127.0.0.1, et le script ne s'exécutera contre rien d'autre qu'un laboratoire local — voir le PoC du laboratoire ne s'exécute que dans le laboratoire. Ce n'est pas un scanner ; pour vérifier un site réel, utilisez poc/.


Remédiation

  1. Mettez à jour vers 1.13.0 ou ultérieur.
  2. Si vous ne pouvez pas mettre à jour, désactivez le plugin. Les mitigations partielles sont faibles — l'action vulnérable est accessible par toute requête anonyme.
  3. Si vous avez exécuté une version affectée avec les popups d'abonnement activés, auditez pour détecter une compromission : comptes administrateurs inattendus, popups avec sub_wp_create_user_role défini sur un rôle privilégié, et requêtes POST vers admin-ajax.php portant pl=pps&mod=popup&action=save.

Requêtes et greps de logs : §10.


Arborescence du dépôt

root@kitploit:~
.
├── README.md
├── SECURITY.md                  Scope, authorized-use policy, disclosure
├── LICENSE                      MIT
├── requirements.txt
├── docs/
│   └── ANALYSIS.md              Full root-cause analysis and fix rationale
├── patches/
│   ├── 01-frame-permission-merge.diff              array_merge() → _mergePermissions()
│   ├── 02-subscribe-role-allowlist-and-nonce.diff  Role allowlist + dedicated nonce
│   └── 03-v1.12.0-confirmation-page-hardening.diff Unrelated hardening added in 1.12.0
├── poc/
│   └── cve_2026_18322_check.py  Passive version fingerprint (GET only)
├── lab/                         Disposable Docker lab — INTENTIONALLY VULNERABLE
│   ├── docker-compose.yml       MariaDB + WordPress 1.12.0 + Mailpit (loopback only)
│   ├── setup.sh / teardown.sh   Bring the lab up / destroy it
│   ├── exploit_full_chain.py    Full attack chain — CREATES AN ADMIN; lab-gated
│   └── verify.sh                Independent confirmation from the database
├── scripts/
│   └── fetch_versions.sh        Export plugin versions from SVN and regenerate diffs
└── tests/
    └── test_check.py            Offline unit tests for the passive checker

Développement

root@kitploit:~
pip install -r requirements.txt pytest
python3 -m pytest tests/ -v

La suite est entièrement hors ligne — elle utilise des fixtures enregistrées, jamais de cibles réelles. Elle couvre le triage de version à travers la frontière 1.13.0, la normalisation des cibles, et les règles de preuve qui décident de chaque statut.

Les propriétés de sécurité sont vérifiées, pas seulement documentées : qu'un scan complet n'émet aucune requête modifiant l'état, et que HttpClient n'expose aucun verbe HTTP autre que GET.

Deux régressions trouvées pendant le développement sont verrouillées : un readme.txt en CRLF qui déjouait l'analyse ancrée sur les lignes, et une page d'erreur HTTP comptée à tort comme preuve de plugin.

Pour reproduire l'analyse depuis les sources amont :

root@kitploit:~
./scripts/fetch_versions.sh          # exports 1.11.2 / 1.12.0 / 1.13.0 and regenerates patches/

Licence

MIT — voir LICENSE. Le plugin analysé est GPLv2-or-later et n'est pas redistribué ici ; scripts/fetch_versions.sh le récupère depuis le dépôt SVN officiel de WordPress.

Télécharger l’outil
StatutSignification
VULNERABLEPlugin détecté en < 1.13.0
NOT_VULNERABLEPlugin détecté en >= 1.13.0
PLUGIN_DETECTED_VERSION_UNKNOWNPlugin présent, version indéterminable — vérifier manuellement
PLUGIN_NOT_DETECTEDAucune preuve du plugin (pas une preuve d'absence)
ERRORCible injoignable ou malformée