WEB3DEV

Cover image for Introdução e Análise de Casos de Vulnerabilidades de Reentrância Read-Only
Panegali
Panegali

Posted on

Introdução e Análise de Casos de Vulnerabilidades de Reentrância Read-Only

Visão geral

Entre vários problemas de segurança em contratos inteligentes, a vulnerabilidade de reentrância sempre foi a mais prejudicial e ocorre com frequência. A vulnerabilidade é causada pelo contrato que não gerencia adequadamente a ordem das alterações nas variáveis ​​de estado ao processar chamadas de função. A exploração de reentrância mais famosa foi o ataque The DAO de 2016, que levou a uma bifurcação da Ethereum. Atualmente, devido à melhoria da conscientização de segurança dos desenvolvedores, a maioria dos projetos usam bloqueios de reentrância para evitar vulnerabilidades de reentrância e reduzir os riscos de segurança. No entanto, no campo DeFi, embora o contrato use bloqueios de reentrância em algumas funções importantes, ainda é possível ser atacado por vulnerabilidades de reentrância read-only.

Introdução à vulnerabilidade de reentrância read-only

A vulnerabilidade de reentrância read-only é um caso especial da vulnerabilidade de reentrância. O local onde a vulnerabilidade ocorre é a função de visualização no contrato inteligente. Como esse tipo de função não modifica as variáveis ​​de estado do contrato, geralmente esse tipo de função não será decorada com bloqueios de reentrância. Quando o contrato da vítima chama a função de visualização do contrato vulnerável, como as variáveis ​​de estado usadas na função de visualização não foram atualizadas neste momento, os dados obtidos pelo contrato da vítima através da função de visualização também não são atualizados. Se o contrato da vítima depende da função de visualização para retornar o valor, pode haver situações anormais, como cálculo anormal de preços colaterais e cálculo incorreto de recompensas.

Análises de casos

A seguir, uma análise das vulnerabilidades de reentrância read-only em dois protocolos DeFi famosos, Curve e Balancer. O protocolo Curve se concentra na negociação de stablecoins (moedas estáveis) entre criptomoedas, como USDC, DAI, USDT, etc. O protocolo Balancer foi desenvolvido para permitir que os usuários criem seus portfólios de ativos e possam ser usados ​​como pools de liquidez para que outros usuários negociem. As vulnerabilidades de reentrância nesses dois protocolos foram descobertas pela ChainSecurity, e nós admiramos muito suas contribuições para a segurança da Web3.

Curve

Análise de vulnerabilidade na Curve

A Curve é uma exchange descentralizada. Os usuários podem fornecer ativos de liquidez ao pool de liquidez para obter uma parcela correspondente de tokens LP, que representam a parcela no pool de liquidez. Quando o usuário retirar a liquidez, o contrato queimará tokens LP e enviará os ativos de liquidez correspondentes ao usuário. Ao mesmo tempo, o contrato fornece uma função get_virtual_price para calcular o valor virtual dos tokens LP. A fórmula de cálculo é dividir D pela oferta total de tokens LP. D pode ser simplesmente entendido como o valor total dos tokens de liquidez. A função é implementada da seguinte forma:

Os usuários podem remover a liquidez chamando a função remove_liquidity do contrato após adicionar liquidez:

A função primeiro obtém o fornecimento total de tokens LP, calcula quantos ativos de liquidez o usuário pode retirar, subtrai o valor correspondente do saldo de ativos do pool de liquidez e, em seguida, envia para o usuário. Se for ETH, chama a função call de baixo nível para enviar, se for um token ERC20, usa sua função de transferência para enviar e, finalmente, grava o token LP.

Aqui, quando o ativo líquido for ETH, ele usará call para enviar ETH, e entrará na lógica da função fallback de msg.sender. Parece haver pontos que podem ser explorados, mas as funções-chave no contrato, como add_liquidity, exchange, remove_liquidity e assim por diante, usaram o bloqueio de reentrância, não podem ser reentrantes. Voltando à função remove_liquidity, como a atualização do estado não é concluída de forma síncrona, ao entrar na função fallback de msg.sender, os tokens LP ainda não foram queimados, mas o saldo no pool de liquidez foi deduzido, portanto, o saldo ficará menor enquanto TotalSupply não mudou. Observando as funções que usam essas duas variáveis ​​de estado no contrato, pode-se descobrir a função get_virtual_price é afetada e, essa função é uma função do tipo de visualização que não usa o bloqueio de reentrância. Se esta função for chamada durante a reentrância, o resultado será menor que o valor normal, ou seja, o preço dos tokens LP será reduzido.

