​More than Re-entrancy : Revest Finance Attack Event Analysis
BlockSec
2022-04-18 11:06
本文约5095字,阅读全文需要约20分钟
Revest Finance, a staking DeFi project on Ethereum, was hacked and lost about $2 million.

On March 27, 2022, Revest Finance, a staking DeFi project on Ethereum, was hacked and lost about $2 million. The BlockSecTeam team immediately intervened in the analysis and shared our analysis results with the community on tweeter. In fact, when we shared our analysis results with the community via tweeter, we found a critical zero-day vulnerability in the TokenVault contract of Revest Finance. Using this vulnerability, attackers can steal assets in the protocol in a simpler way. So we immediately contacted the Revest Finance project party. After confirming that the vulnerability has been fixed, we decided to share this blog with the community.

0. What's the Revest Finance FNFT

secondary title

Revest Finance is a solution for staking in the DeFi field. Any DeFi staking that users participate in through Revest Finance can directly generate an NFT, namely FNFT (Finance Non-Fungible Token), which represents the current and current status of the staking position. future value. Users can interact with projects through the 3 interfaces provided by Revest Finance. Pledge your own digital assets, mint out the corresponding FNFT.

•mintTimeLock : The digital assets pledged by the user can only be unlocked after a period of time.

•mintValueLock : The digital assets pledged by users can only be unlocked when they appreciate or depreciate to a preset value.

•mintAddressLock : The digital assets pledged by the user can only be unlocked by the preset account.

Revest Finance completes the locking and unlocking of digital assets deposited by users through the following three smart contracts.

•FNFTHandler: Inherited from ERC-1155 token (openzepplin implementation). Every time the lock operation is executed, fnftId will be incremented (fnftId is similar to tokenId in ERC721). When FNFT is created, the user needs to specify its totalSupply. When users want to withdraw the underlying assets behind FNFT, they need to burn the corresponding proportion of FNTF.

•LockManage : Record the conditions for FNFT to be unlocked (unlock).

•TokenVault: Receive and send the underlying asset deposited by the user, and record the metadata of each FNFT. For example, the asset type pledged behind the FNFT with fnftId = 1.

Because of this attack, the entry point of the hacker attack is the mintAddressLock function, so let’s take this function as an example to describe the life cycle of FNFT.

  • •unlocker : User X ->User A calls Revest's mintAddressLock function

  • Only User X can unlock this asset •recipients : [User A , User B , User C] •quantities : [50 , 25 , 25] -> mint quantity is 100 (sum (quantities)) , User A , User B , User C each owns 50, 25, and 25 coins. •asset : WETH -> FNFT from mint takes WETH as collateral. • depositAmount : 1e18 -> The amount of collateral behind each FNFT is 1 WETH ( WETH decimal is 18 )

  • Assuming that there is no other FNFT in the current system, User A interacts with the system through mintAddressLock, and the fnftId returned by FNFTHandler = 1

  • •fnftId : 1•unlocker : User X 

  • LockManger adds corresponding records for it

  • •fnftId : 1•asset : WETH•depoistAmount : 1e18

Token Vault adds corresponding records for it

Then Token Valut needs to transfer 100 * 1e18 WETH from User A.

Finally, the system gives User A, User B, and User C mint 50, 25, and 25 pieces of 01-FNFT respectively.

This is done by minting the FNFT through the mintAddressLock function.

After User X unlocks 01-FNFT, User B can withdraw the underlying asset through withdrawFNFT. As shown in Figure 2, User B wants to withdraw the 25 digital assets pledged by 01-FNFT in his hands.

The protocol first checks whether 01-FNTF has been unlocked. If it has been unlocked, the protocol will burn 25 01-FNFTs of User B and transfer 25*1e18 WETH to him. At this time, the totalSupply of 01-FNFT is 75.

The Revest contract also provides another interface called depositAdditionalToFNFT to allow users to add more underlying assets to an existing FNFT. Below we describe its "normal" usage with 2 pictures.

There are three cases here

1.quantity == 01-FNFT.totalSupply() as shown in Figure 3

•quantity = 75 ->Taking the scenario in Figure 2 as the context, User A wants to add more collateral for 01-FNFT.

•amount = 0.5*1e18 ->Additional pledge for 75 01-FNFT.

Each 01-FNFT will add 0.5*1e18 WETH.

So User A needs to transfer 37.5*1e18 WETH (75 * 0.5*1e18) to the Token Vault. The Token Vault modifies the system accounting and changes the depositAmount to 1.5*1e18. Now each 01-FNFT carries assets of 1.5*1e18 WETH.

