Guia para a auditoria automática de contratos inteligentes. Parte 2: Slither

Analisador: Slither
Descrição: estrutura de análise estática de código aberto para Solidity
githib: https://github.com/trailofbits/slither


Este é um analisador de código estático escrito em python. Ele sabe como monitorar variáveis, chamadas e detecta essa lista de vulnerabilidades . Cada vulnerabilidade possui um link com uma descrição e, se você é novo no Solidity, faz sentido conhecer todos.


Slither pode funcionar como um módulo python e fornecer ao programador uma interface para auditoria de acordo com seu próprio plano. Um exemplo simples e ilustrativo do que o slither pode fazer pode ser visto aqui .


Voltaremos aos cenários de análise no final do artigo, mas, por enquanto, execute o Slither:


git clone https://github.com/trailofbits/slither.git cd slither docker build -t slither . 

e tente analisar nosso contrato.


Entramos no diretório com o construtor-eth-booking e tentamos executar slither a partir da janela de encaixe:


 $ docker run -v $(pwd)/contracts:/slither/contracts slither contracts/flattened.sol 

obtemos o erro "O arquivo de origem requer uma versão diferente do compilador" e agora precisamos colocar a versão do solc=0.4.20 na janela de encaixe slither. Para fazer isso, corrigimos o próprio Dockerfile of Slither, conforme indicado na Seção 2 na introdução , ou seja, em algum lugar no final do Dockerfile, adicionamos a linha:


 COPY --from=ethereum/solc:0.4.20 /usr/bin/solc /usr/bin 

, reconstrua a imagem, execute, aplaude, tudo compila.


