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)
- BK-136
- BK-137
- BK-138
- BK-147
- BK-148
- BK-149
- BK-150
- BK-151
- BK-156
- BK-158
- BK-159
- BK-160
- BK-161
- BK-162
- BK-163
- BK-164
- BK-165
- BK-166
- BK-170
- BK-171
- BK-172
- BK-177
- BK-183
- BK-186
- BK-187
- BK-189
- BK-190
- BK-191
- BK-192
- BK-193
- BK-195
- BK-196
- BK-197
- BK-198
- BK-199
- BK-201
- BK-205
- BK-207
- BK-208
- BK-209
- BK-210
- BK-211
- BK-212
- BK-213
- BK-214
- BK-215
- BK-216
- BK-217
- BK-218
- BK-219
- BK-220
- BK-221
- BK-229
- BK-230
- BK-231
- BK-232
- BK-233
- BK-234
- BK-235
- BK-236
- BK-239
- BK-240
- BK-241
- BK-242
- BK-243
- BK-244
- BK-245
- BK-246
- BK-248
- BK-249
- BK-250
- BK-251
- BK-252
- BK-253
- BK-254
- BK-255
- BK-256
- BK-257
- BK-258
- BK-259
- BK-260
- BK-261
- BK-262
- BK-263
- BK-264
- BK-265
- BK-266
- BK-267
- BK-268
- BK-269
- BK-270
- BK-271
- BK-272
- BK-275
- BK-276
- BK-277
- BK-278
- BK-279
- BK-280
- BK-295
- BK-313
Detalhe do BK Selecionado
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.phpredireciona 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.phpcase '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_idempedidosuser_idemorders- 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
- auditar
action.php case 'pedido' - mapear exatamente qual formulário do fluxo pré-checkout cria/reativa conta
- 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.phppedido.phpcheckout_pix.phpcheckout.phpcheckout_pix_pagseguro.php
Ajustes feitos
- inclusão de
config/security_request_helper.php - inclusão de
csrf_tokennos formulários inline comact=cadastro - inclusão de honeypot invisível
website page_returnpassou a sair escapado comhtmlspecialchars- em
checkout_pix.php, opedido_idfoi 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_idorders.id + user_id- em
pedido.php, o formulárioact=pedidopassou a enviar: csrf_token- honeypot invisível
page_returnescapado- 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.phpphp -l pedido.phpphp -l checkout_pix.phpphp -l checkout.phpphp -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.phpendurecido
- 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 porpedido.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