Assinatura cega: o que você está aprovando de verdade quando a tela não diz nada útil

·6 min de leitura·Por SSP Editorial Team
Capa da SSP Academy: assinatura cega e aprovações de transação ilegíveis

Assinatura cega: o que você está aprovando de verdade quando a tela não diz nada útil

Existe um momento específico em que acontecem quase todas as grandes perdas em cripto. Não é quando uma chave é roubada. É quando alguém olha para um pedido de assinatura que não entende, decide que provavelmente está tudo bem, e toca em aprovar.

O setor tem um nome para isso: assinatura cega. Significa pôr sua assinatura em dados que você não consegue ler. E durante anos as carteiras trataram isso como algo normal.

O que uma tela de assinatura deveria fazer

A promessa de segurança da autocustódia se apoia numa suposição: que nada se move sem a sua aprovação. Multisig dois-de-dois, carteiras de hardware, assinadores isolados — cada um deles é uma máquina para transformar sua intenção em uma assinatura.

Esse maquinário vale exatamente o que vale o seu entendimento daquilo que você aprovou.

Se a tela diz «Enviar 0,5 ETH para 0x8f3C…», sua aprovação significa alguma coisa. Você a comparou com o que pretendia fazer. Se a tela diz «Interação com contrato — dados: 0xa22cb465000000000000000000000000d8dA6…0001», sua aprovação não significa absolutamente nada. Você não está consentindo com uma transação; está consentindo com um retângulo de hexadecimal.

Os atacantes sabem qual dessas duas telas precisam que você veja.

O que se esconde nos bytes

A carga útil de uma transação EVM são os calldata: um seletor de função de quatro bytes seguido de argumentos codificados em ABI. Perfeitamente legível por uma máquina e perfeitamente opaco para uma pessoa. Um punhado desses seletores é por onde o dinheiro vai embora.

approve(address,uint256) — seletor 095ea7b3. Concede a um contrato permissão para gastar seus tokens. Não é uma transferência; é uma autorização permanente. Não move nada quando você assina, e é exatamente por isso que ela passa despercebida. O esvaziamento acontece depois, no cronograma do atacante.

setApprovalForAll(address,bool) — seletor a22cb465. O equivalente para NFT, e pior: uma única assinatura dá a um operador autoridade sobre todos os tokens daquela coleção que você tiver, agora e no futuro. Não existe campo de valor para lhe dar sossego.

increaseAllowance(address,uint256) — seletor 39509351. Recarrega uma permissão existente. Muitas vezes passa batido porque não é o seletor que todo mundo foi orientado a vigiar.

transferFrom(address,address,uint256) — seletor 23b872dd. Move tokens de um endereço que já concedeu uma permissão. É a assinatura que finalmente gasta o que um approve anterior autorizou.

O padrão que vale internalizar: a transação que rouba seu dinheiro raramente é a transação que você assinou. Você assinou uma permissão. O roubo é uma transação separada, enviada depois, que você nunca vê.

Por que «ilimitado» é a palavra a procurar

Quase todo abuso de permissão compartilha uma característica: o valor é infinito na prática.

As dapps pedem aprovações ilimitadas porque é conveniente — aprovar uma vez, nunca mais ser perguntado. O resultado é que os usuários são treinados a conceder direitos de gasto ilimitados por rotina, e uma interface que exibe 115792089237316195423570985008687907853269984665640564039457584007913129639935 não está dizendo nada a ninguém.

Há uma sutileza que pega implementações ingênuas. O valor canônico da «aprovação infinita» é exatamente 2²⁵⁶−1, então a checagem óbvia é comparar com essa constante. Mas as dapps usam rotineiramente outros números astronomicamente grandes — metade do máximo, 0xff…f0, 10¹⁸ × 10³⁸ — que são ilimitados em todo sentido prático e ainda assim falham num teste de igualdade exata. Um aviso que só dispara no sentinela exato é um aviso que um atacante contorna subtraindo um.

O limiar certo fica bem abaixo do sentinela e bem acima de qualquer coisa real. A SSP sinaliza qualquer permissão igual ou superior a 2²⁵⁵ — cerca de 5,8 × 10⁷⁶, que supera qualquer oferta ERC-20 possível por dezenas de ordens de grandeza. Nada legítimo é jamais sinalizado por engano, e ajustar um valor logo abaixo do máximo não escapa do aviso.