Vemos a saída, alertando sobre diferentes “pragmas” e sobre nomes de variáveis ​​incorretos, como o Parameter '_price' of Booking.Booking (flattened.sol#73) is not in mixedCase . Os analisadores dão muitos avisos, mas estamos procurando por bugs reais e não prestaremos atenção ao pouco. Filtramos todas as mensagens sobre o mixedCase, agora não está de acordo com o estilo:


 $ docker run -v $(pwd)/contracts:/slither/contracts slither contracts/flattened.sol 2>&1 | fgrep -v 'mixedCase' 

Os verdadeiros programadores perdem tudo que está verde, parecem tudo que estão vermelhos, e aqui está o que Slither encontrou no contrato, além de falsos positivos:


 Booking.refundWithoutCancellationFee (flattened.sol#243-250) sends eth to arbirary user Dangerous calls: - client.transfer(address(this).balance) (flattened.sol#249) Reference: https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#functions-that-send-ether-to-arbitrary-destinations INFO:Detectors: Booking.refundWithCancellationFee (flattened.sol#252-259) sends eth to arbirary user Dangerous calls: - owner.transfer(m_cancellationFee) (flattened.sol#257) - client.transfer(address(this).balance) (flattened.sol#258) Reference: https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#functions-that-send-ether-to-arbitrary-destinations 

Agora veja o que há de errado com esta função no contrato:


  /************************** PRIVATE **********************/ function refundWithoutCancellationFee() private { address client = m_client; m_client = address(0); changeState(State.OFFER); client.transfer(address(this).balance); } function refundWithCancellationFee() private { address client = m_client; m_client = address(0); changeState(State.CANCELED); owner.transfer(m_cancellationFee); client.transfer(address(this).balance); } 

nesse caso, por exemplo, a função refundWithoutCancellationFee() é chamada assim:


  function rejectPayment() external onlyOwner onlyState(State.PAID) { refundWithoutCancellationFee(); } function refund() external onlyClient onlyState(State.PAID) { refundWithoutCancellationFee(); } 

Hmm, formalmente não há erros: as chamadas são protegidas por todos os tipos de onlyOwner , mas Slither jura que, dizem eles, a transmissão é enviada sem nenhuma verificação dentro de refundWithoutCancellationFee (). E ele está certo, a função em si realmente não tem quase nenhuma restrição. Seja privado, e é chamado pelos invólucros "rejectPayment ()" "refund ()" com as restrições necessárias, mas, neste formulário, se você modificar o contrato, há um grande risco de esquecer as restrições e aderir à chamada refundWithoutCancellationFee() para outro local, disponível para o atacante. Portanto, mesmo que formalmente não exista vulnerabilidade, as informações acabaram sendo úteis - isso é pelo menos um nível de "aviso", se a atribuição planejar desenvolver mais o código do contrato. Nesse caso, duas funções de participantes diferentes usam o mesmo código, e essa decisão foi tomada para economizar gás - o contrato é único e o custo de seu cálculo é um fator importante.


Verifiquei novamente que Slither está xingando qualquer transmissão e transferi o corpo da função diretamente para os itens "rejectPayment ()" e "refund ()" acima, o aviso desapareceu, ou seja, Slither percebeu que agora a transmissão não é enviada sem verificação de endereço. Ótimo começo!


Agora vamos verificar como o Slither monitora a inicialização das variáveis, para isso comentamos duas inicializações:


 - m_fileHash = _fileHash; + // m_fileHash = _fileHash; - m_price = _price; + // m_price = _price; 

o primeiro não é muito importante, em termos de falhas, exceto pelo desperdício de recursos, porque o m_fileHash não é usado em nenhum lugar, é simplesmente armazenado no blockchain ao criar um contrato. Mas m_price é usado, e Slither jura corretamente que m_price não foi inicializado em nenhum lugar, embora seja usado:


 Booking.m_price (flattened.sol#128) is never initialized. It is used in: - fallback (flattened.sol#144-156) 

Bem, este é um truque simples, como esperado, tudo funcionou bem.


Agora, adicionaremos ao contrato a reentrada tão amada por todos: mudaremos o estado do contrato após uma chamada externa. Fazemos as seguintes alterações:


  function refundWithoutCancellationFee() private { address client = m_client; - m_client = address(0); - changeState(State.OFFER); - client.transfer(address(this).balance); + client.call.value(address(this).balance)(); + m_client = address(0); + changeState(State.OFFER); } 

Eu tive que substituir transferência por chamada, porque a variante de transferência não jura porque a transferência envia uma chamada com um mínimo de gás e um retorno de chamada é impossível (embora ao mudar para a bifurcação de Constantinopla no Ethereum, o preço da gasolina tenha sido alterado e isso reative o ataque de reentrada usando a transferência .


Resultado da pesquisa de reentrada:


 Reentrancy in Booking.refundWithoutCancellationFee (flattened.sol#243-253): External calls: - client.call.value(address(this).balance)() (flattened.sol#245) State variables written after the call(s): - m_client (flattened.sol#246) 

Tudo bem, pelo menos não fornecerá variáveis ​​de estado após chamadas externas, e isso é muito bom.


Se você percorrer a lista, as vulnerabilidades restantes na lista simplesmente pesquisam métodos específicos no código ou padrões conhecidos que, é claro, se o acesso à marcação por python estiver disponível, funcionarão com bastante confiabilidade. I.e. padrões bem conhecidos Slither não perderá.


Agora, farei alterações que mostrem perfeitamente as especificidades do trabalho dos analisadores estáticos:


 - client.transfer(address(this).balance + for (uint i=0; i < 1; i++) { + client.transfer(address(this).balance - 999999999999999999); + } 

e o resultado:


 Booking.refundWithoutCancellationFee has external calls inside a loop: - client.transfer(address(this).balance - 999999999999999999) (flattened.sol#252) Reference: https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description/_edit#calls-inside-a-loop 

O ciclo é executado uma vez e é degenerado - portanto, o aviso emitido é falso positivo e a ausência de um aviso sobre aritmética perigosa é falso negativo. Análise de tipos, resultados de operações, contagem de chamadas - tarefas não são para analisadores estáticos. Portanto, entenda claramente quais erros o Slither encontrará e quais devem ser procurados usando outras ferramentas.


Prometemos mencionar a possibilidade de escrever nossos próprios scripts para teste e a saída de qualquer informação interessante sobre o contrato usando a tecla --print . Desse ponto de vista, o Slither é uma ótima ferramenta para o IC. Os desenvolvedores de um grande sistema de contratos conhecem os nomes de variáveis ​​críticas de segurança: saldos, tamanhos de comissão, sinalizadores e podem escrever um script de teste que irá bloquear quaisquer alterações no código que, por exemplo, substituam uma variável importante ou alterem variáveis ​​de estado após uma chamada externa, e sua análise altamente previsível é uma ótima ferramenta para uso em ganchos.
A tarefa do Slither é salvá-lo de bichos estúpidos, encontrar padrões perigosos bem conhecidos e avisar o desenvolvedor. Nesta versão, é uma ferramenta para um desenvolvedor iniciante no Solidity, solicitando imediatamente como escrever o código corretamente no Solidity.


Sumário


Nos meus testes pessoais, eu colocaria o Slither four por versatilidade, simplicidade e facilidade de uso, além de scripts de teste simples e compreensíveis e adaptabilidade ao CI.


Slither encontrou com confiança um AVISO real associado ao uso da função que envia a transmissão, encontrou todos os bugs introduzidos. Ele não podia lidar apenas com a análise dinâmica, que formalmente não deveria ser feita; caso contrário, teria que sacrificar a universalidade, a previsibilidade e a facilidade de uso.


No próximo artigo, trataremos do analisador Mythril, mas aqui está o índice dos artigos prontos ou planejados para a escrita:


Parte 1. Introdução. Compilação, nivelamento, versões do Solidity
Parte 2. Slither (este artigo)
Parte 3. Mythril
Parte 4. Manticore (no processo de escrita)
Parte 5. Equidna (no processo de escrita)

Source: https://habr.com/ru/post/pt438338/


All Articles