Acabamos de ver como uma pequena brecha leva a uma perda financeira (de magnitude variável), da mesma forma que os contratos inteligentes desenvolvidos sobre o Solidity estão sujeitos a vários ataques conhecidos e desconhecidos. Os exploradores tiram proveito de bugs e brechas para descobrir contratos inteligentes e manipulá-los para realizar ataques. Aqui, apresentamos uma lista abrangente dos 5 principais erros comumente encontrados na linguagem de programação Solidity.
Na QuillAudits, seguimos a metodologia adaptativa para obter a essência de cada hack e implementar seus aprendizados em futuros contratos inteligentes para evitar qualquer ameaça potencial.
Erros na linguagem de programação de solidez
1. Chamada Externa Não Verificada
Estamos tratando desse problema em primeiro lugar porque é uma das armadilhas do Solidity mais comumente observadas. Geralmente, o envio de ether para qualquer conta externa é realizado através do transferir() função. Além disso, as duas funções mais utilizadas para fazer uma chamada externa são; ligar() e enviar(), aqui principalmente o ligar() A função é amplamente usada para realizar chamadas externas versáteis pelos desenvolvedores.
Embora o ligar() e enviar() funções retornam um valor booleano especificando se a chamada foi bem-sucedida ou não. Assim, neste caso, se alguma das funções ligar() or enviar() falhar em realizar a tarefa, eles reverterão com um falsa Portanto, se o desenvolvedor não verificar o valor de retorno, isso se tornaria uma armadilha.
A vulnerabilidade
Considere o exemplo abaixo:
contrato Lotto {
boolpublic payedOut = false;
endereço público vencedor;
uintpublic winAmount;
//… funcionalidade extra aqui
function sendToWinner () public {
require (! payedOut);
vencedor.send (winAmount);
payedOut = true;
}
functiondrawLeftOver () public {
requer (payedOut);
msg.sender.send (this.balance);
}
}
No contrato semelhante a loteria acima, podemos observar que um vencedor recebe winAmount de éter deixando um pouco de sobra para ser retirado de qualquer agente externo.
Aqui, a armadilha para o contrato existe na linha [11], onde um enviar é usado sem validação cruzada da resposta. No exemplo acima, um vencedor cuja transação falha (seja por deficiência de Gás ou se for um contrato que intencionalmente lance na função de fallback), autoriza pago para ser definido para verdadeiro independentemente se a transação do éter foi um sucesso ou não. Neste caso, qualquer explorador pode retirar o do vencedor ganhos através do retirarLeftOver função.
Abordagem do QuillAudit
Nossa equipe interna de desenvolvedores resolve esse bug com o uso de [transferir] função em vez de [enviar] , pois [transferência] será revertida se a transação externa for revertida. E se você estiver usando [enviar], sempre verifique o valor de retorno.
Uma das abordagens robustas que seguimos é a utilização de um [padrão de retirada]. Aqui, isolamos logicamente a funcionalidade de envio externo do resto da base de código e colocamos a carga de transações potencialmente falhadas no usuário final, pois é ele quem chama a função de retirada.
2. Reentrada
Os contratos inteligentes Ethereum chamam e utilizam códigos de outros contratos externos e, para conduzir isso, os contratos são obrigados a enviar chamadas externas. Essas chamadas externas são vulneráveis e sujeitas a ataques, um desses ataques ocorreu recentemente no caso de hack do DAO.
A vulnerabilidade
Os invasores realizam esses ataques quando um contrato envia éter para um endereço desconhecido. Nesse caso, o atacante pode criar um contrato em um endereço externo que possua código malicioso na função de fallback, e esse código malicioso será invocado quando o contrato enviar ether para este endereço.
Facto: O termo 'Reentrada' foi cunhado a partir do fato de que, quando um contrato malicioso externo chama uma função sobre o contrato vulnerável, o caminho de execução do código 'entra novamente' nele.
Considere o exemplo abaixo, é um cofre Ethereum que permite aos depositantes sacar apenas 1 éter por semana.
contrato EtherStore {
uint256 publicdrawLimit = 1 éter;
mapeamento (endereço => uint256) public lastWithdrawTime;
mapeamento (endereço => uint256) balanços públicos;
function depositFunds () external payable {
saldos [msg.sender] + = msg.value;
}
functiondrawFunds (uint256 _weiToWithdraw) public {
requer (saldos [msg.sender]> = _weiToWithdraw);
// limitar a retirada
require (_weiToWithdraw <=drawLimit);
// limitar o tempo permitido para a retirada
requer (agora> = lastWithdrawTime [msg.sender] + 1 semana);
require (msg.sender.call.value (_weiToWithdraw) ());
saldos [msg.sender] - = _weiToWithdraw;
lastWithdrawTime [msg.sender] = agora;
}
}
No contrato acima, temos duas funções públicas, [depositFunds] e [saqueFunds]. O [depositFunds] é usado para aumentar o saldo do remetente, enquanto [drawFunds] especifica a quantia a ser sacada. Nesse caso, será um sucesso se o valor a ser sacado for inferior a 1 éter.
A armadilha aqui está na linha [17], onde ocorre a transferência de éter. O invasor pode criar um contrato malicioso com o endereço do contrato do [EtherStores] como o único parâmetro do construtor. Isso tornaria [etherStore] uma variável pública, portanto, mais sujeita a ataques.
Abordagem de QuilllAudit
Seguimos várias técnicas para evitar potenciais vulnerabilidades de reentrada em contratos inteligentes. A primeira e melhor maneira possível é o uso da função embutida [transferência] ao transferir o éter para qualquer contrato externo.
Em segundo lugar, é importante garantir que todas as mudanças lógicas nas variáveis de estado sejam feitas antes de enviar o ether para fora do contrato. No exemplo [EtherStore], as linhas [18] e [19] devem ser colocadas antes da linha [17].
Uma terceira técnica também pode ser usada para evitar chamadas reentrantes; através da introdução de um mutex. É uma adição de uma variável de estado que bloqueará o contrato durante a execução do código.
3. Visibilidades padrão
Existem especificadores de visibilidade para as funções que usamos no Solidity e eles prescrevem a forma como podem ser chamadas. É a visibilidade que determina a chamada das funções; externamente por usuários, por outros contratos derivados, apenas internamente ou apenas externamente. Vejamos como o uso errôneo de especificadores de visibilidade pode causar grande vulnerabilidade em contratos inteligentes.
A vulnerabilidade
Por padrão, a visibilidade da função é [pública], portanto, os usuários externos podem chamar as funções sem visibilidade específica. O bug surge quando os desenvolvedores esquecem de especificar a visibilidade das funções que deveriam ser privadas (ou podem ser chamadas dentro do próprio contrato). Por exemplo;
contrato HashForEther {
functiondrawWinnings () {
// Vencedor se os últimos 8 caracteres hexadecimais do endereço forem 0
requer (uint32 (msg.sender) == 0);
_sendWinnings ();
}
function _sendWinnings () {
msg.sender.transfer (this.balance);
}
}
O contrato acima é um jogo de recompensa simples para adivinhar endereços. Nisto, podemos ver que a visibilidade das funções não é especificada, particularmente a função [_sendWinnings] é [public] (por padrão), portanto, pode ser chamada por meio de qualquer endereço para roubar a recompensa.
Abordagem do QuillAudit
Nossa equipe interna é formada por desenvolvedores experientes que sempre seguem as melhores práticas de auditoria, aqui a visibilidade das funções deve ser especificada explicitamente, mesmo que sejam mantidas públicas, isso deve ser mencionado.
4. Salvaguarda do uso de construtores
Geralmente, os Construtores são chamados de funções especiais que são usadas para realizar tarefas críticas e privilegiadas ao inicializar os contratos. Antes do Solidity [v0.4.22], os construtores tinham o mesmo nome usado pelo contrato que os continha. Agora, considere um caso em que o nome do contrato é alterado durante a fase de desenvolvimento, mas o nome do construtor permanece o mesmo. Essa lacuna também pode fornecer aos invasores uma entrada fácil em seu contrato inteligente.
A vulnerabilidade
Isso pode levar a consequências graves se o nome do contrato for modificado, mas o nome do construtor não for alterado. Por exemplo:
contrato OwnerWallet {
endereço público proprietário;
// construtor
function ownerWallet (address _owner) public {
proprietário = _owner;
}
// Cair pra trás. Colete éter.
função () a pagar {}
função retirar () public {
requer (msg.sender == proprietário);
msg.sender.transfer (this.balance);
}
}
No contrato acima, podemos ver que apenas o proprietário pode retirar o éter chamando a função [retirar]. Aqui, a vulnerabilidade ocorre quando o construtor é nomeado diferente do contrato (a primeira letra é diferente!). Assim, o explorador pode chamar a função [ownerWallet] e autorizar-se como proprietário, e então retirar todo o éter do contrato chamando [retirar].
Abordagem do QuillAudit
Cumprimos a versão [0.4.22] do compilador Solidity. Esta versão introduziu uma palavra-chave; [construtor] que requer que o nome da função corresponda ao nome do contrato.
5. Autenticação Tx.Origin
Aqui, [Tx.Origin] é a variável global do Solidity, ele contém o endereço da conta que originalmente executou a chamada ou transação. Essa variável não pode ser usada para autenticação, pois isso torna o contrato vulnerável a ataques de phishing.
A vulnerabilidade
Os contratos que autorizam usuários por meio da variável [tx.origin] são expostos a ataques externos, levando os usuários a executar ações autenticadas no contrato incorreto. Considere o exemplo abaixo:
contrato Phishable {
endereço público proprietário;
construtor (endereço _owner) {
proprietário = _owner;
}
function () external payable {} // coletar éter
functiondrawAll (address _recipient) public {
requer (tx.origin == proprietário);
_recipient.transfer (this.balance);
}
}
Aqui na linha [11], o contrato autoriza a função [retirarAll] com a ajuda de [tx.origin].
Abordagem do QuillAudit
Geralmente evitamos usar [tx.origin] para autorização em contratos inteligentes. Embora o uso de [tx.origin] não seja estritamente proibido, há alguns casos de uso específicos. Podemos usar [tx.origin] para negar que contratos externos chamem o presente contrato, ele pode ser executado com [require] da forma [require (tx.origin == msg.sender)]. É feito para evitar a convocação de contratos intermediários para convocar o contrato atual, o que limita o contrato a endereços regulares sem código.
Conclusão final
Cobrimos de forma abrangente as cinco armadilhas comuns na linguagem Solidity. Ao desenvolver contratos inteligentes, não devemos esquecer que eles são imutáveis por design, o que significa que, uma vez que os criamos, não há como corrigir o código-fonte.
Isso representa um grande desafio para os desenvolvedores aproveitarem as vantagens dos testes de segurança disponíveis e das ferramentas de auditoria antes da implantação.
A descoberta de potenciais ameaças maliciosas aos contratos inteligentes e os riscos, alguns dos quais mencionamos acima, são realizados de uma forma única e robusta por nossa equipe interna de especialistas em auditoria. Nós da QuillAudits colocamos nossos melhores esforços na pesquisa de segurança para manter seu contrato atualizado com todas as práticas de segurança de software para manter seu contrato seguro e protegido.
Entre em contato com o QuillHash
Com uma presença no setor há anos, QuillHash forneceu soluções corporativas em todo o mundo. A QuillHash com uma equipe de especialistas é uma empresa líder de desenvolvimento de blockchain que fornece várias soluções do setor, incluindo DeFi enterprise. Se você precisar de ajuda na auditoria de contratos inteligentes, sinta-se à vontade para entrar em contato com nossos especialistas aqui!
Siga QuillHash para mais atualizações
Fonte: https://blog.quillhash.com/2021/06/04/top-5-common-errors-in-solidity-programming-language/
- 11
- Conta
- Vantagem
- Todos os Produtos
- auditor
- Autenticação
- autorização
- MELHOR
- blockchain
- Bug
- erros
- chamada
- casos
- Causar
- desafiar
- código
- comum
- Empresa
- contract
- contratos
- Atual
- DAO
- DeFi
- Design
- Developer
- desenvolvedores
- Desenvolvimento
- Empreendimento
- Ether
- ethereum
- Evento
- especialistas
- financeiro
- Primeiro nome
- seguir
- formulário
- Gratuito
- função
- futuro
- jogo
- GAS
- Global
- ótimo
- cortar
- SUA PARTICIPAÇÃO FAZ A DIFERENÇA
- Como funciona o dobrador de carta de canal
- HTTPS
- enorme
- Incluindo
- indústria
- IT
- língua
- conduzir
- principal
- Line
- Lista
- Match
- Outros
- proprietário
- Remendo
- padrão
- Phishing
- ataques de phishing
- prescrever
- presente
- privado
- Programação
- público
- puxando
- pesquisa
- resposta
- DESCANSO
- seguro
- segurança
- Serviços
- conjunto
- simples
- pequeno
- smart
- smart contract
- Smart Contracts
- So
- Software
- solidez
- Soluções
- Estado
- sucesso
- ensaio
- A fonte
- ameaças
- tempo
- topo
- 5 topo
- transação
- Transações
- us
- usuários
- valor
- Cofre
- visibilidade
- vulnerabilidades
- vulnerabilidade
- Vulnerável
- semana
- QUEM
- dentro
- anos