Price Tampering: Quando o Servidor Confia no Preço Enviado pelo Cliente
O desafio
Aqui está a requisição de pagamento que a loja envia quando você compra o item #42. Olhe com atenção: o preço que será cobrado de você está ali na requisição, e o servidor confia nele. Reduza o preço, envie o pedido e leia a nota de confirmação que a resposta vaza.
O que você vai aprender
- Reconhecer uma falha de confiança no preço do lado do cliente, em que o valor cobrado é lido de um parâmetro da requisição
- Adulterar um único campo numérico em uma requisição de checkout capturada e reenviá-la
- Ler uma resposta HTTP/JSON para confirmar que o preço mais baixo foi realmente cobrado
- Explicar por que preços e outros valores relacionados a dinheiro devem ser rederivados no servidor a partir de uma fonte confiável
- Distinguir validação de entrada (rejeitar formatos inválidos) de autoridade (decidir qual deveria ser o valor)
Habilidades testadas
Pré-requisitos
- Entendimento básico de requisições HTTP, parâmetros de consulta e corpos JSON
- Familiaridade com o conceito de carrinho de compras e fluxo de checkout
Como funciona
Uma falha de price-tampering é uma vulnerabilidade de lógica de negócio: a aplicação coloca um valor relacionado a dinheiro, o preço que será cobrado do comprador, dentro de uma requisição que o comprador controla, e o servidor trata esse valor como a fonte da verdade. Como cada byte de uma requisição passa pelo navegador e por qualquer proxy que o usuário execute, o comprador pode simplesmente reescrever price=4999 para price=1 antes de a requisição ser enviada. O servidor cobra o que lhe foi informado, e o pedido é concluído a um preço que o comerciante nunca definiu.
O detalhe sutil é que isso não é um bug de injeção nem de autenticação. O cookie de sessão é válido, o campo é um número perfeitamente bem formado, e nada está malformado. O erro é de autoridade: um preço é um fato do lado do servidor que pertence ao catálogo, indexado pelo id do item. A requisição deveria dizer qual item o comprador quer e em que quantidade, nunca quanto ele custa. Quando o servidor deixa o cliente definir o preço, ele entrega o controle de sua receita ao atacante.
Esse padrão aparece em qualquer lugar em que um número enviado pelo cliente seja confiado para uma decisão de dinheiro ou quantidade: descontos, taxas de envio, pontos de fidelidade, moeda e impostos. A correção confiável é a mesma em todos os casos: buscar o valor em um repositório confiável no servidor e ignorar o que o cliente enviou.
Erros comuns
- Presumir que o preço mostrado na requisição é fixado pelo servidor, quando na verdade é apenas um número no corpo da requisição que qualquer pessoa pode reescrever antes de enviar.
- Recorrer a caracteres especiais ou payloads de injeção em vez de perceber que um número simples e editável decide quanto você será cobrado.
- Definir o preço como
0e desistir quando o servidor rejeita - a validação só impõe um mínimo de 1, então o menor valor aceito é 1. - Tratar isso como uma validação de entrada ruim em vez da falha real: o servidor nunca deveria ter aceitado um preço vindo do cliente.
Como se proteger
Nunca leia um preço, desconto, taxa ou qualquer valor relacionado a dinheiro a partir de um parâmetro de requisição, campo do corpo ou campo oculto de formulário. No servidor, busque o item pelo seu id no seu catálogo ou serviço de precificação confiável, calcule o total ali e cobre esse valor: a requisição só pode descrever o que o usuário quer comprar, nunca quanto isso custa.
- Derive o preço unitário no servidor a partir do id do item e do seu repositório de preços; descarte qualquer campo de preço enviado pelo cliente.
- Recalcule o total do pedido, os impostos e o frete no servidor no momento do checkout, e depois concilie com a autorização de pagamento.
- Valide também a quantidade e o id do item, e rejeite pedidos cujo total calculado não corresponda ao valor efetivamente autorizado.
- Registre e alerte quando uma requisição trouxer um preço divergente do catálogo, para que tentativas de adulteração fiquem visíveis.