WEB3DEV

Cover image for Considerações de Segurança para Oráculos Chainlink
Adriano P. Araujo
Adriano P. Araujo

Posted on

Considerações de Segurança para Oráculos Chainlink

A Chainlink permite que desenvolvedores de contratos inteligentes recebam uma ampla variedade de dados fora da cadeia, com os recursos mais comumente usados sendo a obtenção de aleatoriedade fora da cadeia e dados de preços fora da cadeia. Integrar seus contratos inteligentes com a Chainlink apresenta um conjunto único de vulnerabilidades de segurança potenciais que os atacantes podem explorar; aqui estão as vulnerabilidades comuns que os desenvolvedores e auditores de contratos inteligentes precisam estar atentos.

Sumário

  • Não verificar preços desatualizados

  • Não verificar o sequenciador L2 inoperante

  • Mesmo intervalo de verificação usado para várias fontes de preços

  • Atualizações de preços do oráculo não ocorrem com frequência

  • Confirmação da solicitação < Profundidade da Reorganização da Cadeia

  • Supondo Precisão do Preço do Oráculo

  • Endereço da Feed de Preços do Oráculo Incorreto

  • Atualizações de Preços do Oráculo Podem ser Front-Run

  • Negligência Não Tratada de Reversão do Oráculo

  • Desvinculação não gerenciada de ativos conectados

  • Oráculo Retorna Preço Incorreto Durante Flash Crashes

  • Realizar Apostas Após Solicitação de Aleatoriedade

  • Re-solicitar Aleatoriedade

Não verificar preços desatualizados

Muitos contratos inteligentes usam a Chainlink para solicitar dados de preços fora da cadeia, mas um erro comum ocorre quando o contrato inteligente não verifica se esses dados estão desatualizados. Considere esta descoberta de dados de preços desatualizados da auditoria USSD do Sherlock:


// @audit sem verificação de dados de preço desatualizados

(, int256 preço, , , ) = priceFeedDAIETH.latestRoundData();



return

(wethPriceUSD * 1e18) /

((DAIWethPrice + uint256(preço) * 1e10) / 2);

Enter fullscreen mode Exit fullscreen mode

As fontes de dados do oráculo podem retornar dados de preços desatualizados por várias razões. Se os dados de preços retornados estiverem desatualizados, este código será executado com preços que não refletem os preços atuais, resultando em uma possível perda de fundos para o usuário e/ou o protocolo. Os contratos inteligentes devem sempre verificar o parâmetro updatedAt retornado de latestRoundData() e compará-lo com um limite de desatualização:


// @audit corrigido para verificar dados de preço desatualizados

(, int256 preço, , uint256 atualizadoEm, ) = priceFeedDAIETH.latestRoundData();



if (atualizadoEm < block.timestamp - 60 * 60 /* 1 hora */) {

revert("feed de preço desatualizado");

}



return

(wethPriceUSD * 1e18) /

((DAIWethPrice + uint256(preço) * 1e10) / 2);



Enter fullscreen mode Exit fullscreen mode

O limite de desatualização deve corresponder ao intervalo de verificação da fonte de preço do oráculo. Isso pode ser encontrado na lista de fontes de preço da Ethereum na Chainlink, verificando a caixa “Show More Details”, que mostrará a coluna "Heartbeat" para cada fonte. Para redes diferentes da Ethereum, certifique-se de selecionar sua L1/L2 desejada na página antes de ler as colunas de dados.

Mais exemplos:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

Não verificar o sequenciador L2 inoperante

Ao usar a Chainlink com cadeias L2 como Arbitrum, os contratos inteligentes devem verificar se o Sequenciador L2 está inoperante para evitar dados de preços desatualizados que parecem atualizados - a documentação oficial da Chainlink fornece uma implementação de exemplo. Auditores de contratos inteligentes devem estar atentos à ausência de verificações de atividade do Sequenciador L2 quando veem código de preço chamando o latestRoundData() em projetos que serão implantados em L2s.

Mais exemplos:  [1, 2, 3, 4, 5]

Mesmo intervalo de verificação usado para várias fontes de preços

Os contratos inteligentes frequentemente usam várias fontes de preços do oráculo para acompanhar preços de vários ativos. É um erro supor que o mesmo intervalo de tempo de verificação pode ser usado como uma verificação de desatualização para cada fonte, pois diferentes fontes podem ter diferentes intervalos de tempo de verificação. Considere este código da auditoria do Sherlock do JOJO:


