2022年3月27日,以太坊上的质押DeFi项目Revest Finance因ERC-1155回调机制遭受攻击,导致价值约200万美元的代币(具体为BLOCKS、ECO、LYXe和RENA)被盗。我们第一时间分析了此次攻击,并在当晚(UTC+8)发布了分析推文。
事实上,在发布推文时,我们对Revest TokenVault合约中的某个函数仍存有疑虑。我们深入研究了该合约以理解其功能。之后,我们发现了一个** otro critical zero-day vulnerability**,可以通过远为简单的方式进行利用,并造成与已发生攻击相同的巨大损失。
随后,我们立即联系了Revest Finance团队,他们迅速回应并提出了一个针对该漏洞的临时解决方案。在确认该漏洞无法被触发后,我们决定发布这篇博客。
本博客的后续内容将分为三个部分:Revest Finance的机制、原始重入攻击以及新的零日漏洞。
Revest Finance FNFT是什么?
Revest Finance的金融非同质化代币(FNFT)使得锁定资产的未来权利可以进行无需信任的转让。入口合约(Revest 合约)提供了三种不同的接口来通过锁定底层资产来铸造FNFT:
mintTimeLock:底层资产将在一段时间后解锁。mintValueLock:当底层资产的价值高于或低于规定值时,它将被解锁。mintAddressLock:底层资产将被指定的账户解锁。
Revest合约连接了其他三个合约来锁定和解锁底层资产。
-
FNFTHandler:继承自ERC-1155代币。它为每一次锁定创建一个带有递增
fnftId的新FNFT。锁定在创建时规定了新FNFT的总供应量。FNFT不能以其他方式铸造,但可以被销毁以解锁底层资产。 -
LockManager:在创建时记录每个锁定的解锁条件,并在解锁时决定锁定是否可以被解锁。
-
TokenVault:接收和发送底层资产,并记录每个FNFT的元数据,例如指定FNFT的价值。
我们以mintAddressLock为例来说明铸造FNFT的过程。


以上两图描述了FNFT的创建、铸造和销毁过程。
具体来说,用户A将100 WETH锁定到Revest Finance,创建了相应的FNFT,fnftId为1。最终,它以指定的份额将100个1-FNFT铸造给指定的接收者。
请注意,一旦底层资产被解锁,那么每个1-FNFT都可以被销毁以接收一(*1e18)个WETH。如图2所示,用户B通过销毁25个1-FNFT提取了25(*1e18)个WETH。
此外,Revest合约还提供了另一个名为depositAdditionalToFNFT的接口,该接口存在两个漏洞,将在下文中讨论。
我们首先使用以下两图来描述此函数的正常用法。


depositAdditionalToFNFT函数向现有的锁定(由fnftId指定)添加更多的底层资产。合理地(图3),它要求指定的数量与指定FNFT的总供应量相同,然后将添加的资产均匀分配给每个指定的FNFT。
否则(图4),它会创建一个带有最新fnftId的新锁定,销毁指定数量的旧FNFT并铸造指定数量的新FNFT,然后将新锁定的depositAmount记录为旧锁定depositAmount与指定金额的总和,如下面的代码所示。
// 现在,我们转移到Token Vault
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);
由于TokenVault合约中记录的depositAmount表示一个指定FNFT可以提取的底层资产量,该操作将指定数量的旧FNFT的价值从旧锁定转移到新锁定。
(指定数量大于总供应量将回滚交易)
重入漏洞是什么?
在本节中,我们将说明重入攻击是如何工作的,并讨论根本原因和修复方法。



以上三图基本描述了重入攻击的整个过程。具体来说,攻击者首先锁定零RENA代币以铸造2个无价值的1-FNFT。其次,攻击者再次锁定零RENA代币,但铸造了360,000个同样无价值(目前)的2-FNFT。在最后一步,攻击者通过继承自ERC-1155代币标准的FNFTHandler的回调机制,重新进入Revest合约的depositAdditionalToFNFT函数,该函数在更新fnftId之前,将fnftId为2的锁定的depositAmount覆盖。结果,攻击者获得了360,001个2-FNFT,其depositAmount为1e18,这意味着他可以从TokenVault合约中提取360,001 * 1e18 RENA。此外,唯一成本是1e18 RENA。
修复方法
Revest Finance的代码完全符合经典的重入模式:使用fnftId -> 通过回调机制进行外部调用 -> 更新fnftId。因此,修复问题的最直接方法是打破这种模式。修复后的代码如下所示:
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);
}
首先,它将更新操作移到外部调用(_mint)之前,这可以避免攻击。其次,由于系统不允许铸造零FNFT和重复铸造同一FNFT,它添加了两个检查以确保系统按预期工作,从而提高系统的安全性。
新的零日漏洞
在分析Revest Finance的代码时,TokenVault合约中的handleMultipleDeposits函数总是让我们感到困惑,其代码如下所示。
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);
}
}
在调用depositAdditionalToFNFT函数时,handleMultipleDeposits函数会更改旧锁定的depositAmount或记录新锁定的depositAmount。当newFNFTId为零时,它不记录新锁定的depositAmount,因为这是一个向现有锁定添加额外资产的操作。
根据常识,当newFNFTId不为零时,它只记录新锁定的depositAmount,而不更改旧锁定的depositAmount。然而,代码告诉我们,它不仅记录新锁定的depositAmount,还更改了旧锁定的depositAmount。
我们认为这是一个严重的零日逻辑漏洞,并编写了一个PoC来验证它。以下三图描述了PoC的工作原理。



具体来说,攻击者首先锁定零RENA来铸造360,000个1-FNFT。之后,攻击者直接调用depositAdditionalToFNFT函数来创建一个新的锁定。由于逻辑错误,TokenVault合约将旧锁定的depositAmount错误地从零更改为1e18。结果,攻击者获得了价值359,999 RENA的359,999个1-FNFT。显然,PoC比实际的重入攻击要简单得多。
修复漏洞的临时解决方案
这是一个逻辑错误,我们建议使用以下代码进行修复。
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);
}
}
由于两个易受攻击的合约:TokenVault和FNFTHandler存储了大量关键状态,项目无法在不迁移状态的情况下重新部署TokenVault合约和FNFTHandler合约。为了避免该漏洞遭受进一步攻击,项目重新部署了Revest合约的精简版本,禁用了更复杂的功能,以减少潜在攻击者的攻击面。在检查了临时解决方案后,我们认为精简版的Revest合约可以缓解本博客中提到的可能攻击。
启示
确保DeFi项目的安全并非易事。除了代码审计,我们认为社区应该采取主动措施来监控项目状态,并在攻击发生之前就阻止它。
关于BlockSec
BlockSec是一家领先的区块链安全公司,成立于2021年,由一群全球知名的安全专家组成。公司致力于提高新兴Web3世界的安全性和可用性,以促进其大规模采用。为此,BlockSec提供智能合约和EVM链安全审计服务,用于安全开发和主动阻止威胁的Phalcon平台,用于资金追踪和调查的MetaSleuth平台,以及用于Web3构建者在加密世界高效冲浪的MetaDock扩展。
迄今为止,公司已为MetaMask、Uniswap Foundation、Compound、Forta和PancakeSwap等300多家知名客户提供服务,并在两轮融资中从Matrix Partners、Vitalbridge Capital和Fenbushi Capital等领先投资者那里获得了数千万美元的投资。
官方Twitter账号:https://twitter.com/BlockSecTeam



