
Reproduz o CVE-2026-1581, uma injeção SQL baseada em tempo não autenticada no wpForo Forum <=2.4.14, com um laboratório Docker e PoC para demonstrar a vulnerabilidade e verificar o patch.
| Campo | Detalhe |
|---|---|
| ID CVE | CVE-2026-1581 |
| Plugin | wpForo Forum |
| Versões Afetadas | <= 2.4.14 |
| Versão Corrigida | 2.4.15 |
| Tipo de Vulnerabilidade | Injeção SQL Baseada em Tempo Não Autenticada (ORDER BY) |
| Pontuação CVSS | 7.5 (Alta) |
| Vetor CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
CVE-2026-1581 é uma vulnerabilidade de Injeção SQL Baseada em Tempo Não Autenticada no plugin wpForo Forum (<= 2.4.14). O parâmetro wpfob é usado em uma cláusula ORDER BY com apenas sanitização de texto aplicada, permitindo que um atacante não autenticado injete expressões SQL arbitrárias e leia dados do banco de dados.
O fornecedor corrigiu isso na versão 2.4.15 substituindo sanitize_text_field() por wpforo_sanitize_orderby(), que aplica uma lista de permissões sensível ao contexto.
Execute apenas em localhost + Docker Compose.
O PoC é uma prova de temporização baseada em tempo para demonstrar a diferença entre as versões vulnerável e corrigida.
Não use contra qualquer sistema sem autorização explícita.
Prova de versão: A página /community/ carrega /wp-content/plugins/wpforo/assets/js/frontend.js?ver=2.4.14 (vuln) vs 2.4.15 (corrigida).
Prova de código: sanitize_text_field(WPF()->GET['wpfob']) → wpforo_sanitize_orderby(..., context, default)
Prova de comportamento: wpfob=modified,(SELECT SLEEP(5)) causa atraso de ~5s na versão vulnerável; a versão corrigida responde próximo à linha de base.
O aviso do CVE apenas afirma que se trata de uma injeção SQL baseada em tempo através do parâmetro wpfob, corrigida na versão 2.4.15. No momento da análise, nenhum PoC público estava disponível.
Este relatório foi, portanto, construído através de comparação de código-fonte entre 2.4.14 e 2.4.15, rastreando o parâmetro desde a entrada HTTP através da sanitização até o ponto onde é usado para construir a consulta SQL — a fim de entender a causa raiz e reproduzir o problema.

wpfobComeçando com uma busca por wpfob no código-fonte, descobriu-se que a página Recentes recebe o valor diretamente de um parâmetro GET e o atribui como argumento orderby.

Vulnerável (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';
Corrigido (2.4.15) — mesmo arquivo, sanitizador substituído:
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';
Por que focar em
recent.php? Porque é uma rota acionável ondewpfobé atribuído diretamente a$args['orderby'].
ORDER BY ...Uma vez que $args['orderby'] é definido, ele flui para o construtor de consultas do wpForo para construir a cláusula ORDER BY.
Concatenação ORDER BY (vuln 2.4.14)
classes/Topics.php:

classes/Posts.php:

Explicação
sanitize_text_field() apenas remove/limpa a string — não aplica uma lista de permissões de nomes de colunas permitidos.orderby é concatenado diretamente em ORDER BY <orderby>, um atacante pode injetar expressões SQL arbitrárias na posição do ORDER BY.Referência: 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()A versão 2.4.15 introduz um sanitizador de lista de permissões sensível ao contexto que retorna o valor padrão se a entrada não estiver na lista permitida:

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 — usa wp-cli para instalar o WordPress, instalar o plugin, criar a página /community/ com o shortcode [wpforo], configurar permalinks, gerar .htaccess e criar artefatos de verificação.A partir da leitura do código-fonte, wpfob é usado explicitamente na página recentes:
http://localhost:8081/community/recent/?view=openedhttp://localhost:8082/community/recent/?view=openedPelo menos 1 tópico e 1 postagem devem existir antes do teste.