function getMarkPrice() external view returns (uint256 price) {

  int256 rawPrice;

  uint256 updatedAt;

  // @audit primeira fonte

  (, rawPrice, , updatedAt, ) = IChainlink(chainlink).latestRoundData();



  // @audit segunda fonte

  (, int256 USDCPrice,, uint256 USDCUpdatedAt,) = IChainlink(USDCSource).latestRoundData();

require( // @audit fonte #1 verificação obsoleta usando o mesmo  heartbeatInterval

block.timestamp - updatedAt <= heartbeatInterval,

  "ORACLE_HEARTBEAT_FAILED"

  );       

  // @audit feed #2 verificação obsoleta usando o mesmo heartbeatInterval



  require(block.timestamp - USDCUpdatedAt <= heartbeatInterval, "USDC_ORACLE_HEARTBEAT_FAILED");

  uint256 tokenPrice = (SafeCast.toUint256(rawPrice) * 1e8) / SafeCast.toUint256(USDCPrice);

  return tokenPrice * 1e18 / decimalsCorrection;

}




Enter fullscreen mode Exit fullscreen mode

Neste exemplo, a primeira fonte de preço tem um intervalo de verificação de 1 hora, enquanto a segunda tem um intervalo de verificação de 24 horas, portanto, elas requerem intervalos de verificação diferentes para serem usados em suas verificações de desatualização. Os intervalos apropriados podem ser encontrados na lista de fontes de preço da Ethereum na Chainlink, verificando a caixa "Show More Details", que mostrará a coluna "Heartbeat" para cada fonte. Para redes diferentes da Ethereum, certifique-se de selecionar sua L1/L2 desejada na página antes de ler as colunas de dados.

Atualizações de preços do oráculo não ocorrem com frequência

Deve-se ter cuidado ao selecionar qual(is) oráculo(s) de preço usar; usar um oráculo de preço que não seja atualizado com frequência resultará em cálculos realizados com preços imprecisos que não refletem o valor real do ativo.

Os Oráculos da Chainlink são atualmente a escolha mais segura, mas mesmo assim, deve-se ter cuidado na escolha da fonte de preço; fontes de preço semelhantes podem ter intervalos de verificação e limites de desvio diferentes; quanto mais longo o intervalo de verificação e maior o limite de desvio, mais o preço do oráculo pode diferir do preço verdadeiro e atual.

Os desenvolvedores de contratos inteligentes devem usar e os auditores devem verificar se estão sendo usadas fontes de preço com os intervalos de verificação e limites de desvio mais baixos para garantir que o preço relatado pelo oráculo esteja o mais próximo possível do preço verdadeiro e atual.

Mais exemplos:  [1, 2, 3, 4]

Confirmação da solicitação < Profundidade da Reorganização da Cadeia

Ao solicitar aleatoriedade, o parâmetro REQUEST_CONFIRMATION deve ser maior do que a profundidade das reorganizações comuns na cadeia de destino(s) na qual o contrato será implantado, pois as reorganizações da cadeia reordenam blocos e transações, o que pode afetar a aleatoriedade retornada.

Isso pode resultar em um vencedor se tornando um perdedor ou vice-versa devido à reorganização da cadeia reordenando a solicitação de aleatoriedade, resultando em um resultado de aleatoriedade diferente. Esse parâmetro é encontrado no contrato que o herda de VRFConsumerBaseV2:


