Cerebro Studio · Backlog · Changelog
RioNoTeatro • /www/wwwroot/rionoteatro.com.br/docs/BACKLOG.md
Abrir Studio Projeto externo em modo read-only; encaminhamento permitido, escrita bloqueada.

Backlog Unificado

Projeto: RioNoTeatro. Fonte principal: /www/wwwroot/rionoteatro.com.br/docs/BACKLOG.md.

Modo read-only: ações de escrita ficam disponíveis apenas para o Cérebro.

Sem itens pendentes em /www/wwwroot/rionoteatro.com.br/docs/BACKLOG.md.

Especificações Disponíveis (fora da fila pendente)

Detalhe do BK Selecionado

/www/wwwroot/rionoteatro.com.br/docs/backlog/BK-257-hardening-checkout-pix-ownership-tokenizacao.md • 2026-04-11T09:26:40.871Z

BK-257 - Hardening do checkout PIX: ownership guard, tokenização e cadastro inline

Objetivo

Revisar com calma a segurança do checkout_pix.php por ser a frente mais sensível do pagamento do RNT.

Pergunta inicial da rodada

O parâmetro sequencial checkout_pix.php?id=73715 configura um IDOR real hoje?

Leitura técnica do código atual

1. Pedido comum (pedidos)

Em checkout_pix.php:

```php

$sql_pedido = mysqli_query($conexao, "SELECT * FROM pedidos WHERE id = {$pedido_id} AND status_transacao < 3");

$pedido = mysqli_fetch_array($sql_pedido);

if ($pedido && $pedido['cliente_id'] == $_SESSION['CMS_X_Cliente']['id']) {

...

} elseif ($Logado) {

http_response_code(404);

...

}

```

Leitura:

  • a URL usa ID sequencial
  • mas o checkout não libera o pedido para outro cliente logado
  • se o pedido existir e não pertencer ao cliente da sessão, a tela cai em 404

2. ClubeRNT (orders)

Em checkout_pix.php:

```php

$sql_pedido = mysqli_query($conexao, "SELECT * FROM orders WHERE id = {$pedido_id_real} AND status < 3");

$pedido = mysqli_fetch_array($sql_pedido);

if ($pedido && $pedido['user_id'] == $_SESSION['CMS_X_Cliente']['id']) {

...

} else {

http_response_code(404);

}

```

Leitura:

  • o mesmo guard existe para a trilha do clube

3. Endpoints de ação do checkout

Em action.php, os casos críticos do checkout já filtram por sessão:

  • carteira:
  • SELECT * FROM pedidos WHERE id = '{$pedido_id}' AND cliente_id = '{$_SESSION['CMS_X_Cliente']['id']}'
  • PIX:
  • SELECT ... FROM pedidos ... WHERE p.id = '{$pedido_id}' AND p.cliente_id = '{$_SESSION['CMS_X_Cliente']['id']}'
  • cartão:
  • WHERE p.id = '{$pedido_id}' AND p.cliente_id = '{$_SESSION['CMS_X_Cliente']['id']}'
  • sync:
  • SELECT * FROM pedidos WHERE id = '{$pedido_id}' AND cliente_id = '{$_SESSION['CMS_X_Cliente']['id']}'

Conclusão desta análise

O que não está confirmado

  • IDOR clássico confirmado do pedido alheio no checkout atual

Motivo:

  • a leitura viva do código mostra binding de ownership por sessão em checkout_pix.php
  • as ações críticas do pagamento também filtram por cliente_id

O que continua preocupando

  • a URL ainda é sequencial/guessable
  • isso não é suficiente para chamar de IDOR já explorável hoje
  • mas é suficiente para justificar hardening futuro

Risco real remanescente

1. Enumeração

Mesmo com ownership guard, IDs sequenciais:

  • facilitam guessing
  • facilitam ruído de ataque
  • ajudam reconhecimento do fluxo

2. Cadastro inline do checkout

O ponto mais sensível desta frente pode não ser o id= em si, e sim o fluxo de entrada de usuário antes do pagamento.

Observação operacional:

  • o checkout_pix.php redireciona corretamente para login/cadastro quando não há sessão
  • mas a trilha que cria/login do cliente antes do pagamento ainda merece revisão própria

Especial atenção:

  • action.php case 'pedido'
  • eventual ausência de CSRF/rate limit/honeypot nessa trilha específica

