
Análise de causa raiz, verificador de versão passivo e PoC de laboratório para CVE-2026-18322, uma escalada de privilégios não autenticada no plugin WordPress Smart Popup da Supsystic.
Pesquisa de segurança sobre CVE-2026-18322, uma vulnerabilidade de escalação de
privilégios não autenticada no plugin WordPress Smart Popup by Supsystic
(popup-by-supsystic). Este repositório contém uma análise de causa raiz derivada do
diff do código-fonte upstream, os patches extraídos, uma ferramenta de detecção
(fingerprinter passivo de versão) para identificar instalações afetadas e um laboratório para PoC de exploração.
A vulnerabilidade é pública e corrigida. Este trabalho é publicado para uso defensivo: ajudar operadores a encontrar e remediar hosts afetados.
| Faixa de versão | Status |
|---|---|
< 1.13.0 | Vulnerável |
>= 1.13.0 | Corrigida |
1.13.0 (lançada em 31.07.2026) é a primeira versão corrigida; ela repara todos os três elos da cadeia descrita abaixo. Veja §1 da análise.
Três fraquezas independentes se combinam para criar um administrador não autenticado:
1. Uma colisão de mapa de permissões — havePermissions() combinava dois mapas de permissões com
array_merge(). Ambos usam a chave string PPS_USERLEVELS ('userlevels'), e
array_merge() sobrescreve chaves string, então a lista curta padrão do controlador base
substituiu integralmente a lista do módulo popup — removendo silenciosamente a restrição de
administrador de save e de outros nove métodos:
$permissions = $mod->getController()->getPermissions(); // [... 'save' ...]
$permissionsBase = $mod->getController()->getBasePermissions(); // ['getListForTbl','removeGroup','clear']
$permissions = array_merge($permissions, $permissionsBase); // base wins — 'save' is gone
A verificação falha aberta: uma ação ausente do mapa nunca é negada.
2. Um nonce reutilizável enviado por e-mail a estranhos — save ainda exigia um pps_nonce, mas o
e-mail de confirmação de assinatura incorporava exatamente essa ação de nonce. Para usuários desconectados,
um nonce do WordPress é baseado em uid=0, então o token gerado para um assinante anônimo
valida para qualquer atacante anônimo.
3. Nenhuma allowlist de função no lado do servidor — createWpSubscriber() passava a função configurada
diretamente para WP_User::set_role(). A lista de funções seguras do plugin (que exclui
administrator) era aplicada apenas ao renderizar o dropdown de administração — um controle apenas de UI.
Encadeado: assine para obter um nonce → reutilize-o contra popup::save para definir
sub_wp_create_user_role=administrator → dispare o fluxo de assinatura → conta de administrador
persistente.
Passo a passo completo com referências de arquivo/linha: docs/ANALYSIS.md.
poc/cve_2026_18322_check.py determina se um site executa uma versão afetada. Ele nunca
tenta exploração: ele emite GETs HTTP simples e lê a versão que a instalação
publica sobre si mesma.
git clone <this-repo> && cd CVE-2026-18322
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
requests é recomendado, mas opcional — a ferramenta recorre à biblioteca padrão.
# 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
Alvos podem ser fornecidos como argumentos ou via -f (- lê da entrada padrão). Um hostname
simples é assumido como 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)
| Status | Significado |
|---|---|
VULNERABLE | Plugin detectado em < 1.13.0 |
NOT_VULNERABLE | Plugin detectado em >= 1.13.0 |
PLUGIN_DETECTED_VERSION_UNKNOWN | Plugin presente, versão não determinável — verifique manualmente |
PLUGIN_NOT_DETECTED | Nenhuma evidência do plugin (não é prova de ausência) |
ERROR | Alvo inacessível ou malformado |
Códigos de saída: 0 = nenhum alvo vulnerável, 1 = pelo menos um vulnerável, 2 = erro de uso.
Dois sinais passivos, ambos GETs HTTP simples de recursos públicos:
?ver=PPS_VERSION
(classes/frame.php:410,473), então a homepage frequentemente vaza a versão exata.readme.txt — Stable tag: é autoritativo e tem precedência sobre um asset em cache
potencialmente desatualizado; o changelog serve como fallback.A varredura da homepage também descobre diretórios wp-content renomeados (por exemplo, /app/ do Bedrock)
para que a busca do readme siga o layout real do site.
A ferramenta de detecção não tenta exploração. Nenhum exploit armado para este CVE é publicado aqui: o único código que executa a cadeia completa é rigidamente restrito ao laboratório local (veja abaixo).
O verificador passivo realiza apenas fingerprinting de versão. Seu HttpClient expõe nenhum
verbo HTTP além de GET — imposto por construção e verificado na suíte de testes, então
ele não pode emitir uma requisição que altere estado, mesmo por engano. Ele nunca chama popup::save,
envia um parâmetro sub_wp_create_user_role, submete um formulário de assinatura, dispara um
e-mail de confirmação, ou cria ou modifica qualquer usuário, popup ou configuração. Ele lê dois recursos
públicos — a homepage e readme.txt — e nada mais.
Essa segurança tem um custo, dito claramente: o veredito é tão bom quanto os metadados de versão que o host publica. Tradeoffs e modos de falha: §9.