O Campo que Vence: Dois Campos, Um Nome, Uma Resposta
O desafio
A prefeitura de Brackwell paga seus voluntários por hora. Os pagamentos de um voluntário não batem com a folha de ponto impressa há quatro meses: a folha assinada pelo supervisor diz um número, a folha de pagamento diz outro, e o portal mostra o número assinado em todas as telas que alguém abre. Ninguém achou um jeito de editar uma folha já enviada. Esta é a página como o navegador a tinha no momento do envio do formulário. Leia o que o formulário realmente enviaria e informe o número de horas que o servidor aceitou.
O que você vai aprender
- Identificar nomes de parâmetros duplicados no código-fonte de um formulário
- Explicar como frameworks diferentes resolvem um parâmetro HTTP repetido
- Separar o valor que uma página renderiza do valor que ela envia
- Reconhecer um campo de relatório que lê dados diferentes dos que foram enviados
- Descrever por que uma interface internamente consistente ainda pode estar errada
Habilidades testadas
Pré-requisitos
- O que são um formulário HTML e um campo oculto (hidden input)
Como funciona
O HTTP não tem regra nenhuma dizendo que um parâmetro aparece uma única vez. Um formulário com dois campos compartilhando o mesmo nome envia os dois, e a requisição fica perfeitamente bem formada. O que a aplicação enxerga depende inteiramente de como o framework resolve essa repetição, e os frameworks discordam entre si: PHP e Rails ficam com a última ocorrência, o Express com seu parser padrão produz um array, o Flask devolve a primeira ocorrência via acesso por colchetes e a lista completa via getlist, e alguns load balancers e web application firewalls fazem esse parsing de um jeito diferente ainda do que o app por trás deles.
Essa discordância é a classe de bug em si. Ela importa mais quando uma ocorrência é visível e está sob o controle do usuário, e a outra é oculta e carrega um valor que significa dinheiro, permissão ou identidade. A correção que as pessoas costumam tentar, esconder o campo, não resolve nada: um campo oculto não é secreto nem somente leitura, ele só não é exibido na tela.
A segunda metade deste caso vale tanto quanto a primeira. Os relatórios leem hours_display, um campo diferente com um valor diferente, e por isso toda tela voltada para o usuário concordou consigo mesma por quatro meses. Quando dois caminhos de código leem dois campos que deveriam guardar a mesma coisa, mais cedo ou mais tarde eles vão discordar, e o campo que ninguém observa é o que decide.
Erros comuns
- Responder 12. Esse é o campo visível e o número assinado, e é exatamente por isso que a discrepância ficou sem explicação por quatro meses.
- Supor que o último campo vence. Verdade em PHP e Rails. O comentário no código menciona o Werkzeug, onde o acesso por colchetes devolve o primeiro.
- Responder 418.
total_hours_ytdé um total acumulado, e não é o campo que está sendo enviado para esta semana. - Ler somente a pré-visualização. Três dos campos que importam nunca aparecem na tela.
Como se proteger
Rejeite a ambiguidade em vez de resolvê-la.
- Trate um parâmetro repetido como uma requisição malformada. Leia com
getlist, e se a lista tiver mais de um item, retorne um 400 e registre isso em log. - Nunca envie um valor que o servidor consiga calcular sozinho. As horas pagas devem vir apenas do campo enviado, validado contra o registro de aprovação do supervisor no servidor.
- Tenha um único campo de referência. Relatórios, exportações e a execução do pagamento devem ler o mesmo valor armazenado, para que uma divergência seja impossível em vez de apenas invisível.
- Faça a checagem cruzada das saídas. Um relatório semanal comparando horas pagas com horas aprovadas teria detectado isso já na primeira folha de pagamento.