Vulnerabilidade de repetição de assinatura em contrato inteligente | BlockAudit

Um truque útil é assinar mensagens fora da cadeia e ter um contrato que exija essa assinatura antes de executar uma função.
Por exemplo, esta técnica é usada para: -
- reduzir o número de transações na cadeia
- transação sem gás, chamadameta transaction
Vamos ver do que estamos falando
Blockchains dependem fortemente de assinaturas criptográficas. As transações são assinadas com as chaves privadas correspondentes, permitindo que os remetentes das transações sejam associados às suas contas. A contabilidade do blockchain seria inoperável sem esse recurso.
As assinaturas digitais também são frequentemente validadas diretamente nos contratos inteligentes da Ethereum, permitindo que um ou mais verificadores autorizem ações enviando assinaturas fora da cadeia ( ou mesmo assinaturas geradas por outro contrato inteligente ).
Isso é frequentemente usado em cofres de assinatura múltipla ou contratos de votação para enviar várias assinaturas ao mesmo tempo ou para delegar autorização.
Ataques de repetição de assinatura são uma vulnerabilidade comum em tais implementações.
Às vezes, a verificação de assinatura em contratos inteligentes é necessária para melhorar a usabilidade ou economizar custos de gás. Uma implementação segura deve evitar ataques de repetição de assinatura.
Por exemplo: -
Acompanhar todos os hashes de mensagens processadas e permitir o processamento apenas de novos hashes de mensagens. Um usuário mal-intencionado pode atacar um contrato que carece desse controle e obter um hash de mensagem enviado por outro usuário e processado várias vezes.
Assinaturas Digitais Criptográficas
As assinaturas digitais são os primitivos de chave pública da autenticação de mensagens. No mundo físico, as assinaturas manuscritas são comumente usadas em mensagens manuscritas ou digitadas. Eles são usados para vincular o signatário à mensagem.
Uma assinatura digital é um valor criptográfico gerado por dados e uma chave secreta conhecida apenas pelo signatário.
Modelo de Assinatura Digital
O procedimento completo é explicado minuciosamente nos seguintes pontos:

