Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-18322 — Root-cause analysis, passive version checker, and lab PoC for CVE-2026-18322, an unauthenticated privilege escalation in the Smart Popup by Supsystic WordPress plugin. | Kitploit
Tools/GitHubGitHub/i3it/cve-2026-18322
Privilege EscalationVulnerability ScannersVulnerability AnalysisExploitationWeb Application ExploitationSecurity VirtualizationWeb SecurityPenetration TestingPapers & ResearchLabs & Practice
GitHub
226 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
i3it/cve-2026-18322

CVE-2026-18322

Root-cause analysis, passive version checker, and lab PoC for CVE-2026-18322, an unauthenticated privilege escalation in the Smart Popup by Supsystic WordPress plugin.

View Repository

CVE-2026-18322 — Smart Popup by Supsystic Privilege Escalation

CVE Severity Affected Fixed in CWE Python Tests License

Security research on CVE-2026-18322, an unauthenticated privilege-escalation vulnerability in the WordPress plugin Smart Popup by Supsystic (popup-by-supsystic). This repository contains a root-cause analysis derived from the upstream source diff, the extracted patches, a detection tool (passive version fingerprinter) for identifying affected installations and a lab for exploitation PoC.

The vulnerability is public and patched. This work is published for defensive use: helping operators find and remediate affected hosts.


Affected versions

Version rangeStatus
< 1.13.0Vulnerable
>= 1.13.0Fixed

1.13.0 (released 31.07.2026) is the first patched release; it repairs all three links of the chain described below. See §1 of the analysis.


Vulnerability summary

Three independent weaknesses compose into unauthenticated administrator creation:

1. A permission-map collision — havePermissions() combined two permission maps with array_merge(). Both use the string key PPS_USERLEVELS ('userlevels'), and array_merge() overwrites string keys, so the base controller's short default list replaced the popup module's list wholesale — silently stripping the administrator restriction from save and nine other methods:

root@kitploit:~
$permissions     = $mod->getController()->getPermissions();      // [... 'save' ...]
$permissionsBase = $mod->getController()->getBasePermissions();  // ['getListForTbl','removeGroup','clear']
$permissions     = array_merge($permissions, $permissionsBase);  // base wins — 'save' is gone

The check fails open: an action absent from the map is never denied.

2. A reusable nonce mailed to strangers — save still required a pps_nonce, but the subscription confirmation email embedded that exact nonce action. For logged-out users a WordPress nonce is keyed on uid=0, so the token minted for an anonymous subscriber verifies for any anonymous attacker.

3. No server-side role allowlist — createWpSubscriber() passed the configured role straight to WP_User::set_role(). The plugin's safe role list (which excludes administrator) was applied only when rendering the admin dropdown — a UI-only control.

Chained: subscribe to get a nonce → replay it against popup::save to set sub_wp_create_user_role=administrator → trigger the subscribe flow → persistent admin account.

Full walkthrough with file/line references: docs/ANALYSIS.md.


Detection — the passive checker

poc/cve_2026_18322_check.py determines whether a site runs an affected version. It never attempts exploitation: it issues plain HTTP GETs and reads the version the install publishes about itself.

Install

root@kitploit:~
git clone <this-repo> && cd CVE-2026-18322
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

requests is recommended but optional — the tool falls back to the standard library.

Usage

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

Targets may be given as arguments or via -f (- reads stdin). A bare hostname is assumed to be https://.

Example

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)

Statuses

Exit codes: 0 = no vulnerable targets, 1 = at least one vulnerable, 2 = usage error.

How it detects

Two passive signals, both plain HTTP GETs of public resources:

  1. Asset cache-busters — the plugin enqueues its CSS/JS with ?ver=PPS_VERSION (classes/frame.php:410,473), so the homepage often leaks the exact version.
  2. readme.txt — Stable tag: is authoritative and takes precedence over a potentially stale cached asset; the changelog serves as a fallback.

