2022年3月27日,以太坊上的质押DeFi项目Revest Finance因ERC-1155回调机制遭受攻击,导致约200万美元的代币(BLOKS、ECO、LYXe和RENA)被盗。我们第一时间分析了此次攻击,并在当晚(UTC+8)发布了分析推文。
事实上,在撰写推文时,我们对Revest TokenVault合约中的某个函数仍有疑虑。我们深入研究了该合约,试图理解其功能。随后,我们发现了一个另一个关键的零日漏洞,该漏洞可以以简单得多的方式被利用,并导致与已发生攻击相同程度的巨大损失。
我们随即立即联系了Revest Finance团队,他们迅速响应并提出了该漏洞的临时解决方案。在确认该漏洞无法被触发后,我们决定发布此博客。
本博客的后续内容将分为三个部分:Revest Finance的机制、原始重入攻击以及新的零日漏洞。
Revest Finance FNFT是什么?
Revest Finance的金融不可替代代币(FNFT)使得对锁定资产的未来权利进行无需信任的转让成为可能。入口合约(Revest 合约)提供了三种不同的接口,通过锁定底层资产来铸造FNFT:
mintTimeLock:底层资产将在一段时间后解锁。mintValueLock:当底层资产的价值高于或低于规定值时,将解锁底层资产。mintAddressLock:通过指定账户解锁底层资产。
Revest 合约连接了以下三个合约来锁定和解锁底层资产。
-
FNFTHandler:继承自ERC-1155代币。它为每一次锁定创建一个新的FNFT,
fnftId会递增。锁定在创建时规定了新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覆盖为fnftId。结果是,攻击者获得了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等知名投资者那里获得了数千万美元的投资。
官方网站:https://blocksec.com/ 官方Twitter账号:https://twitter.com/BlockSecTeam