Se o contrato externo depender do resultado retornado pela função get_virtual_price na Curve para processamento lógico, ele poderá ser afetado por essa vulnerabilidade de reentrância read-only.

Análise de casos de ataques

Em 9 de fevereiro de 2023, o protocolo DeFi dForcenet sofreu uma invasão, e a causa principal do ataque foi o uso do valor de retorno da função get_virtual_price do pool de liquidez da Curve por seu oráculo.

A transação de ataque é: (https://arbiscan.io/tx/0x5db5c2400ab56db697b3cc9aa02a05deab658e1438ce2f8692ca009cc45171dd)

Análise do processo de ataque:

Primeiro, uma grande quantidade de WETH foi emprestada por meio de um empréstimo flash e, em seguida, a liquidez foi adicionada ao pool ETH/wstETH na Curve usando a função add_liquidity, obtendo wstETHCRV:

Então, parte do wstETHCRV foi transferida para outro contrato de ataque, e wstETHCRV-gauge e USX foram emprestados, depositando wstETHCRV-gauge no contrato wstETHCRV-gauge:

Em seguida, a função remove_liquidity do pool ETH/wstETH na Curve foi chamada para retirar a liquidez. Ao retirar a liquidez, a lógica entrou na função fallback do contrato de ataque. Devido ao oráculo dForcenet usar a função get_virtual_price do pool ETH/wstETH na Curve, no estado reentrante atual, uma vez que o saldo de ETH no pool diminuiu, mas a oferta total de wstETHCRV não mudou, o preço virtual obtido será menor. Portanto, o atacante liquidou outro contrato de ataque e um empréstimo de usuário na função fallback:

Por fim, o invasor trocou o medidor wstETHCRV-gauge por WETH por meio de uma série de operações, devolveu o empréstimo instantâneo e obteve um lucro de 1.236 ETH e 710.000 tokens USX.

A implementação do oráculo no dForcenet:

Pode se ver que a função getPrice chama a função get_virtual_price do pool de liquidez da Curve e executa o processamento de multiplicação.

Balancer

Análise de vulnerabilidade na Balancer

Os usuários podem criar pools de liquidez para uma variedade de ativos por meio do protocolo Balancer e ganhar taxas facilitando negociações de outros usuários. Os usuários podem adicionar liquidez ao pool de liquidez criado por meio da função joinPool do contrato Balancer: Vault ou retirar liquidez por meio da função exitPool. Ambas as funções são implementadas chamando a função interna _joinOrExit, mas o tipo de operação transmitida é diferente. A função _joinOrExit é implementada da seguinte forma:

A função primeiro verifica os parâmetros de entrada e, em seguida, obtém os saldos dos tokens no pool de liquidez correspondente. Em seguida, a função _callPoolBalanceChange será chamada para calcular o saldo, transferir ativos e pagar taxas:

A função chamará a função onJoinPool ou onExitPool do pool de liquidez com base no tipo de operação e, em seguida, realizará as transferências de ativos. Ao remover a liquidez, a função chamará _processExitPoolTransfers para transferir ativos para o usuário. A implementação é a seguinte:

A função chama _sendAsset para transferir ativos. Se o token for WETH, ele será convertido em ETH e enviado usando sendValue. Como sendValue usa call para enviar ETH, se o destinatário for um contrato e tiver implementado a função fallback, a lógica continuará para a função fallback.

Após executar a função _callPoolBalanceChange, a função modificará o saldo de acordo com o tipo de pool chamando a função correspondente:

A lógica da função está aqui. Percebe-se que existe um problema onde a transferência é realizada primeiro, e depois o saldo é atualizado. Se houver ETH nos ativos, ele acionará a função fallback do receptor. Neste ponto, a transferência já foi concluída, mas o saldo interno do contrato ainda não foi atualizado. Portanto, estamos procurando possíveis vulnerabilidades no contrato e descobrindo que todas as principais funções usam bloqueio de reentrância para evitar tais ataques.

Embora nenhuma vulnerabilidade tenha sido encontrada na função de gravação, descobrimos que a função getPoolTokens, que é do tipo de visualização, usa o saldo interno do contrato e não é protegida por um bloqueio de reentrância porque não modifica nenhuma variável de estado.

Como resultado, se um contrato externo chamar a função getPoolTokens do contrato Balancer: Vault e usar seu valor de retorno para processamento posterior, ele poderá ficar vulnerável a um ataque de reentrância read-only.

Análise do caso de ataque

O projeto Sentiment na Arbitrum sofreu um ataque, resultando em uma perda de $ 1 milhão. A causa principal desse ataque foi que o oráculo do Sentiment ter usado os dados retornados pela função getPoolTokens no contrato Balancer: Vault, que possui uma vulnerabilidade conhecida de reentrância read-only.

A transação do ataque é: (https://arbiscan.io/tx/0xa9ff2b587e2741575daf893864710a5cbb44bb64ccdc487a100fa20741e0f74d)

Análise do processo de ataque:

O invasor emprestou 606 WBTC, 10.050 WETH e 18.000.000 USDC por meio de um empréstimo flash, criou uma conta por meio do Sentiment e depositou 50 WETH nela. Usando esta conta, eles depositaram os 50 WETH no Balancer: Vault:

Depois disso, o invasor forneceu liquidez ao pool chamando a função joinPool do contrato Balancer: Vault por meio do contrato de ataque, depositando 606 WBTC, 10.000 WETH e 18.000.000 USDC. Em seguida, ele removeu a liquidez chamando a função exitPool e acionou a função fallback do contrato de ataque ao enviar o ETH:

Na função fallback, o invasor estava chamando a função de empréstimo para emprestar fundos. Essa função estava usando RiskEngine.isAccountHealthy para verificar a integridade da conta e a função getPricedo oráculo foi obtida por WeightedBalancerLPOracle.getPrice. Esta função também estava usando os dados retornados pela função getPoolTokens do contrato Balancer: Vault. Nesse ponto, como a lógica ainda estava na função fallback do contrato de ataque, as quantidades de WBTC, WETH e USDC no pool de liquidez não haviam sido atualizadas. Portanto, o preço do oráculo era 16 vezes maior do que antes, permitindo que o invasor tomasse mais ativos emprestados usando os 50 WETH:

O invasor chamou repetidamente as funções borrow e exec para emprestar outros ativos e, após pagar o empréstimo flash, obteve um lucro de 0,5 WBTC, 30 WETH, 538.399 USDC e 360.000 USDT:

Confira a função getPrice do contrato WeightedBalancerLPOracle:

É possível observar que, depois que a função obtém o saldo de tokens do pool de liquidez correspondente por meio da função getPoolTokens do contrato Balancer.vault, o saldo do token será multiplicado na lógica subsequente. O saldo de token não atualizado fará com que aqui os resultados sejam ampliados.

Resumo

Este artigo apresenta brevemente o conceito de vulnerabilidades de reentrância read-only e analisa as vulnerabilidades de reentrância read-only em dois protocolos DeFi principais, bem como os casos de ataque reais correspondentes. Ao desenvolver um projeto DeFi, a possível integração de outros projetos deve ser considerada e várias medidas de segurança devem ser tomadas para reduzir os danos causados ​​por vulnerabilidades de reentrância. Se necessário, as funções do tipo visualização também podem ser protegidas por bloqueios de reentrância para aumentar a segurança.

Sobre nós

Na Eocene Research, fornecemos informações sobre intenções e segurança por trás de tudo o que você conhece ou não sobre blockchain e capacitamos cada indivíduo e organização a responder a perguntas complexas com as quais nem sonhávamos naquela época.

Saiba mais: Site | Medium | Twitter

Referência

Curve LP Oracle Manipulation: Post Mortem

Reentrancy Vulnerability Scope Expanded


Artigo escrito por Eocene | Security. Traduzido por Panegali.

Oldest comments (0)