- Cada usuário deste esquema possui um conjunto de chaves públicas e privadas.
- Os pares de chaves usados para criptografia/descriptografia e assinatura/verificação geralmente são distintos um do outro. A chave pública é chamada de chave de verificação e a chave privada é chamada de chave de assinatura.
- Os dados são enviados para a função hash pelo signatário, que produz o hash.
- O algoritmo de assinatura então gera a assinatura digital no hash fornecido usando o valor do hash e a chave de assinatura. Os dados recebem uma assinatura e ambos são posteriormente submetidos ao verificador.
- O algoritmo de verificação é alimentado pelo verificador juntamente com a assinatura digital e a chave de verificação. O resultado do algoritmo de verificação é algo útil. Nos dados que recebe, o verificador também usa o mesmo algoritmo de hash para produzir um valor de hash.
- Esse valor de hash e os resultados do processo de verificação são comparados para verificação. O verificador determina se a assinatura digital é legítima com base nos resultados da comparação.
- Ninguém mais pode usar a chave “privada” do signatário para estabelecer uma assinatura digital, portanto o signatário não pode mais tarde retirar sua assinatura dos dados.
A camada de protocolo
Somente transações com assinaturas válidas são incluídas em novos blocos, graças à rede Ethereum . Para transações, isso oferece os seguintes atributos de segurança:
- Autenticação : A assinatura é usada pelos nós Ethereum para confirmar que a pessoa que assina a transação é a proprietária da chave privada conectada ao seu endereço público. Portanto, os desenvolvedores podem ter certeza de que o msg.sender é genuíno.
- Integridade : Integridade é a condição de que a transação não foi alterada depois de assinada; caso contrário, a assinatura é nula.
- Não repúdio: a assinatura de uma transação e quaisquer alterações de estado feitas pela parte signatária em posse da chave privada não podem ser contestadas. A chave privada pertence ao endereço público listado no campo de .
A mesma assinatura pode ser usada várias vezes para executar uma função. Isso pode ser prejudicial se a intenção do signatário for aprovar uma transação uma vez.
Exemplo de código
Vamos examinar o bug.
function unlock(
address _to,
uint256 _amount,
uint8[] _v,
bytes32[] _r,
bytes32[] _s
)
external
{
require(_v.length >= 20);
bytes32 hashData = keccak256(_to, _amount);
for (uint i = 0; i < _v.length; i++) {
address recAddr = ecrecover(hashData, _v[i], _r[i], _s[i]);
require(_isValidator(recAddr));
}
to.transfer(_amount);
}
A mensagem que é assinada pelos validadores usando a técnica ECDSA é onde está o problema com o código mencionado. O endereço do destinatário e o dinheiro necessário são as únicas informações na mensagem. Nada na mensagem pode ser utilizado para evitar o uso da mesma assinatura mais de uma vez. Considere o seguinte caso:
- Sam transfere 2.000 ETH da rede vinculada de volta para a cadeia Ethereum usando a mesma quantidade de moeda.
- O processamento desta transação cross-blockchain é Tom, um retransmissor. Para liberar 2.000 ETH do contrato e enviá-lo para Sam, ele reúne as assinaturas do validador necessárias, bloqueia a quantidade certa na cadeia conectada e, em seguida, usa a função de desbloqueio.
- No blockchain, a transação que contém os arrays de valores de assinatura é vista por todos.
- Agora que Sam copiou as matrizes de assinatura, ele pode enviar independentemente uma chamada de desbloqueio. Mais uma vez, o processo de desbloqueio será bem-sucedido, resultando na transferência de 2.000 ETH para Sam.
- Sam pode continuar dessa maneira até que o contrato se esgote.

Um ataque de repetição de assinatura é o que é descrito no exemplo acima. É concebível, pois não há como determinar se essa mensagem assinada específica é única ou se foi usada anteriormente.
Assinar mensagens com nonce
e endereço do contrato.
public uint256 nonce;
function unlock(
address _to,
uint256 _amount,
uint256 _nonce,
uint8[] _v,
bytes32[] _r,
bytes32[] _s
)
external
{
require(_v.length >= 20);
require(_nonce == nonce++);
bytes32 hashData = keccak256(_to, _amount, _nonce);
for (uint i = 0; i < _v.length; i++) {
address recAddr = ecrecover(hashData, _v[i], _r[i], _s[i]);
require(_isValidator(recAddr));
}
to.transfer(_amount);
}
Para se proteger contra ataques de repetição de assinatura, considere as seguintes recomendações:
- Armazene cada hash de mensagem que foi processado pelo contrato inteligente. Quando novas mensagens são recebidas, verifique as já existentes e apenas prossiga com a lógica de negócios se for um novo hash de mensagem.
- Inclua o endereço do contrato que processa a mensagem. Isso garante que a mensagem só possa ser usada em um único contrato.
- Sob nenhuma circunstância gere o hash da mensagem incluindo a assinatura. A
ecrecover
função é suscetível à maleabilidade da assinatura.
Assinaturas não exclusivas podem ser reproduzidas em várias situações, como visto no exemplo acima. Para evitar ataques de repetição, é crucial na maioria das situações garantir que as assinaturas correspondam especificamente a cada chamada. Além disso, por esse motivo, um nonce é incluído em todas as transações Ethereum.
Referências: -
https://solidity-by-example.org/hacks/signature-replay/
https://blog.finxter.com/smart-contract-replay-attack-solidity/
https://swcregistry.io/docs/SWC-121
Lutando com segurança web3!!! Conecte-se conosco!!!
BlockAudit :- Por que nós??
A BlockAudit tem os recursos e o conhecimento para criar soluções de segurança cibernética que economizam milhões de dólares.
Linkedin | Site | Twitter
