First introduced in 1995 by Nick Szabo, the concept of a smart contract is a computer protocol designed to disseminate, validate, or enforce contracts in an informational manner, allowing trusted transactions to be made without a third party, which are traceable and irreversible. However, smart contracts are not secure either, with Re-Entrance attack vulnerability being one of the attack methods in Ether, which caused a hard fork of Ether back in 2016 due to The DAO incident.
In Ether, smart contracts can call the code of other external contracts. Since smart contracts can call external contracts or send Ether, these operations require contracts to submit external calls, so these external calls to contracts can be used by attackers to cause attack hijacking, making the attacked contract re-execute at any location, bypassing the constraints in the original code, thus reentry attacks occur. A re-entry attack is essentially similar to a recursive call in programming, so it can occur when a contract sends Ether to an unknown address.
In simple terms, there are 2 conditions for a reentry attack vulnerability to occur.
An external contract is called and the contract is insecure
The function call of the external contract precedes the modification of the state variable
A simple code snippet example is given below.
The above code snippet is the simplest withdrawal operation, and the next will give you a detailed analysis of the reasons caused by the re-entry attack.
Before the formal analysis of the re-entry attack, let’s introduce a few key knowledge.
01 Transfer method
Since the re-entry attack will be sent during the transfer operation, and the common transfer method in Solidity is .transfer(), .send() and .gas().call.vale()(), the following three transfer methods are explained. .transfer(): will only send 2300 gas to be called, and will be rolled back by throw when the send fails, thus preventing reentrant attacks. .send(): will only be called to send 2300 gas, and will return a boolean value of false when it fails, thus preventing reentrant attacks. .gas().call.vale()(): send all the gas when called, and return false when it fails, which is not effective to prevent re-entry attacks.
02 fallback function
Next, let’s explain the fallback function.
The fallback function: The fallback function is a function without a name in each contract, and the function has no parameters, no return value, as follows.
Fallback functions are executed in the following cases.
(a) No function is matched when the contract is called.
No data is passed.
The smart contract receives Ether (in order to accept Ether, the fallback function must be marked as payable).
03 Vulnerable code
The following code is a re-entry attack, implemented as a public wallet-like contract, all users can use deposit() to deposit into the Reentrance contract, and can also use withdraw() to withdraw from the Reentrance contract, but of course everyone can also use balanceof() to check the balance of yourself or others in the contract.
First play the victim using an account (0x5B38Da6a701c568545dCfcB03FcB875f56beddC4) and deploy the contract in the Remix IDE by clicking the Deploy button.
After successful deployment of the contract, fill in the VALUE setting box with 5, change the unit to ether and click deposit to deposit 5 Ether.
Click on wallet to check the balance of this contract and find a balance of 5 ether, indicating that our deposit was successful.
The following code, on the other hand, is an attack against the above vulnerable contract.
Using another account (0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2) as the attacker, copy the address of the vulnerable contract into the Deploy settings box and click Deploy to deploy the above attack contract.
After successful deployment, first call the wallet() function to see if the balance of the attack contract is 0.
The attacker first deposits 1 ether into the exploit contract, where the VALUE is set to 1 ether, and then clicks on the deposit in the attack contract to make the deposit.
Call the wallet function of the contract again to check the balance of the vulnerable contract and find that it has become 6 ether.
The attacker (0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2) calls the attack function of the attack contract to simulate the attack, and then calls the wallet function of the attacked contract to check the balance of the contract and finds that it has gone to zero. 6 ether in the attacked contract has been withdrawn to the attacker contract, which caused the re-entry attack.
04 Source code analysis
The above explains how to conduct a re-entry attack has been the cause of the vulnerability, here combing the source code of the vulnerability and the steps of the attack, listed the key code.
On June 17, 2016, the TheDAO project was hit by a re-entry attack that resulted in more than 3 million Ether coins being separated from the TheDAO asset pool, and the attacker used the splitDAO() function in the TheDAO smart contract to reuse his own DAO assets in the re-entry attack, constantly The attacker uses the splitDAO() function in the TheDAO smart contract to reuse his own DAO assets to perform a re-entry attack, continuously separating DAO assets from the TheDAO project’s asset pool and transferring them to his account.
The following code is part of the splitDAO() function, the source code of which is in TokenCreation.sol, which transfers tokens from the parent DAO to the child DAO. The balance array uint fundsToBeMoved = (balances[msg.sender] * p.splitData.splitBalance) / p.splitData.totalSupply determines the number of tokens to be transferred.
The following code performs a withdrawal reward operation. p.splitData is the same each time the attacker calls this function (it is a property of p, i.e., a fixed value), and the values of p.splitData.totalSupply and balances[msg.sender] are not updated due to a function order problem, which occurs after the transfer operation, are not updated.
paidOut[_account] += reward The update status variable is placed after the payOut function call in the problem code.
A .call.value call is made to _recipient, transferring _amount Wei, and the .call.value call will use all the currently remaining gas by default.
Through the analysis of the re-entry attack above, we can find that the focus of the re-entry attack vulnerability lies in the use of fallback and other function callbacks themselves to cause recursive calls to cyclic transfer operations, so the solutions to the re-entry attack vulnerability are as follows.
01 Use other transfer functions
Use Solidity’s built-in transfer() function when transferring Ether to an external address, because transfer() will only send 2300 gas to be called, which will not be enough to call another contract, use transfer() to rewrite the original contract’s withdraw() as follows
02 Modify state variables first
This approach is to ensure that state variables are modified before the transfer operation, i.e. the checks-effects-interactions model recommended by Solidity.
03 Using Mutual Exclusion Locks
A mutex lock is a state variable that is added to lock a contract during code execution to prevent reentry attacks.
04 Using the official OpenZeppelin library
The official OpenZeppelin library contains a security contract specifically for reentry attacks: the
Posted by:CoinYuppie，Reprinted with attribution to:https://coinyuppie.com/in-depth-understanding-of-re-entry-attack-vulnerabilities/
Coinyuppie is an open information publishing platform, all information provided is not related to the views and positions of coinyuppie, and does not constitute any investment and financial advice. Users are expected to carefully screen and prevent risks.