The homepage scan also discovers renamed wp-content directories (e.g. Bedrock's /app/) so the readme lookup follows the site's real layout.


Safety

The detection tool does not attempt exploitation. No weaponized exploit for this CVE is published here: the only code that runs the full chain is hard-gated to the local lab (see below).

The passive checker performs version fingerprinting only. Its HttpClient exposes no HTTP verb other than GET — enforced by construction and asserted in the test suite, so it cannot issue a state-changing request even by mistake. It never calls popup::save, sends a sub_wp_create_user_role parameter, submits a subscription form, triggers a confirmation email, or creates or modifies any user, popup, or setting. It reads two public resources — the homepage and readme.txt — and nothing else.

That safety comes with a cost, stated plainly: the verdict is only as good as the version metadata the host publishes. Tradeoffs and failure modes: §9.

The lab exploit runs in the lab only

lab/exploit_full_chain.py is the one piece of code here that performs the actual attack, and it creates a WordPress administrator. It is published so the analysis can be reproduced rather than taken on trust — a claim about an authorization bug that cannot be demonstrated is an assertion, not a finding. It is written for the disposable stack in lab/ and for nothing else:

  • Non-local targets are refused. The target hostname is resolved and every address it returns must be loopback or RFC1918 private. A public DNS name is rejected even if it currently resolves to a private address — that is a well-known way to aim a "lab" tool at production.
  • --lab-confirm is mandatory. Both checks run in assert_lab_target() before the first HTTP request, so a rejected invocation touches the target not at all.
  • It can only read the lab's mailbox. Phase 1 harvests the reusable pps_nonce from the subscription confirmation email through the lab's Mailpit API (http://localhost:8025/api/v1/…). There is no code path for retrieving that email from anywhere else, so the chain has no first step against a host outside the lab.
  • The lab it targets is unreachable from off-box. Every service in lab/docker-compose.yml binds to 127.0.0.1.

As shipped it therefore cannot be pointed at a real site — it stops at REFUSING TO RUN before issuing a request:

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/.

These guards are load-bearing rather than decorative: the script is the real chain, and what keeps it a demonstration is that it will not run outside a throwaway local stack. Please do not remove them, and see SECURITY.md — changes that weaken them, or a standalone exploit meant to run outside the lab, are out of scope for this repository. To find out whether a real site is affected, use poc/ — the checker answers the same question without touching anything.

For an authoritative answer on a host you control:

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

Any output means the host is vulnerable — that line is precisely what 1.13.0 replaced.

Use only against systems you own or are explicitly authorized to test. See SECURITY.md.


Reproducing it — the demonstration lab

lab/ contains a disposable Docker stack that runs the vulnerability end-to-end, so the analysis can be verified rather than taken on trust:

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

Its most useful output is the version comparison — identical requests against three releases:

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

The lab is intentionally vulnerable and its exploit script creates a WordPress administrator. Everything binds to 127.0.0.1, and the script will not run against anything but a local lab — see the lab exploit runs in the lab only. It is not a scanner; to check a real site, use poc/.


Remediation

  1. Update to 1.13.0 or later.
  2. If you cannot update, disable the plugin. Partial mitigations are weak — the vulnerable action is reachable by any anonymous request.
  3. If you ran an affected version with subscription popups enabled, audit for compromise: unexpected administrator accounts, popups with sub_wp_create_user_role set to a privileged role, and POST requests to admin-ajax.php carrying pl=pps&mod=popup&action=save.

Queries and log greps: §10.


Repository layout

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

Development

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

The suite is fully offline — it uses recorded fixtures, never live targets. It covers version triage across the 1.13.0 boundary, target normalization, and the evidence rules that decide each status.

Safety properties are asserted, not just documented: that a full scan issues no state-changing requests, and that HttpClient exposes no HTTP verb other than GET.

Two regressions found during development are locked in: a CRLF readme.txt defeating line-anchored parsing, and an HTTP error page being miscounted as plugin evidence.

To reproduce the analysis from upstream sources:

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

License

MIT — see LICENSE. The analysed plugin is GPLv2-or-later and is not redistributed here; scripts/fetch_versions.sh fetches it from the official WordPress SVN repository.

Download Tool
StatusMeaning
VULNERABLEPlugin detected at < 1.13.0
NOT_VULNERABLEPlugin detected at >= 1.13.0
PLUGIN_DETECTED_VERSION_UNKNOWNPlugin present, version not determinable — verify manually
PLUGIN_NOT_DETECTEDNo evidence of the plugin (not proof of absence)
ERRORTarget unreachable or malformed