contract VRFv2Consumer is VRFConsumerBaseV2 {

// @audit REQUEST_CONFIRMATIONS = quantos blocos confirmados

// antes de receber a aleatoriedade. Deve ser maior do que a profundidade

// das reorganizações comuns da cadeia que ocorrem na cadeia de destino.

//

// por exemplo, a polygon tem mais de 5 blocos reorganizados por dia com profundidade > 3 blocos

// e frequentemente tem reorganizações com profundidade < 30 blocos

//

// quando sua transação para solicitar aleatoriedade de VRF é movida

// para um bloco diferente, a aleatoriedade retornada pode mudar

// significando que o vencedor determinado pela aleatoriedade retornada

// também pode mudar!

   uint16 internal constant REQUEST_CONFIRMATIONS = 3;

Enter fullscreen mode Exit fullscreen mode

Este parâmetro frequentemente tem o valor de 3 porque este é o valor padrão no tutorial oficial da Chainlink, portanto, é simplesmente copiado sem muita reflexão pelos desenvolvedores. Os desenvolvedores de contratos inteligentes e auditores devem confirmar se o valor de REQUEST_CONFIRMATIONS é adequado para a(s) cadeia(s) de destino nas quais o contrato inteligente será implantado. Se o contrato inteligente for implantado em várias cadeias, um valor diferente para REQUEST_CONFIRMATIONS pode ser necessário para cada implantação.

Mais exemplos: [1]

Supondo a Precisão do Preço do Oráculo

Ao trabalhar com feeds de preço do Oracle, os desenvolvedores devem levar em consideração que diferentes feeds de preço podem ter diferentes precisões decimais; é um erro assumir que todos os feeds de preço informarão os preços com a mesma precisão. Geralmente, os pares não-ETH relacionam-se com 8 casas decimais, enquanto os pares ETH relacionam-se com 18 casas decimais.

Se a precisão não for devidamente considerada, há um grande potencial para erros por parte dos desenvolvedores. Por exemplo, o par ETH/USD é relatado com 8 casas decimais, mesmo que o ETH seja considerado um par não-ETH, pois seu preço está sendo expresso em USD. No entanto, existem feeds de preço, como AMPL/USD, que utilizam 18 casas decimais, o que contraria a regra geral de que os feeds de preço em USD usam apenas 8 casas decimais.

Os contratos inteligentes podem chamar AggregatorV3Interface.decimals() para obter o número exato de casas decimais do feed de preço que está sendo chamado.

Mais exemplos: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Endereço Incorreto do Feed de Preço do Oráculo

Alguns projetos codificarão os endereços dos feeds de preço do Oráculo diretamente no código. Outros terão endereços nos scripts de implantação para serem definidos durante a implantação do contrato. Onde quer que os endereços estejam localizados, os auditores devem verificar se eles apontam para o feed de preço do Oracle correto. Examine este código do concurso USSD da Sherlock:


// @audit endereço correto aqui, mas endereço errado no construtor

// chainlink btc/usd priceFeed

0xf4030086522a5beea4988f8ca5b36dbc97bee88c;

contract StableOracleWBTC is IStableOracle {

    AggregatorV3Interface priceFeed;



    constructor() {

        priceFeed = AggregatorV3Interface(

        // @audit endereço errado; este é o ETH/USD, não BTC/USD !

            0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419

        );

    }

Enter fullscreen mode Exit fullscreen mode

Aqui, o endereço correto para o feed de preços BTC/USD aparece no comentário, mas no construtor, o endereço do feed de preços ETH/USD aparece incorretamente. Os auditores devem verificar se os endereços do feed de preços estão corretos consultando a lista de feeds de preços da Ethereum mainnet da Chainlink. Para projetos implantados em L2s ou L1s alternativos, os auditores devem verificar se os endereços corretos do feed de preços estão sendo usados para essas redes.

Mais exemplos:  [1]

Atualizações de Preço do Oráculo Podem Ser Front-Run

Alguns protocolos de stablecoin que permitem aos usuários depositar garantias e emitir/resgatar tokens de stablecoin com base nos preços dos ativos de garantia podem estar sujeitos a ter valor extraído do protocolo por meio de ataques sanduíche em suas atualizações do oráculo.

As atualizações de preço do oráculo podem ser muito lentas em relação aos preços do mundo real, devido à atualização apenas após uma alteração % de desvio definida no preço, além de os atacantes poderem ver as atualizações do oráculo na mempool e fazerem front run. Isso é um problema complicado de resolver; soluções potenciais envolvem:

Para obter mais informações, consulte a  Angle’s Research Series e Synthetix History with Frontrunning.

Mais exemplos:[1, 2]

Negligência Não Tratada de Reversão do Oráculo

Chamadas para Oráculos podem potencialmente reverter, o que pode resultar em uma negação completa do serviço para contratos inteligentes que dependem deles. Multisigs da Chainlink podem bloquear imediatamente o acesso aos feeds de preços a seu critério, portanto, apenas porque um feed de preços está funcionando hoje não significa que ele continuará a fazê-lo indefinidamente. Os contratos inteligentes devem lidar com isso da seguinte forma:

  • envolver chamadas para Oráculos em blocos try/catch e lidar adequadamente com quaisquer erros,

  • fornecer funcionalidade para substituir ou atualizar os feeds do oráculo depois de configurados.

Se um feed do Oráculo configurado tiver falhado ou parado de funcionar, mas o contrato inteligente não tiver nenhuma fonte de dados alternativa, nem permitir atualizações para fontes de dados, esse contrato ficará permanentemente inoperante.

Isso seria especialmente prejudicial para protocolos das stablecoin e plataformas de oferecimento e pedido de empréstimo, onde grandes quantidades de valor do usuário são armazenadas na forma de garantias que não poderiam mais ser retiradas devido a chamadas aos oráculos de preços revertidos.

Mais exemplos:  [1, 2]

Desvinculação não gerenciada de ativos conectados

Considere um protocolo de oferecimento e pedido de empréstimo onde:

  • os usuários podem depositar um ativo comutado como WBTC (wrapped BTC) e emprestar contra ele,

  • o protocolo usa o feed BTC/USD da Chainlink para precificar o WBTC,

Se a ponte WBTC for comprometida e o WBTC se desvincular do BTC, o protocolo continuará a precificar o WBTC usando o preço BTC/USD, mesmo que o WBTC instantaneamente passe a valer muito menos do que o BTC nativo devido ao comprometimento da ponte.

Os usuários poderiam então comprar o WBTC por um valor muito menor do que o BTC nativo, depositá-lo no protocolo e tomar emprestado contra ele usando o valor do BTC nativo. Isso permitiria que os atacantes esvaziassem o protocolo no caso de um comprometimento da ponte levando a um evento de desvinculação.

Para ajudar a resolver esse problema, o protocolo poderia usar o feed de preço WBTC/BTC da Chainlink para monitorar um evento de desvinculação.

Mais exemplos: [1]

Oráculo Retorna Preço Incorreto Durante Flash Crashes

Os feeds de preço da Chainlink têm preços mínimos e máximos embutidos que serão retornados; se durante um flash crash, comprometimento da ponte ou evento de desvinculação, o valor de um ativo cair abaixo do preço mínimo do feed de preço, o feed de preço do oráculo continuará a relatar o preço mínimo (agora incorreto).

Um atacante poderia:

  • comprar esse ativo usando uma exchange descentralizada a um preço muito baixo,

  • depositar o ativo em uma plataforma de oferecimento e pedido de empréstimo usando os feeds de preço da Chainlink,

  • tomar emprestado contra o ativo pelo preço mínimo que o feed de preço da Chainlink retorna, mesmo que o preço real seja muito mais baixo.

Esse ataque permitiria que o atacante retirasse valor das plataformas de  oferecimento e pedido de empréstimo. Para mitigar tal ataque na blockchain, os contratos inteligentes poderiam verificar se minAnswer < receivedAnswer < maxAnswer.

Esse ataque também poderia ser potencialmente mitigado fora da blockchain por meio de monitoramento off-chain, que compara o preço mais recentemente relatado pela Chainlink com outras fontes off-chain, como exchanges centralizadas e/ou índices líquidos que agregam múltiplas fontes de preço off-chain para produzir um preço de índice; se as fontes externas estiverem relatando preços menores do que o minAnswer da Chainlink, o monitoramento off-chain poderia desativar o feed de preço do contrato inteligente para esse ativo, forçando qualquer transação para sua reversão.

Desenvolvedores e auditores podem encontrar os valores [minAnswer, maxAnswer] do feed do oráculo da Chainlink por meio de:

  • consultas so endereço do feed de preço na lista de feeds de preço da Ethereum mainnet da Chainlink (ou selecionar outras L1/L2 para feeds de preço em outras redes),

  • leitura  do valor "aggregator", por exemplo, para o feed de preço AAVE / USD,

  • leitura dos valores minAnswer e maxAnswer do contrato agregador.

Mais exemplos:[1, 2]

Realizar Apostas Após Solicitação de Aleatoriedade

Os participantes do mercado não devem ser capazes de fazer uma aposta ou outra entrada após uma solicitação de aleatoriedade ter sido feita, uma vez que retornará o resultado de uma loteria ou outra forma de sorteio, pois um atacante pode assim antecipar a resposta de aleatoriedade inspecionando o resultado vencedor e, em seguida, comprar um bilhete com as entradas vencedoras.

Mais exemplos: [1]

Re-solicitar Aleatoriedade

Re-solicitar Aleatoriedade permite que os provedores de serviços VRF retenham o cumprimento se o resultado não for favorável a eles, aguardem a nova solicitação e, em seguida, retornem a aleatoriedade somente se for favorável a eles.


Este artigo foi escrito por Dacian e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui

Top comments (0)