Arbitragem do snippet externo Auth::meuRecurso(...)

Parte válida

  • a ideia de ownership guard é correta
  • a ideia de tokenizar a URL do checkout também é correta

Parte que não é drop-in para o RNT

  • o snippet assume coluna usuario_id
  • o RNT usa hoje:
  • cliente_id em pedidos
  • user_id em orders
  • então o exemplo não pode ser copiado literalmente

Direção recomendada

Curto prazo

  • revisar e endurecer a trilha de cadastro/login usada antes do checkout
  • validar se action.php case 'pedido' precisa do mesmo pacote:
  • CSRF
  • honeypot
  • rate limit
  • reCAPTCHA opcional

Médio prazo

  • tokenizar checkout com chave aleatória não sequencial

Exemplo de alvo futuro:

  • checkout_pix.php?token=<random>

em vez de:

  • checkout_pix.php?id=73715

Próximos passos

  1. auditar action.php case 'pedido'
  2. mapear exatamente qual formulário do fluxo pré-checkout cria/reativa conta
  3. decidir se a tokenização entra como hardening obrigatório ou melhoria defensiva fase 2

Achado adicional da rodada

O formulário inline de cadastro do checkout usa:

```php

<input type="hidden" name="act" value="cadastro" />

```

Ou seja:

  • ele não passa por case 'pedido'
  • ele passa por action.php case 'cadastro'

Impacto:

  • após o hardening do case 'cadastro', os checkouts inline precisavam ser alinhados com:
  • csrf_token
  • honeypot website

Hotfix aplicado nesta rodada

Arquivos ajustados:

  • action.php
  • pedido.php
  • checkout_pix.php
  • checkout.php
  • checkout_pix_pagseguro.php

Ajustes feitos

  • inclusão de config/security_request_helper.php
  • inclusão de csrf_token nos formulários inline com act=cadastro
  • inclusão de honeypot invisível website
  • page_return passou a sair escapado com htmlspecialchars
  • em checkout_pix.php, o pedido_id foi endurecido:
  • pedido comum agora exige inteiro válido
  • ClubeRNT extrai só a parte numérica
  • em checkout_pix.php, o ownership guard passou a ficar dentro da query:
  • pedidos.id + cliente_id
  • orders.id + user_id
  • em pedido.php, o formulário act=pedido passou a enviar:
  • csrf_token
  • honeypot invisível
  • page_return escapado
  • em action.php case 'pedido', entraram:
  • validação de csrf_token
  • validação de honeypot
  • rate limit por IP
  • validação de e-mail + confirmação de e-mail
  • senha mínima
  • nome mínimo
  • CPF válido
  • documento oficial válido

Validação local executada

  • php -l action.php
  • php -l pedido.php
  • php -l checkout_pix.php
  • php -l checkout.php
  • php -l checkout_pix_pagseguro.php

Análise do patch externo recebido para checkout_pix.php

O que estava certo

  • endurecer o parse do id
  • mover o ownership guard para dentro da query
  • escapar REQUEST_URI / retorno
  • tokenizar checkout no futuro

O que não deve entrar cru no RNT

  • abrir PDO paralelo dentro de checkout_pix.php
  • o RNT é legado em mysqli/helpers
  • a mudança em massa dentro de uma tela crítica de pagamento é arriscada
  • usar DB_NAME, DB_USER, DB_PASS
  • precisa conferir se as constantes existem exatamente com esses nomes no connect.php
  • CSRF próprio via $_SESSION['csrf_token']
  • isso conflita com o helper real já criado no projeto
  • o RNT hoje já tem rnt_request_csrf_token('cadastro_publico')
  • então o patch externo, do jeito que veio, não conversa com o action.php endurecido
  • a alegação de “IDOR confirmado” veio forte demais
  • o código vivo já fazia o bind por cliente_id/user_id
  • o problema real era mais de enumeração e de query retornar antes da checagem fora do SQL
  • isso foi melhorado sem precisar reescrever tudo em PDO hoje
  • o patch externo olhou o cadastro inline do checkout_pix.php, mas o fluxo pré-checkout principal do RNT também passa por pedido.php -> action.php case 'pedido'
  • sem alinhar esse segundo fluxo, a proteção ficaria incompleta

Decisão técnica

  • manter a direção do patch externo
  • aplicar só o recorte compatível com o RNT
  • deixar PDO/tokenização completa como fase seguinte, se necessário