Conn2Flow
Navegação das docs

SDD

Requisição 181 — Correções de Segurança Encontradas na Revisão da Documentação

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…

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() (AJAX ajax-opcao=listar de 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 $_REQUEST no roteador.

  • Impacto: qualquer usuário autenticado com acesso a uma listagem (inclusive perfis restritos) lê qualquer tabela, como usuarios com os hashes de senha.

  • Correção proposta: aceitar só colunas presentes na configuração salva na sessão ($interface['columns']/columnsExtraSearch gravados no servidor), escapar o termo com banco_escape_field() e validar dir ∈ {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 no WHERE, protegido só por banco_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 campo contra 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) e variaveis.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() e perfil_usuario_oauth_authenticate_2fa() (gestor/modulos/perfil-usuario/perfil-usuario.php), com two_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 último timeSlice aceito 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 de arquivo_extensao_perigosa(), e gestor/controladores/arquivo-estatico/arquivo-estatico.php, que entrega contents/ com Content-Type pela extensão (text/html, image/svg+xml), sem Content-Disposition nem CSP própria.

  • O quê: .html, .htm, .svg e .xml passam 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/ com Content-Disposition: attachment (ou Content-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) com seguranca_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 dura COOKIE_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) com oauth_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. O state do 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 === true no Google (e confirmar o equivalente na Meta) antes do vínculo automático; apagar oauth_state ao validar.

A9 — Senha SMTP gravada em log (MÉDIO)

  • Onde: comunicacao_email() (gestor/bibliotecas/comunicacao.php), bloco catch.

  • O quê: quando o envio falha fora do modo debug, a função grava print_r($server, true) em gestor/logs/email-<data>.log, e $server contém pass, a senha SMTP em texto claro.

  • Impacto: quem lê os logs (backup, suporte, painel de hospedagem, permissões 0666 do log_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() e ia_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, alvo e idioma, sem conferir o dono (id_usuarios) nem permissão; o filtro ia.prompts.load.where isola só a listagem. ia_ajax_prompt_novo() também não grava id_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_usuarios na criação e aplicar o mesmo filtro de dono (ou um hook ia.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) com modulo_distribuido_verificar_assinatura() e modulo_distribuido_executar_local() (gestor/bibliotecas/modulo-distribuido.php).

  • O quê: o central envia SQL assinado por HMAC (cabeçalho X-C2F-Signature); o payload leva timestamp e nonce, mas o distribuído não confere nenhum dos dois. A única restrição à SQL é "uma instrução" (modulo_distribuido_sql_segura()): qualquer tabela, inclusive usuarios, 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/DELETE no banco do cliente. O segredo equivale a acesso total ao banco.

  • Correção proposta: recusar timestamp fora de uma janela (ex.: 300 s) e nonce já visto (guardado pela janela); restringir as tabelas às do módulo declaradas no manifesto.

Critérios de aceite

  1. Testes PHPUnit que falham antes e passam depois, para A1, A2, A3, A5, A6, A7, A8, A9, A10 e A11.

  2. Listagens, exclusão, status e verificação de campo funcionando no Lab ([REDACTED]).

  3. As docs de reference/libraries/interface.md e gestor.md atualizadas, removendo os avisos [!CAUTION] correspondentes.

O Futuro é Híbrido: Humano + IA.

O Conn2Flow é a ferramenta. Se você precisa do Arquiteto para desenhar a estratégia da sua empresa usando essa tecnologia...

Contrate Consultoria Especializada