Reenviando um Saque Assinado: Reuso de Nonce e Double-Spend
O desafio
Esta API de carteira custodial assina cada saque com um nonce de uso único para que uma requisição não possa ser usada duas vezes. Só que ela nunca registra quais nonces já gastou. Envie primeiro o seu saque novo para vê-lo dar certo, depois reenvie um nonce de um saque assinado anterior e veja o mesmo dinheiro sair duas vezes. Leia o flag que o gasto duplo vaza.
O que você vai aprender
- Reconhecer um ataque de replay em que uma requisição assinada, previamente válida, é reenviada para repetir seu efeito
- Entender o propósito de um nonce de uso único e como ele falha quando não é rastreado no lado do servidor
- Executar um exploit em duas etapas: enviar uma requisição nova e depois reenviar uma anterior
- Ler as respostas da API para confirmar que um double-spend foi aceito
- Explicar por que nonces precisam ser marcados como consumidos de forma durável e duplicatas rejeitadas atomicamente
Habilidades testadas
Pré-requisitos
- Entendimento básico de requisições HTTP e corpos JSON
- Familiaridade com a ideia de nonce como um valor de uso único
- Consciência de que um saque movimenta fundos e deve ocorrer no máximo uma vez por autorização
Como funciona
Um nonce (número usado uma vez) serve para tornar uma requisição assinada de uso único: cada saque é autorizado com um nonce exclusivo, de modo que capturar e reenviar a requisição não consiga mover o dinheiro novamente. A proteção só funciona se o servidor lembrar quais nonces já liquidou e recusar qualquer repetição. Esta API gera e verifica a assinatura do nonce, mas nunca persiste um registro de nonce gasto, então o mesmo nonce pode ser apresentado quantas vezes o atacante quiser.
Essa lacuna se transforma em um ataque de replay. Um saque anterior, nonce 41, já havia sido assinado e concluído. Como nada marca o 41 como consumido, reenviá-lo é aceito novamente e a carteira transmite a mesma transferência uma segunda vez, um double-spend. Nenhuma assinatura foi forjada e nenhum valor foi editado; a requisição original, legitimamente assinada, foi simplesmente reenviada. Essa é uma das falhas mais comuns em fluxos de pagamento, carteira e autorização, e é por isso que idempotência é uma propriedade de segurança, não apenas de confiabilidade.
O exploit é inerentemente feito em duas etapas, o que o coloca acima de uma simples alteração de campo: primeiro você envia seu próprio nonce novo para confirmar que o endpoint funciona e ver como é uma liquidação real, depois reenvia um nonce anterior e observa a duplicata sendo aceita. Entender por que o 42 tem sucesso uma vez, mas o 41 tem sucesso de novo, é toda a lição.
Erros comuns
- Incrementar o nonce para um valor futuro como
43e desistir ao receber409- nonces mais altos ainda não foram assinados pela carteira, então são corretamente rejeitados. - Assumir que a assinatura na requisição a torna segura para reenvio, quando na verdade a assinatura prova autenticidade, não uso único.
- Enviar apenas o nonce novo e concluir que a API está correta, sem perceber que a falha só aparece quando um nonce já liquidado é reenviado.
- Tratar isso como um bug de tampering em vez da falha real: a requisição está inalterada e legitimamente assinada; o servidor simplesmente esqueceu que já havia honrado aquele nonce.
Como se proteger
Torne cada nonce verdadeiramente de uso único registrando seu consumo de forma durável e rejeitando qualquer reuso antes que os fundos se movam. Um nonce que não é rastreado é decoração, não defesa.
- Persista cada nonce liquidado por conta e rejeite duplicatas; ou aplique uma sequência estritamente crescente por conta e recuse qualquer valor não superior ao último valor liquidado.
- Torne a verificação de gasto e a liquidação atômicas (uma única transação ou uma restrição de unicidade em conta mais nonce) para que replays concorrentes não possam ambos vencer a corrida.
- Vincule a autorização assinada a todos os campos relevantes (valor, ativo, destino, nonce, chain id) e a uma expiração curta, para que uma requisição capturada não possa ser reutilizada depois ou contra uma transferência diferente.
- Registre e alerte sobre qualquer requisição que apresente um nonce já consumido, já que isso é uma tentativa inequívoca de replay.