Origem: leitura do código para a reescrita da documentação (req-179 / BATCH-184), em 2026-09-25. Nenhum dos itens foi reportado por usuário; todos foram verificados lendo o código.
Lote Vinculado: a definir.
Status: partial — A1 e A2 corrigidos pela req-189 (BATCH-193, 2026-09-28); A3 a A11 seguem proposed, aguardando o Humano.
Achados (por gravidade)
A1 — SQL injection na busca e na ordenação da listagem administrativa (CRÍTICO) — ✅ corrigido na req-189
-
Onde:
gestor/bibliotecas/interface.php,interface_listar_ajax()(AJAXajax-opcao=listarde todo módulo com listagem). -
O quê: o
WHERE/ORDER BYé montado com$_REQUEST['search']['value'],$_REQUEST['columns'][i]['data']e$_REQUEST['columnsExtraSearch'], sem escape e sem lista de permissão. Não há sanitização global de$_REQUESTno roteador. -
Impacto: qualquer usuário autenticado com acesso a uma listagem (inclusive perfis restritos) lê qualquer tabela, como
usuarioscom os hashes de senha. -
Correção proposta: aceitar só colunas presentes na configuração salva na sessão (
$interface['columns']/columnsExtraSearchgravados no servidor), escapar o termo combanco_escape_field()e validardir∈ {asc, desc}. Teste de regressão reproduzindo a injeção.
A2 — Exclusão e troca de status por GET, sem CSRF (ALTO) — ✅ corrigido na req-189
-
Onde:
interface_excluir_iniciar()/interface_status_iniciar()+seguranca_csrf_requisicao_validar(), que só valida POST/PUT/PATCH/DELETE. -
O quê:
?opcao=excluir&id=…e?opcao=status&id=…&status=…alteram dados em GET. O cookie de sessão éSameSite=Lax, então vai junto em navegação de nível superior. -
Impacto: um link aberto por um administrador logado exclui (soft delete) ou desativa registros.
-
Correção proposta: exigir um token CSRF na query dessas duas opções (o JS da listagem e o modal de exclusão já têm o token), ou convertê-las em POST.
A3 — Nome de coluna controlável em verificar-campo (MÉDIO) e valor escapado duas vezes (BAIXO)
-
Onde:
interface_ajax_verificar_campo()→interface_verificar_campos(). -
O quê:
$_REQUEST['campo']vira nome de coluna noWHERE, protegido só porbanco_escape_field(), que não barra expressões sem aspas. O valor é escapado no AJAX e de novo dentro da função, então nunca casa com apóstrofos. -
Correção proposta: validar
campocontra os campos declarados no módulo (ou regex^[a-z0-9_]+$) e escapar o valor uma vez só.
A4 — Outros pontos sem escape em funções do núcleo (MÉDIO, depende do chamador)
-
gestor_variaveis_alterar()(valor),gestor_variaveis_globais()/gestor_variaveis()(id/módulo),gestor_layout()(id) evariaveis.php(grupo/id). Hoje recebem valores do código, mas são armadilhas para chamadores futuros. -
Correção proposta: escapar dentro das funções.
A5 — Segundo fator sem limite de tentativas (ALTO)
-
Onde:
perfil_usuario_signin_2fa()eperfil_usuario_oauth_authenticate_2fa()(gestor/modulos/perfil-usuario/perfil-usuario.php), comtwo_factor_validate_code()/two_factor_email_validate()(gestor/bibliotecas/2fa.php). -
O quê: cada POST com código errado só redireciona de volta para a tela; não há contador por usuário nem por IP, e a sessão pendente continua valendo. O TOTP aceito não é marcado como usado (pode ser repetido na janela de 90 s). O reenvio do código por e-mail (
?enviar-email, GET) também não tem limite. -
Impacto: quem já tem a senha pode testar códigos até acertar; o segundo fator vira só um atraso. O reenvio ilimitado permite inundar a caixa do usuário.
-
Correção proposta: contar falhas por usuário pendente (por exemplo, 5) e, ao estourar, descartar a sessão pendente e exigir novo login, reaproveitando os limites
acessos-*; guardar o últimotimeSliceaceito para recusar replay; limitar o reenvio (um a cada 60 s).
A6 — Upload de HTML e SVG servidos no mesmo domínio (MÉDIO)
-
Onde:
admin_arquivos_ajax_upload_file()(gestor/modulos/admin-arquivos/admin-arquivos.php), que só barra a lista dearquivo_extensao_perigosa(), egestor/controladores/arquivo-estatico/arquivo-estatico.php, que entregacontents/comContent-Typepela extensão (text/html,image/svg+xml), semContent-Dispositionnem CSP própria. -
O quê:
.html,.htm,.svge.xmlpassam no upload e abrem inline na origem do site. -
Impacto: XSS armazenado: quem tem acesso ao gerenciador de arquivos (inclusive um perfil restrito) publica uma página que roda JavaScript com a sessão de quem a abrir, como um administrador.
-
Correção proposta: servir esses tipos de
contents/comContent-Disposition: attachment(ouContent-Security-Policy: sandbox), ou bloqueá-los no upload por padrão com opção explícita no JSON do módulo.
A7 — Proteção contra sequestro de sessão contornável (MÉDIO)
-
Onde:
gestor_permissao_token()(gestor/gestor.php) comseguranca_sessao_validar()(gestor/bibliotecas/seguranca.php). -
O quê: o User-Agent e o bloco de IP do login ficam em variáveis da sessão (
sessoes_variaveis), e a validação aceita quando elas não existem ("fail-safe" para sessões antigas). O cookie de autenticação duraCOOKIE_LIFETIME(15 dias); a sessão,SESSION_LIFETIME(3 h). Quem copia só o cookie de autenticação e não envia o cookie de sessão recebe uma sessão nova, sem marcas, e passa na checagem. O mesmo acontece com o dono legítimo depois de 3 h, então a proteção só vale dentro da primeira sessão. -
Impacto: a defesa anunciada contra roubo de cookie praticamente não atua.
-
Correção proposta: gravar o User-Agent e o bloco de IP na linha do token (
usuarios_tokens) no login e validar contra ela; manter o "fail-safe" só para tokens emitidos antes da mudança. Para IPv6, comparar o prefixo /64 em vez do endereço inteiro.
A8 — Login social vincula conta pelo e-mail sem conferir verificação (BAIXO)
-
Onde:
perfil_usuario_oauth_callback()(gestor/modulos/perfil-usuario/perfil-usuario.php) comoauth_authenticate_code()(gestor/bibliotecas/oauth.php). -
O quê: sem vínculo prévio, o callback procura um usuário ativo com o mesmo e-mail do provedor e cria o vínculo, sem ler
email_verified(Google) nem equivalente. Ostatedo OAuth não é apagado depois de usado. -
Impacto: depende do provedor aceitar e-mail não verificado; se aceitar, quem cadastra lá o e-mail de um usuário do site entra como ele.
-
Correção proposta: exigir
email_verified === trueno Google (e confirmar o equivalente na Meta) antes do vínculo automático; apagaroauth_stateao validar.
A9 — Senha SMTP gravada em log (MÉDIO)
-
Onde:
comunicacao_email()(gestor/bibliotecas/comunicacao.php), blococatch. -
O quê: quando o envio falha fora do modo debug, a função grava
print_r($server, true)emgestor/logs/email-<data>.log, e$servercontémpass, a senha SMTP em texto claro. -
Impacto: quem lê os logs (backup, suporte, painel de hospedagem, permissões
0666dolog_disco()) obtém a credencial do e-mail do site. -
Correção proposta: registrar só host, porta, usuário e o modo de segurança; nunca a senha.
A10 — Prompts de IA editáveis e excluíveis por qualquer usuário do editor (BAIXO; MÉDIO em projetos multiusuário)
-
Onde:
ia_ajax_prompt_edit()eia_ajax_prompt_del()(gestor/bibliotecas/ia.php), acionadas pelo AJAX do editor HTML. -
O quê: a alteração e a exclusão filtram só por
id,alvoe idioma, sem conferir o dono (id_usuarios) nem permissão; o filtroia.prompts.load.whereisola só a listagem.ia_ajax_prompt_novo()também não gravaid_usuarios. -
Impacto: num projeto que separa prompts por usuário (hooks multiusuário), um usuário altera ou apaga o prompt de outro conhecendo o
id. -
Correção proposta: gravar
id_usuariosna criação e aplicar o mesmo filtro de dono (ou um hookia.prompts.write.where) na alteração e na exclusão.
A11 — Canal do módulo distribuído aceita reenvio de requisições (MÉDIO)
-
Onde:
api_module_distributed()/api_module_distributed_db()(gestor/controladores/api/api-module-distributed.php) commodulo_distribuido_verificar_assinatura()emodulo_distribuido_executar_local()(gestor/bibliotecas/modulo-distribuido.php). -
O quê: o central envia SQL assinado por HMAC (cabeçalho
X-C2F-Signature); o payload levatimestampenonce, mas o distribuído não confere nenhum dos dois. A única restrição à SQL é "uma instrução" (modulo_distribuido_sql_segura()): qualquer tabela, inclusiveusuarios, pode ser lida e escrita. -
Impacto: uma requisição assinada que vaze (log de proxy, ferramenta de depuração) pode ser reenviada indefinidamente, repetindo um
INSERT/UPDATE/DELETEno banco do cliente. O segredo equivale a acesso total ao banco. -
Correção proposta: recusar
timestampfora de uma janela (ex.: 300 s) enoncejá visto (guardado pela janela); restringir as tabelas às do módulo declaradas no manifesto.
Critérios de aceite
-
Testes PHPUnit que falham antes e passam depois, para A1, A2, A3, A5, A6, A7, A8, A9, A10 e A11.
-
Listagens, exclusão, status e verificação de campo funcionando no Lab (
[REDACTED]). -
As docs de
reference/libraries/interface.mdegestor.mdatualizadas, removendo os avisos[!CAUTION]correspondentes.