Back to Blog

Vulnerabilidades do Revest Finance: Mais do que Reentrada

Code Auditing
31 de março de 2022
9 min read

Em 27 de março de 2022, o projeto DeFi de staking Revest Finance na Ethereum foi atacado devido ao mecanismo de call-back do ERC-1155, o que causou o roubo de tokens no valor de aproximadamente $2M (especificamente BLOCKS, ECO, LYXe e RENA). Analisamos o ataque em primeiro lugar e publicamos no Twitter nossa análise naquela noite (UTC+8).

Na verdade, no momento em que escrevemos o Twitter, ainda tínhamos algumas dúvidas sobre uma função no contrato TokenVault da Revest. Investigamos o contrato tentando entender sua funcionalidade. Posteriormente, descobrimos que se trata de outra vulnerabilidade crítica de dia zero, que pode ser explorada de uma forma muito mais simples e pode causar as mesmas enormes perdas (como o ataque que aconteceu).

Em seguida, contatamos imediatamente a equipe da Revest Finance, e eles responderam rapidamente e propuseram uma solução alternativa para a vulnerabilidade. Após confirmar que a vulnerabilidade não podia ser acionada, decidimos publicar este blog.

A seguir, este blog é composto por três partes: o mecanismo da Revest Finance, o ataque de re-entrância original e a nova vulnerabilidade de dia zero.

O que é o FNFT da Revest Finance

O Financial Non-Fungible Token (FNFT) da Revest Finance torna possível a transferência sem necessidade de confiança de direitos futuros sobre ativos bloqueados. O contrato de entrada (contrato Revest) fornece três interfaces diferentes para cunhar FNFTs bloqueando ativos subjacentes:

  • mintTimeLock: o ativo subjacente será desbloqueado após um período de tempo.
  • mintValueLock: o ativo subjacente será desbloqueado quando seu valor subir acima ou cair abaixo de um valor predeterminado.
  • mintAddressLock: o ativo subjacente será desbloqueado por uma conta predeterminada.

O contrato Revest conecta os outros três contratos para bloquear e desbloquear ativos subjacentes.

  • FNFTHandler: herdado do token ERC-1155. Ele cria um novo FNFT com o fnftId incremental para cada bloqueio. O bloqueio determina o fornecimento total do novo FNFT na criação. O FNFT não pode ser cunhado de nenhuma outra forma, mas pode ser queimado para desbloquear ativos subjacentes.

  • LockManager: registra as condições de desbloqueio para cada bloqueio durante a criação e decide se o bloqueio pode ser desbloqueado no momento do desbloqueio.

  • TokenVault: recebe e envia os ativos subjacentes e registra os metadados para cada FNFT, como o valor de um FNFT específico.

Usamos mintAddressLock como exemplo para ilustrar o processo de cunhagem de FNFTs.

Figura 1
Figura 2

As duas figuras acima descrevem como um FNFT é criado, cunhado e queimado. Especificamente, o usuário A bloqueia 100 WETH na Revest Finance, criando o FNFT correspondente com fnftId igual a 1. Por fim, ele cunha 100 1-FNFT para destinatários específicos com participações específicas.

Observe que, uma vez que o ativo subjacente é desbloqueado, cada 1-FNFT pode ser queimado para receber um (*1e18) WETH. Conforme mostrado na Figura 2, o usuário B retira 25 (* 1e18) WETH ao queimar 25 1-FNFT.

Além disso, o contrato Revest fornece outra interface, chamada depositAdditionalToFNFT, que apresenta duas vulnerabilidades que serão discutidas a seguir.

Primeiro usamos as duas figuras a seguir para descrever o uso normal dessa função.

Figura 3
Figura 4

A função depositAdditionalToFNFT bloqueia mais ativos subjacentes em um bloqueio existente (especificado por fnftId). Razoavelmente (Figura 3), ela exige que a quantidade especificada seja igual ao fornecimento total do FNFT especificado e, em seguida, distribui uniformemente os ativos adicionados a cada FNFT especificado.

Caso contrário (Figura 4), ela cria um novo bloqueio com o fnftId mais recente, queima as quantidades especificadas do FNFT antigo e cunha a quantidade especificada do novo FNFT, e então registra o depositAmount do novo bloqueio como a soma do depositAmount do bloqueio antigo e o valor especificado, conforme mostrado no código a seguir.

// Agora, transferimos para o cofre de tokens
if(fnft.asset != address(0)){
    IERC20(fnft.asset).safeTransferFrom(_msgSender(), vault, quantity * amount);
}

ITokenVault(vault).handleMultipleDeposits(fnftId, newFNFTId, fnft.depositAmount + amount);

emit FNFTAddionalDeposited(_msgSender(), newFNFTId, quantity, amount);

