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)