Esse limiar se aplica só a chamadas que concedem permissões. Numa transferência comum, «ilimitado» não é uma ideia com sentido — você está movendo um valor específico — então lá o sentinela do máximo exato fica em paz.

O que a SSP faz

A SSP decodifica calldata para linguagem simples na tela de aprovação. Seletores reconhecidos são apresentados pelo que realmente são: quem é a contraparte, qual é o valor, se os direitos concedidos são ilimitados. O hexadecimal bruto não é mais o conteúdo principal — ele fica atrás de uma seção Avançado, para quem quiser.

Três decisões de projeto importam mais do que a decodificação em si.

O decodificador é só apresentação. Ele nunca muda o que é assinado. A aprovação sempre assina a carga original exata; o auxiliar apenas reapresenta bytes que antes eram mostrados como hexadecimal bruto. Um decodificador capaz de alterar a carga seria uma nova superfície de ataque em vez de uma defesa — o que você lê e o que você assina precisam ser os mesmos bytes, sempre.

Ele falha fechando. Seletor desconhecido, comprimento errado, hexadecimal malformado, preenchimento de endereço fora do padrão — qualquer coisa inesperada não devolve nada, e a interface recai numa ação genérica com o hexadecimal bruto em Avançado. Ele nunca adivinha. Um decodificador que adivinha é pior do que nenhum, porque um resumo errado e confiante é mais perigoso do que hexadecimal visível: o hexadecimal ao menos diz honestamente que você não entende aquilo.

Esse instinto de falhar fechando vai mais fundo do que funções desconhecidas. Um endereço codificado em ABI são doze bytes zerados seguidos de vinte bytes de endereço; uma palavra com qualquer outra coisa nesses bytes iniciais não está codificada canonicamente, e a SSP a trata como suspeita em vez de tentar interpretá-la. O mesmo vale para os booleanos — só as codificações canônicas são aceitas: tudo zero (falso) e 31 zeros seguidos de 0x01 (verdadeiro). Codificações não canônicas numa tela que concede direitos de gasto são um sinal vermelho, não um desafio de análise.

Símbolos e casas decimais de tokens nunca são adivinhados. Um valor legível só aparece quando o token é conhecido com segurança pelo registro existente no aparelho. Caso contrário, você recebe o número bruto em unidades base. Isso é deliberadamente menos bonito: exibir «5,0 USDC» para um contrato que apenas se autodenomina USDC transformaria o decodificador numa máquina de mentir, que é exatamente o resultado que o atacante quer.

E então há a parte estrutural, não cosmética. Na SSP a transação é construída num lugar e aprovada em outro: montada na extensão do navegador, decodificada e exibida de forma independente no seu celular, onde a SSP Key recalcula o hash da transação no próprio aparelho e se recusa a assinar se ele não corresponder ao que foi mostrado. Assinatura cega num único aparelho significa que uma tela comprometida basta. Aqui, a tela que mostra a ação decodificada e o aparelho que guarda a segunda chave são o mesmo aparelho, e ele verifica em vez de confiar.

O que fazer com as suas próprias aprovações

Trate approve e setApprovalForAll como as perigosas. São as assinaturas que custam dinheiro às pessoas, e parecem inofensivas justamente porque nada se move.

Recuse aprovações ilimitadas quando puder. Muitas dapps aceitam um valor limitado se você definir um. É atrito, e limita seu prejuízo ao que você realmente pretendia gastar.

Audite o que você já concedeu. Aprovações antigas não expiram. Uma permissão que você deu a um protocolo há dois anos continua viva, e se aquele contrato for comprometido mais tarde, ela é um caminho aberto até seus tokens. Revogar aprovações é rápido, e é a hora de faxina mais valiosa da autocustódia.

Quando a tela não lhe diz nada, esse é o sinal. Se sua carteira não consegue dizer o que uma transação faz, isso é informação — não um incômodo para atravessar no clique. A resposta certa a um pedido ilegível é parar, não apertar os olhos.

O objetivo nunca foi fazer você ler hexadecimal. É garantir que, quando você aprova alguma coisa, você e sua carteira concordem sobre o que era.

Compartilhar este artigo

Artigos relacionados