Introdução
Este post apresenta os resultados de uma auditoria de segurança de um contrato inteligente realizada pela Blaze Information Security e tornada pública em nome do cliente Array.io (anteriormente conhecido como Annihilat.io). Este post contém as mesmas informações e descobertas presentes no relatório publicado no final de dezembro de 2017.
A auditoria foi realizada por Victor Farias (líder do projeto) e Julio Fort, da Blaze Information Security.
Ficamos satisfeitos em saber que a equipe da Array.io leva a segurança a sério e contratou três empresas diferentes para auditar seus contratos inteligentes.
Isenção de responsabilidade: Este documento apresenta as descobertas de uma revisão de segurança dos contratos inteligentes sob o escopo da auditoria. Como um exercício de esforço máximo e com tempo limitado, não garante que não existam outros problemas de segurança no contrato inteligente. Os resultados desta auditoria não devem ser interpretados como aconselhamento de investimento.
Relatório
Este documento apresenta os resultados de uma Revisão de Segurança de Contrato Inteligente para a Array.io. Este trabalho teve como objetivo verificar se o contrato inteligente faz apenas o que se destina a fazer e descobrir vulnerabilidades de segurança que poderiam afetar negativamente o token ANNI da Array.io antes que o contrato seja implantado na rede blockchain.
A Array.io é uma ideia de financiamento baseada no conceito de Oferta Inicial de Desenvolvimento (IDO). Essencialmente, funciona como um token de salário para colaboradores de um projeto que captou capital em uma IDO. A Array.io utiliza um token baseado no padrão ERC20 da Ethereum e os contratos foram escritos em Solidity. Detalhes sobre a Array.io e a IDO podem ser encontrados no whitepaper.
A análise concentrou-se em vulnerabilidades relacionadas à implementação e em problemas causados por erros de arquitetura e design, bem como em inconsistências entre a documentação e o código.
Para cada padrão de código não compatível com o padrão de token da Ethereum ou com a especificação do contrato, desvio de boas práticas e vulnerabilidade descoberta durante a avaliação, a Blaze Information Security atribuiu uma classificação de gravidade de risco e, sempre que possível, validou a existência da vulnerabilidade com um código de exploração funcional.
Os principais objetivos da avaliação foram os seguintes:
- Identificar os principais problemas relacionados à segurança presentes no contrato inteligente
- Avaliar o nível de práticas de codificação segura presentes no projeto
- Obter evidências para cada vulnerabilidade e, se possível, desenvolver um exploit funcional
- Descreva, de forma clara e fácil de reproduzir, todos os procedimentos utilizados para replicar o problema
- Recomende fatores de mitigação e correções para cada defeito identificado na análise
- Forneça contexto com um cenário de risco real baseado em um modelo de ameaça realista
Resumo executivo
O trabalho foi realizado em um período de cinco dias úteis, incluindo a redação do relatório. A revisão de segurança do contrato inteligente começou em 12 de dezembro de 2017 e terminou em 18 de dezembro de 2017, finalizando com a versão preliminar deste relatório.
Em 21 de dezembro de 2017, todas as descobertas relatadas pela Blaze Information Security foram corrigidas pela Array.io. Os problemas não estão mais presentes no código dos contratos e foram corrigidos nos commits b287f07393f8b5b67cbde5c1d70dfc31b9cd5aa1 e afdf264d030e8313fd65e1c8c236d2a958eff7c7.
A auditoria foi realizada com o auxílio de ferramentas automatizadas e também submetida a uma revisão manual. O código EVM gerado não foi inspecionado nesta avaliação.
Foram descobertos três problemas nos contratos auditados neste trabalho. No geral, os contratos sob escopo continham vulnerabilidades significativas que poderiam levar à perda de tokens e causar um impacto considerável nas operações da Array.io. Eles também careciam de padrões de codificação de segurança defensiva e apresentavam outras práticas de programação Solidity não recomendadas.
É importante notar que essas vulnerabilidades não estão mais presentes, pois foram corrigidas pela Array.io e as correções foram revisadas pelos auditores.
Escopo
O escopo desta revisão de segurança é composto por dois contratos inteligentes escritos em Solidity.
- Nome do projeto: annihilatio
- Commit:
8ad0a1bdc7dff7e40ad5cf61aea89deaa982eab5 - Token_flat.sol (345 linhas)
- Multisig_flat.sol (498 linhas)
O código auditado é de código aberto e pode ser encontrado em https://github.com/annihilatio/ido/tree/8ad0a1bdc7dff7e40ad5cf61aea89deaa982eab5/smart-contracts.
Revisão de segurança de smart contracts
Nossa revisão de smart contracts com foco em segurança segue uma metodologia organizada, com o objetivo de identificar o maior número possível de vulnerabilidades nos contratos sob análise, sob a perspectiva de um adversário motivado, tecnicamente capaz e persistente.
Damos atenção especial a áreas críticas do smart contract, como a queima de tokens e o funcionamento da multi-assinatura. Nosso processo também analisa outros problemas comuns de implementação que levam a falhas como reentrância, estouros e subfluxos matemáticos, negação de serviço relacionada a gas, entre outros.
A metodologia de revisão de smart contracts da Blaze envolve técnicas de auditoria automatizadas e manuais. As aplicações são submetidas a uma rodada de análise dinâmica utilizando ferramentas como linters, profilers de programas e scanners de segurança de código-fonte.
O código-fonte dos contratos é inspecionado manualmente em busca de falhas de segurança. Esse tipo de análise tem a capacidade de detectar problemas que passam despercebidos por scanners automatizados e analisadores estáticos, pois consegue descobrir casos extremos e problemas relacionados à lógica de negócio.
Resumo técnico
Descrição dos smart contracts
- Carteira Multisig: Para armazenar fundos com segurança e equilibrar o consenso entre os proprietários, a Array.io utiliza uma carteira multi-assinatura para controlar o token. Este contrato pode ser usado para alterar a configuração do token, por exemplo, definir novos valores para a participação destinada a investidores, fundadores e ao projeto, iniciar o TGE, entre outros.
- Token: Este contrato é o token ANNI propriamente dito. Ele contém todas as funções relacionadas à criação (minting), transferência de tokens entre carteiras, verificação de saldo, queima de tokens e outras funcionalidades descritas na especificação do token.
Observações sobre Multisig_flat.sol
Houve inúmeros erros ao tentar executar o Multisig_flat.sol em ferramentas automatizadas. A Blaze Information Security sugere que os desenvolvedores revisem o código do contrato para entender por que a maioria das ferramentas de segurança para Solidity não conseguiu analisá-lo.
Vulnerabilidades
1. A impossibilidade de trocar tokens ANNI de volta por ETH reterá os fundos dos investidores
Corrigido no commit afdf264d030e8313fd65e1c8c236d2a958eff7c7
Severidade: Crítica
O contrato possui uma função para que os detentores de tokens convertam seus tokens ANNI em Ether (ETH). Esta função, conhecida como burn(), espera-se que funcione transferindo primeiro a quantidade solicitada em tokens para um "endereço de queima" e, posteriormente, transferindo ETH para quem chamou a função.
No entanto, durante a revisão do código, a Blaze Information Security notou que a transfer() função não permite uma transferência para um endereço zero, portanto o saldo do remetente nunca é atualizado e ocorrerá uma exceção devido à falta de uma condição para satisfazer um require(). Assim, quem chama a função pode invocá-la para queimar tokens várias vezes, mas nenhuma ação será realizada.
De Token_flat.sol, linha 135:
address constant public burnAddress = 0x0;
Linha 217:
function burn(uint _amount) public isNotTgeLive
noAnyReentrancy returns(bool _success) {
require(balances[msg.sender] >= _amount);
transfer(burnAddress, amount); // aqui deveria ocorrer uma transferência para o endereço de queima
msg.sender.transfer(amount);
Burn(msg.sender, _amount);
return true;
}
Linha 59:
function transfer(address _to, uint value) isNotFrozenOnly
onlyPayloadSize(2 * 32) returns (bool success) {
require(_to != address(0)); // o endereço de queima é 0x0, o require() não será satisfeito e causará um erro
require(value <= balances[msg.sender]); // esta linha nunca será executada
Dadas as construções de código acima, quando uma burn() função é chamada, ela tentará executar uma transferência como transfer(0x0, _amount) e a instrução require(_to != address(0)); não será concluída conforme o esperado. O impacto deste problema é grave, pois os investidores que possuem tokens ANNI nunca conseguirão converter seus tokens de volta para ETH.
Solução: A Blaze recomenda não chamar a função transfer em burn() mas, em vez disso, verificar o valor e deduzi-lo da conta:
require(value <= balances[msg.sender]);
balances[msg.sender] = balances[msg.sender].sub(value);
2. Desvio das especificações técnicas do contrato e do código para liquidação de tokens
Corrigido no commit b287f07393f8b5b67cbde5c1d70dfc31b9cd5aa1
Gravidade: Média
De acordo com o especificações do contrato inteligente:
Os tokens podem ser liquidados a qualquer momento pelo detentor, momento em que são queimados e o contrato envia a mesma quantidade de ETH para o detentor.
Obviamente, o detentor não pode queimar mais tokens do que possui. Além disso, à medida que os tokens são queimados, a oferta total é reduzida pelo mesmo número de tokens.
De Token_flat.sol:
/// @dev Queima tokens para o burnAddress a partir da carteira do msg.sender
/// @param _amount Quantidade de tokens
function burn(uint _amount)
public
isNotTgeLive
noAnyReentrancy
returns(bool _success)
{
require(balances[msg.sender] >= _amount);
transfer(burnAddress, amount);
msg.sender.transfer(amount);
Burn(msg.sender, _amount);
return true;
}
De acordo com o código acima, a liquidação de tokens (evento de queima) não pode ser chamada a qualquer momento, ao contrário do que diz a documentação, mas apenas quando o TGE (Evento de Geração de Tokens) não estiver ativo.
A equipe de auditoria entende que este problema não traz nenhum impacto negativo de segurança ao contrato em si, mas é certamente um desvio da funcionalidade pretendida descrita nas especificações técnicas do contrato inteligente.
Solução: Considere remover o modificador isNotTgeLive da função burn(). Se o código realmente reflete a lógica de negócio, altere a documentação para refletir isso com precisão.
3. Ausência de visibilidade explícita em algumas declarações de função
Corrigido no commit b287f07393f8b5b67cbde5c1d70dfc31b9cd5aa1
Gravidade: Baixa
A auditoria revelou que, com exceção de duas funções, _finishTge() e _mint(uint,uint,uint), ambas marcadas como internal, todas as outras funções dos contratos são públicas, já que algumas delas não foram rotuladas explicitamente. Muitas dessas funções alteram o estado.
Por padrão, o Solidity marca como públicas todas as funções não rotuladas, tornando-as chamáveis por agentes externos na rede. Para restringir esse comportamento, um desenvolvedor deve usar os rótulos internal ou private para impedir que sejam chamadas de fora.
Embora a Blaze Information Security tenha notado que existem verificações diferentes nas funções para evitar abusos por parte de terceiros, não rotular as funções explicitamente é considerado uma má prática de programação e deve ser evitado. Espera-se que esta recomendação também ofereça à equipe de desenvolvimento a oportunidade de revisar a visibilidade das funções e reconsiderar seus rótulos atuais.
Referência: https://consensys.github.io/smart-contract-best-practices/recommendations/
Solução: Adicione visibilidade explícita a todas as funções e variáveis de estado.
Observações adicionais
- No MultiSigWallet, o
notNull()modificador poderia ser movido deaddTransaction(address destination, uint value, bytes data)para as funçõessubmitTransaction(),setLiveTx(), esetFinishedTx()para verificá-lo em um estágio anterior. - No MultiSigWallet, a função
isConfirmed()não retorna explicitamente falso. - No MultiSigWallet, no loop dentro de
getTransactionIds(), a variávelipoderia ser inicializada com o valor da variávelfromem vez de 0 para economizar gas. - Na MultiSigWallet, a variável
IToken tokenpoderia ser alterada através da funçãosetTokenpor qualquer proprietário sem uma eleição? Isso parece anular o propósito da multi-assinatura e a ideia de chegar a um consenso para realizar uma ação na carteira. - Ambos os contratos começam com
pragma solidity ^0.4.15;. De acordo com as melhores práticas, isso deveria ser fixado em uma versão específica:pragma solidity 0.4.15;— veja https://consensys.github.io/smart-contract-best-practices/recommendations/#lock-pragmas-to-specific-compiler-version.
Conclusão
O objetivo final de uma avaliação de segurança é proporcionar a oportunidade de ilustrar melhor o risco de uma organização e ajudá-la a compreender e validar sua postura de segurança contra ameaças potenciais ao seu negócio.
Com isso em mente, a Blaze Information Security apresenta as seguintes recomendações que acreditamos deveriam ser adotadas como próximos passos para aprimorar ainda mais a postura de segurança dos contratos inteligentes:
- Corrija todos os problemas apresentados no relatório e considere as observações feitas na seção de comentários
- Realize uma nova rodada de auditoria para verificar as correções
- Considere estabelecer um programa de bug bounty, uma prática cada vez mais comum entre empresas do setor de contratos inteligentes e blockchain
A Blaze Information Security agradece à equipe da Annihilat.io pelo apoio e assistência durante todo o projeto. Esperamos sinceramente trabalhar com a Array.io (Annihilat.io) novamente em um futuro próximo.




