WEB3DEV

Cover image for Introdução às auditorias de segurança de Contratos Inteligentes | Acesso a dados privados
Fatima Lima
Fatima Lima

Posted on

Introdução às auditorias de segurança de Contratos Inteligentes | Acesso a dados privados

Image description

Da última vez, aprendemos sobre a função self-destruction (autodestruição) no Solidity (espero que todos tenham sido capazes de entender). Agora, aprenderemos sobre como acessar dados privados.

Panorama geral

Vamos ver os três métodos de armazenamento de dados em Solidity:

1. Armazenamento

  • Os dados em armazenamento são armazenados permanentemente. São armazenados no slot como um par chave-valor.
  • Os dados em armazenamento são escritos na blockchain (de forma que eles mudam de estado) e é por isso que o uso do armazenamento é muito caro.
  • O custo do gas para ocupar um slot de 256 bits é de 20.000.
  • A taxa de gas para modificação do valor do armazenamento custará 5,000.
  • Uma certa quantidade de gas é reembolsada quando um slot de armazenamento é limpo (ou seja, quando bytes não nulos são definidos como zero).
  • O armazenamento tem um total de 2²⁵⁶ slots, 32 bytes de dados em cada slot são armazenados sequencialmente na ordem de declaração. Os dados serão armazenados a partir do lado direito de cada slot. Se as variáveis adjacentes couberem em um único slot de 32 bytes, então elas serão armazenadas em pacotes no mesmo slot. Caso contrário, um novo slot será habilitado para armazenamento.

Image description

  • Os métodos para armazenar arrays no armazenamento são exclusivos. Arrays em solidity são divididas em dois tipos.

a. Array de comprimento fixo (Fixed-length)

Cada elemento em um array de comprimento fixo terá um slot separado para armazenamento. Tomando como exemplo um array de comprimento fixo com três elementos uint64, na figura a seguir, você pode ver claramente seu método de armazenamento:

Image description

b. Array de comprimento variável (Variable-length - O comprimento varia de acordo com o número de elementos)

O método de armazenamento de arrays de comprimento variável é muito estranho. Ao encontrar arrays de comprimento variável, um novo slot, slotA, será habilitado para armazenar o comprimento do array, e seus dados serão armazenados em outro slotV numerado. O slotA representa a posição declarada do array de comprimento variável; length representa o comprimento do array de comprimento variável, o slotV representa o local de armazenamento dos dados do array de comprimento variável, value representa o valor de um determinado dado no array de comprimento variável, e index representa o índice subscrito correspondente ao valor. Contudo, length = sload(slotA), slotV = keccak256(slotA) + index e value = sload(slotV).

Os arrays de comprimento variável não podem saber o comprimento da matriz durante a compilação e não há como reservar espaço de armazenamento com antecedência, então o Solidity usa o slotA para armazenar o comprimento do array de comprimento variável.

Vamos escrever um exemplo para verificar como o array de comprimento variável descrito acima é armazenado:

Image description

Após a implantação do contrato, chame a função addUser e passe o parâmetro a = 998. Uma vez feita a depuração, você pode ver como o array de comprimento variável é armazenado.

Image description

O primeiro slot aqui é onde o comprimento do array de comprimento variável é armazenado:

0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563

O valor é igual a:

sha3(“0x000000000000000000000000000000000000000000000000000000000000000”)

key = 0 é o número do slot atual.

value = 1, isto significa que há apenas uma parte dos dados no array de comprimento variável user[], o que significa que o comprimento do array é 1.

O segundo slot aqui é onde os dados do array de comprimento variável são armazenados:

0x510e4e770828ddbf7f7b00ab00a9f6adaf81c0dc9cc85f1f8249c256942d61d9

Esse valor é igual a:

sha3(“0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563”)

Os números dos slots são:

key=0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563

Esse valor é igual a:

sha3(“0x000000000000000000000000000000000000000000000000000000000000000”)+0

Os dados armazenados no slot são:

value=0x0000000000000000000000000000000000000000000000000000000000003e6

Esse é 998 em hexadecimal, que foi o valor que passamos.

Para uma verificação mais precisa, chamamos novamente a função addUser e passamos a=999 para obter o seguinte resultado.

Image description

Aqui podemos ver que o novo slot é:

0x6c13d8c1c5df666ea9ca2a428504a3776c8ca01021c3a1524ca7d765f600979a.

Esse valor é igual a:

sha3(“0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564”)

O número do slot é: key=0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564

Esse valor é igual a:

sha3(“0x000000000000000000000000000000000000000000000000000000000000000”)+1

Os dados armazenados no slot são:

value=0x0000000000000000000000000000000000000000000000000000000000003e7

Este valor é 999 em hexadecimal, que é o valor passado pela função addUser que acabamos de chamar.

Os exemplos acima devem lhe dar uma compreensão geral de como os arrays de comprimento variável são armazenados.