Como o depositAmount registrado no contrato TokenVault indica a quantidade do ativo subjacente que um FNFT específico pode retirar, essa operação transfere o valor das quantidades especificadas do FNFT antigo do bloqueio antigo para o novo bloqueio.

(A quantidade especificada maior que o fornecimento total reverterá a transação)

O que é a Vulnerabilidade de Re-entrância

Nesta parte, ilustraremos como o ataque de re-entrância funciona e discutiremos a causa raiz e o método de correção.

Figura 5
Figura 6
Figura 7

As três figuras acima descrevem basicamente todo o processo do ataque de re-entrância. Especificamente, o atacante primeiro bloqueia zero tokens RENA para cunhar 2 1-FNFT que não têm valor. Em seguida, o atacante bloqueia zero tokens RENA novamente, mas cunha 360.000 2-FNFT que também não têm valor (por enquanto). Durante a última etapa, o atacante re-entra na função depositAdditionalToFNFT do contrato Revest por meio do mecanismo de call-back do FNFTHandler herdado do padrão de token ERC-1155, o que sobrescreve o depositAmount do bloqueio com fnftId igual a 2 antes da atualização do fnftId. Como resultado, o atacante obtém 360.001 2-FNFT com depositAmount igual a 1e18, o que significa que ele pode retirar 360.001 * 1e18 RENA do contrato TokenVault. Além disso, o único custo é 1e18 RENA.

Método de Correção

Os códigos da Revest Finance estão completamente alinhados com o padrão clássico de re-entrância: usar fnftId -> chamada externa com mecanismo de callback -> atualizar fnftId. Portanto, a maneira mais direta de corrigir os problemas é quebrar esse padrão. O código corrigido é mostrado abaixo:

function mint(
    address account, 
    uint id, 
    uint amount, 
    bytes memory data
) external override onlyRevestController {
    require(amount > 0, "Invalid amount");
    require(supply[id] == 0, "Repeated mint for the same FNFT");
    supply[id] += amount;
    fnftsCreated += 1;
    _mint(account, id, amount, data);
}

Primeiro, ele move a operação de atualização para antes da chamada externa (_mint), o que pode evitar o ataque. Segundo, como o sistema não permite cunhar zero FNFT nem cunhar repetidamente o mesmo FNFT, ele adiciona duas verificações para garantir que o sistema funcione conforme esperado, o que pode melhorar a segurança do sistema.

A Nova Vulnerabilidade de Dia Zero

Ao analisar o código da Revest Finance, a função handleMultipleDeposits no contrato TokenVault sempre nos confundiu, cujo código é mostrado abaixo.

function handleMultipleDeposits(
    uint fnftId,
    uint newFNFTId,
    uint amount
) external override onlyRevestController {
    require(amount >= fnfts[fnftId].depositAmount, 'E003');
    IRevest.FNFTConfig storage config = fnfts[fnftId];
    config.depositAmount = amount;
    mapFNFTToToken(fnftId, config);
    if(newFNFTId != 0) {
        mapFNFTToToken(newFNFTId, config);
    }
}

Durante a chamada à função depositAdditionalToFNFT, a função handleMultipleDeposits altera o depositAmount do bloqueio antigo ou registra o do novo. Quando o newFNFTId é zero, ela não registra o depositAmount do novo bloqueio, pois esta é uma operação para adicionar ativos extras ao bloqueio existente.

De acordo com o senso comum, quando o newFNFTId não é zero, ela apenas registra o depositAmount do novo bloqueio, mas não altera o depositAmount do antigo. No entanto, o código nos mostra que ele não só registra o depositAmount do novo bloqueio, mas também altera o depositAmount do antigo.

Acreditamos que essa é uma séria vulnerabilidade lógica de dia zero e então escrevemos um PoC para verificá-la. As três figuras a seguir descrevem como o PoC funciona.

Figura 8
Figura 9
Figura 10

Especificamente, o atacante primeiro bloqueia zero RENA para cunhar 360.000 1-FNFT. Depois disso, o atacante invoca diretamente a função depositAdditionalToFNFT para criar um novo bloqueio. Devido ao bug lógico, o contrato TokenVault altera incorretamente o depositAmount do bloqueio antigo de zero para 1e18. Como resultado, o atacante obtém 359.999 1-FNFT no valor de 359.999 RENA. Obviamente, o PoC é muito mais simples do que o ataque de re-entrância real.

A Solução Alternativa para Corrigir a Vulnerabilidade

Este é um bug lógico, e recomendamos usar o seguinte código para corrigi-lo.