At this time, User C calls withdrawFNFT to burn off the 25 01-FNFTs he holds, and he can take away 25*(1.5*1e18) = 37.5*1e18 WETH.

Therefore, the totalSupply of 01-FNFT is 50 at this time.

2.quantity < 01-FNFT.totalSupply() as shown in Figure 4

  • •quantity = 10 ->Taking the scene in Figure 3 as the context, User A continues to add more collateral for 01-FNFT.

  • Additional pledge for 10 01-FNFTs. •amount = 0.5*1e18 -> Add 0.5*1e18 WETH to each of the 10 01-FNFTs< 01-FNFT.totalSupply()
    due to quantity

  • •fnftId : 2•asset : WETH•depositAmount : 2.0*1e18 (1.5*1e18 + 0.5*1e18) 

  • Therefore, User A pays 5*1e18 WETH to the agreement. The system will burn 10 01-FNFTs, mint 10 02-FNFTs, and burn the assets carried by 10 01-FNFTs and User A’s newly transferred Assets, injected into 02-FNFT. So there is

  • at this time

• 01-FNFT.totalSupply : 40 01-FNFT.depositAmount : 1.5*1e18 (logically it should be, see below: the New Zero-day Vulnerability) • 02-FNFT.totalSupply : 10 02-FNFT.depositAmount : 2.0* 1e18

In this case, the transaction will revert.

1. What't the Re-entrancy vulnerability

secondary title

As shown in Figure 5
first step:
first step:

•depositAmount = 0 

•quantities = [2] 

The attacker calls the mintAddressLock function

mint issued 2 01-FNFTs, since the attacker set the depositAmount to 0, he did not transfer any digital assets. Equivalent to the underlying asset carried behind 01-FNFT is 0.

•depositAmount = 0

Step 2: The attacker calls the mintAddressLock function again

•quantities = [360000] Prepare mint 36w pieces 02-FNFT depositAmount is 0.

In the last step of mint, the attacker used the ERC-1155 callback mechanism to re-enter the depositAdditionalToFNFT function. (See the _doSafeTransferAcceptanceCheck function given below for details)

•quantity = 1

•amount = 1*1e18

•fnftId = 1 

In depositAdditionalToFNFT, the attacker passes in< fntfId.totalSupply(),because of quantity

Finally, the attacker calls the withdrawNFNFT function, burns 360,001 02-FNFTs, and takes away 360,001*1e18 RENA.

  • suggested fixes

2. the New Zero-day Vulnerability

secondary title

While the blockSecTeam team was analyzing the code of Revest Finance, the handleMultipleDeposits function caught our attention.

When the user calls the depositAdditionToNFT function to add collateral, the function will change the depositAmount of the FNFT. From the code, we can find that when newFNFTId != 0, this function not only changes the depositAmount of FNFT corresponding to fnftId, but also changes the depositAmount corresponding to newFNFTId.

According to common sense, when newFNFTId !=0, the system should only record the depositAmount corresponding to newNFTId. The depositAmount corresponding to fnftId should not be changed.
We think this is a very serious logic bug. By exploiting this vulnerability, attackers can easily withdraw digital assets from the system. The following three pictures describe the principle of the simulated attack.

Assume the latest fnftId = 1
First, the attacker calls the mintAddressLock function, and mint generates 360,000 01-FNFTs. The attacker sets the amount to 0 so he doesn't have to transfer any assets into the Revest Finance protocol.

After mint ends, the attacker has 360000 01-FNFT with depositAmount =0.

•fnftId = 1

•amount = 1 * 1e18

•quantity = 1

Then the attacker calls the depositAdditionalToFNFT function with the following parameters
The protocol transfers the attacker amount * quantity of tokens, that is, 1 * 1e18RENA
The protocol will burn one 01-FNFT of the attacker and mint one 02-FNFT for it (assuming latest fnftId = 2)
According to the logic in the handleMultipleDeposits function, the asset with fnftId = 2 will have its depositAmount set to 1.0*1e18.

But for assets with fnftId = 1, their depositAmount will also be set to 1.0*1e18, which should be 0!

Obviously, using this method to attack is simpler and more direct than real reentrancy attacks.

  • text

In response to this vulnerability, the blockSecTeam team provided a corresponding patch method.

secondary title

secondary title

4. Summary

Improving the security of DeFi projects is not an easy task. In addition to code auditing, we believe that the community should adopt more proactive methods, such as project monitoring and early warning, and even attack blocking to make the DeFi community safer. (https://mp.weixin.qq.com/s/o41Da2PJtu7LEcam9eyCeQ).

references

*[1]: https://blocksecteam.medium.com/revest-finance-vulnerabilities-more-than-re-entrancy-1609957b742f

BlockSec
作者文库