2. Memory (Memória)

  • Memory é um array de bytes com um tamanho de slot de 256 bits (32 bytes). Os dados só são armazenados durante a execução da função e são apagados após a execução. Eles não são salvos na blockchain.
  • Ler ou escrever um byte (256 bits) requer 3 de gas.
  • A fim de evitar muito trabalho para os mineradores, o custo começará a subir após 22 operações de leitura e escrita.

3. Calldata (chamar dados)

  • calldata é uma área não-persistente e inalterável utilizada para armazenar parâmetros de funções e se comporta basicamente como a memória.
  • Calldata é necessária para argumentos de chamadas a funções externas e também pode ser usada para outras variáveis.
  • Ela evita a duplicação e assegura que os dados não possam ser modificados.
  • Arrays e structs com localização de dados calldata também podem ser retornados de funções, mas nenhuma alocação a este tipo é possível.

Agora que entendemos os três métodos de armazenamento em solidity, vejamos as quatro palavras-chave de visibilidade no contrato: Em solidity, há quatro palavras-chave de visibilidade: external (externa), public (pública), internal (interna) e private (privada). A visibilidade da função é pública por padrão. Para variáveis de estado, exceto a external, que não pode ser usada para definir variáveis, as outras três podem ser usadas para definir variáveis. A visibilidade padrão das variáveis de estado é interna.

1. Palavra-chave External

As funções externas, definidas como "external", podem ser chamadas por outros contratos. A função() externa marcada como “external” não pode ser chamada diretamente como uma função interna, ou seja, o método de chamada da função() deve usar this.function().

2. Palavra-chave Public

Funções definidas como públicas podem ser chamadas usando funções internas ou mensagens externas. Para variáveis de estado definidas como públicas, o sistema irá gerar automaticamente uma função getter.

3. Palavra-chave Internal

As funções e variáveis de estado definidas internamente só podem ser acessadas dentro do contrato atual ou contratos derivados do contrato atual.

4. Palavra-chave Private

As funções e variáveis de estado definidas como privadas são visíveis apenas para o contrato que as define. Nenhum dos contratos derivados do contrato pode chamar, acessar funções e variáveis de estado.

Em resumo, as palavras-chave que modificam o armazenamento da variável no contrato limitam apenas o escopo de sua chamada, mas não estabelecem se ela é legível ou não. Portanto, hoje mostraremos a você como ler todos os dados do contrato.

Exemplo de vulnerabilidade

Desta vez nosso contrato alvo é um contrato implantado na testnet Ropsten.

Endereço do contrato:

0x3505a02BCDFbb225988161a95528bfDb279faD6b

Link:

https://ropsten.etherscan.io/address/0x3505a02BCDFbb225988161a95528bfDb279faD6b#code

Código fonte do contrato

Image description

Análise de Vulnerabilidade

Pelo código do contrato acima, podemos ver que o contrato Vault registra dados confidenciais tais como o nome de usuário e a senha no contrato. Pelo conhecimento dos pré-requisitos, podemos entender que as palavras-chave que modificam as variáveis no contrato limitam apenas seu escopo de chamada. Isto prova indiretamente que os dados no contrato são públicos e podem ser lidos arbitrariamente, fazendo com que não seja seguro registrar dados confidenciais no contrato.

Dados de Leitura

Em seguida, examinaremos os dados deste contrato. Primeiro, vamos examinar os dados no slot0: pode-se ver pelo contrato que apenas um tipo de dado é armazenado no slot0.

Vou usar o Web3.py para interagir com os dados aqui

Image description

Após a execução

Image description

Vamos convertê-lo usando um conversor de base

Image description

Aqui chegamos com sucesso à contagem de variáveis do tipo uint=123 armazenadas no primeiro slot, o slot0, do contrato.

Vamos continuar:

Três variáveis são armazenadas no slot1: u16, isTrue, owner

Image description

Image description

Da direita para a esquerda, são:

owner = f36467c4e023c355026066b8dc51456e7b791d99

isTrue = 01 = true

u16 = 1f = 31

A senha da variável privada é armazenada no slot2.

Vamos analisar isto:

Image description

Image description

Os slots 3, 4, 5 armazenam três elementos no array de comprimento variável.

Image description

Image description

O slot6 armazena o comprimento do array de comprimento variável.

Image description

Image description

Podemos ver pelo código do contrato que a identificação e a senha do usuário são armazenadas sob a forma de pares chave-valor. Vamos examinar o id e a senha dos dois usuários.

user1 (usuário1)

Image description

Image description

user2 (usuário2)

Image description

Image description

Lemos com sucesso todos os dados do contrato. Provamos que os dados privados no contrato também podem ser lidos.

Técnicas de Prevenção

(1) Como desenvolvedor

Não armazenar nenhum dado confidencial no contrato, pois qualquer dado do contrato pode ser lido.

(2) Como auditor

Durante o processo de auditoria, deve-se prestar atenção se há dados confidenciais no contrato, tais como chaves secretas, senhas etc.

Referências

Favor consultar os seguintes artigos para obter informações adicionais.

Esse artigo foi escrito por SlowMist e traduzido por Fátima Lima. O original pode ser lido aqui.

Latest comments (0)