function handleMultipleDeposits(
    uint fnftId,
    uint newFNFTId,
    uint amount
) external override onlyRevestController {
    require(amount >= fnfts[fnftId].depositAmount, 'E003');
    IRevest.FNFTConfig memory config = fnfts[fnftId];
    config.depositAmount = amount;
    if(newFNFTId != 0) {
        mapFNFTToToken(newFNFTId, config);
    } else {
        mapFNFTToToken(fnftId, config);
    }
}

Como os dois contratos vulneráveis: TokenVault e FNFTHandler armazenam muitos estados críticos, o projeto não pode reimplantar o contrato TokenVault e o contrato FNFTHandler sem migrar os estados. Para evitar ataques futuros a essa vulnerabilidade, o projeto reimplantou uma versão simplificada do contrato Revest, que desativa funções mais complexas para reduzir as superfícies disponíveis a qualquer potencial atacante. Após verificar a solução alternativa, acreditamos que o contrato Revest simplificado pode mitigar os possíveis ataques mencionados neste blog.

Conclusão

Tornar um projeto DeFi seguro não é uma tarefa fácil. Além da auditoria de código, acreditamos que a comunidade deve adotar um método proativo para monitorar o status do projeto e bloquear o ataque antes mesmo que ele ocorra.

Sobre a BlockSec

A BlockSec é uma empresa pioneira em segurança blockchain estabelecida em 2021 por um grupo de especialistas em segurança de renome mundial. A empresa está comprometida em aprimorar a segurança e a usabilidade para o emergente mundo Web3, a fim de facilitar sua adoção em massa. Para tanto, a BlockSec fornece serviços de auditoria de segurança de contratos inteligentes e cadeias EVM, a plataforma Phalcon para desenvolvimento de segurança e bloqueio proativo de ameaças, a plataforma MetaSleuth para rastreamento e investigação de fundos, e a extensão MetaDock para construtores web3 navegarem com eficiência no mundo cripto.

Até o momento, a empresa atendeu mais de 300 clientes ilustres, como MetaMask, Uniswap Foundation, Compound, Forta e PancakeSwap, e recebeu dezenas de milhões de dólares americanos em duas rodadas de financiamento de investidores proeminentes, incluindo Matrix Partners, Vitalbridge Capital e Fenbushi Capital.

Site oficial: https://blocksec.com/

Conta oficial no Twitter: https://twitter.com/BlockSecTeam

Sign up for the latest updates
Perdas de ~5,57 milhões USD: exploração de falhas no X100 Freezer e no NEM | Boletim semanal da BlockSec
Security Insights

Perdas de ~5,57 milhões USD: exploração de falhas no X100 Freezer e no NEM | Boletim semanal da BlockSec

Este relatório semanal aborda dois incidentes de segurança na Ethereum e na BNB Chain no período de 19/10/2026–25/10/2026. Ele examina uma tomada de controle da governança do X100 Freezer financiada por um empréstimo-relâmpago e uma falha na liquidação de impostos que afetou um pool de liquidez de um token NEM na BNB Chain. As perdas estimadas no escopo do relatório totalizam aproximadamente 5,57 milhões USD: cerca de 4,75 milhões USD da transação representativa do X100 e cerca de 820 mil USD do incidente no pool do NEM. O total exclui as extrações subsequentes do X100 relatadas separadamente.

Perdas de ~3,87 milhões USD: ataques à Autheo Network e à UNCX | Resumo semanal da BlockSec
Security Insights

Perdas de ~3,87 milhões USD: ataques à Autheo Network e à UNCX | Resumo semanal da BlockSec

Este relatório semanal aborda 3 incidentes de segurança no período de 12/10/2026–18/10/2026, com perdas totais estimadas em aproximadamente 3,87 milhões USD. A Autheo Network sofreu um comprometimento de chave privada com perdas de cerca de 1,76 milhões USD, a Qubic Wallet perdeu cerca de 1,30 milhões USD devido à geração de seeds fracas, e os contratos Smart Token Vesting V2 da UNCX Network sofreram um erro contábil de cerca de 807 mil USD nas redes Ethereum, BNB Chain, Base e Robinhood Chain. A análise em destaque da UNCX explica como um cálculo incorreto do total de cotas misturou parcelas de vesting criadas com diferentes proporções entre tokens e cotas, permitindo saques acima do devido de pools compartilhados.

~US$ 542 mil perdidos: ataque ao MakerDaoBot | Boletim semanal da BlockSec
Security Insights

~US$ 542 mil perdidos: ataque ao MakerDaoBot | Boletim semanal da BlockSec

O relatório cobre 1 incidente no Ethereum (05–11/10/2026), com perda estimada de US$ 542 mil. Um adaptador malicioso obteve acesso aos saldos MakerDAO via função desprotegida do MakerDaoBot, liquidou 4 leilões ETH-A e converteu 200 ETH-A em WETH, depois enviados como ETH ao atacante.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit