How to Escape Smart Contracts from The Clutch of Reentrancy Attacks?


0
Read time: 6 minutes

Taking a closer look at the biggest cryptocurrency hacks and the eye-popping numbers lost because of them, they must have been deeply rooted in coding flaws.

One common occurrence of such security vulnerabilities is reentrancy attacks. However, the devastating effects of mishandling reentrancy may not be as simple as initiating the attack itself.

Despite being a well-known and well-known problem, the emergence of reentrancy bugs in smart contracts is always inevitable.

How often have reentrancy vulnerabilities been exploited by hackers in the last few years? How does it work? How to prevent smart contracts from losing funds to reentrant bugs? Find answers to these questions in this blog.

So in the meantime, let’s brush up on maximal reentrancy attacks in memory.

Some of the most notorious real-time reentrancy hacks

The reentrant attacks that had the most devastating impact on the project ended up doing one or both of these two things.

  • Completely drain Ether from smart contracts
  • Hacker breaking into smart contract code

We can now observe several cases of reentrant attacks and their effects.

June 2016: DAO attack – $3.54 million or $150 million of Ether

April 2020: Uniswap/Lendf.Me Hacked – $25 Million

May 2021: BurgerSwap Hacked – $7.2 Million

August 2021: CREAM FINANCE HACKED – $18.8 MILLION

March 2022: Ola Finance – $3.6 million

July 2022: OMNI Protocol – $1.43 million

Clearly, reentrant attacks have never gone out of fashion. Get a deeper insight into it in the following section.

Overview of reentrant attacks

Like the origin of the name “re-entrant”, it means “to re-enter many times”. A reentrant attack involves her two contracts, a victim contract and an attacker contract.

The attacker contract exploits a reentrancy vulnerability in the victim contract. To achieve that, we use the withdraw function.

The attacker’s contract invokes a withdrawal function to drain funds from the victim’s contract by making repeated calls before the victim’s contract balance is updated. Victim contract checks balance and sends funds to update balance.

However, within the timeframe to transfer funds and update the contract balance, the attacker contract makes continuous calls to withdraw funds. As a result, the victim contract’s balance is not updated until the attacker contract has used up all its funds.

The seriousness and cost of reentrancy exploits warn of the urgent need to act Smart contract audit It eliminates the possibility of overlooking such errors.

Illustration of reentrant attack

Let us understand the concept of reentrant attacks from the simplified diagram below.

There are two contracts here: the vulnerable contract and the hacker’s contract.

Hacker contracts call to withdraw from vulnerable contracts. Upon receiving a call, Vulnerable Contract checks the funds in the hacker contract and transfers the funds to the hacker.

Hackers receive funds and implement fallback functionality. This will call the vulnerable contract again even before the balance has been updated on the vulnerable contract. By repeating the same operation in this way, the hacker completely withdraws the funds from the vulnerable contract.

re-entry attack

Characteristics of fallback functions used by attackers

  • They can be called externally.i.e. cannot be called from within the contract they were written in
  • unnamed function
  • the fallback function does not contain arbitrary logic
  • A fallback is triggered when ETH is sent to the smart contract it contains and the receive() function is not declared.

Analysis of Reentrant Attacks from a Technical Perspective

Let’s take a sample contract to understand how a reentrant attack can occur.

malicious contract

contract Attack {
    DepositFunds public depositFunds;

    constructor(address _depositFundsAddress) {
        depositFunds = DepositFunds(_depositFundsAddress);
    }

    // Fallback is called when DepositFunds sends Ether to this contract.
    fallback() external payable {
        if (address(depositFunds).balance >= 1 ether) {
            depositFunds.withdraw();
        }
    }

    function attack() external payable {
        require(msg.value >= 1 ether);
        depositFunds.deposit{value: 1 ether}();
        depositFunds.withdraw();
    }


}

This is an attacker contract where the attacker deposits 2ETH. An attacker calls the withdraw function on a vulnerable contract. Receiving funds from a vulnerable contract will trigger the fallback function.

The fallback then executes the withdrawal function, draining funds from the vulnerable contract. This cycle continues from a weak contract until the funds are completely depleted.

weak contract

contract DepositFunds {
    mapping(address => uint) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw() public {
        uint bal = balances[msg.sender];
        require(bal > 0);

        (bool sent, ) = msg.sender.call{value: bal}("");
        require(sent, "Failed to send Ether");

        balances[msg.sender] = 0;
    }


}

The vulnerable contract is 30ETH. Here, the withdrawal() function sends the requested amount to the attacker. Tokens are repeatedly transferred to the attacker as the balance is not updated.

Types of Reentrant Attacks

  • Single function reentrancy
function withdraw() external {
   uint256 amount = balances[msg.sender];
   require(msg.sender.call.value(amount)());
   balances[msg.sender] = 0;
}

After msg.sender.call.value(amount)() transfers funds, the attacker’s contract fallback function calls withdraw() again before the balance runs out.[msg.sender] = 0 is updated.

  • Reentrancy between functions
function transfer(address to, uint amount) external {
   if (balances[msg.sender] >= amount) {
       balances[to] += amount;
       balances[msg.sender] -= amount;
   }
}
function withdraw() external {
   uint256 amount = balances[msg.sender];
   require(msg.sender.call.value(amount)());
   balances[msg.sender] = 0;
}

Reentrancy between functions is much more complicated to identify. The difference here is that the fallback function calls transfer . This calls withdraw, unlike single-function reentrancy.

Defense against reentrant attacks

Check-effect-interaction pattern: The Checks-effects-interactions pattern helps structure functions.

The program should be coded to check the condition first. After the check has passed, the effect on the state of the contract should be resolved. Then you can call the external function.

function withdraw() external {
   uint256 amount = balances[msg.sender];
   balances[msg.sender] = 0;
   require(msg.sender.call.value(amount)());
}

The code rewritten here follows the checks-effects-interactions pattern. Here we zero the balance before making the external call.

Using modifiers

The modifier noReentrant applied to a function ensures that there are no reentrant calls.

contract ReEntrancyGuard {
    bool internal locked;

    modifier noReentrant() {
        require(!locked, "No re-entrancy");
        locked = true;
        _;
        locked = false;
    }
}

Eventually

The most effective step is to have your smart contracts audited by a leading security company like QuillAudits. Auditors carefully monitor the structure of the code and check how fallback functions are performed. Based on the patterns investigated, it recommends restructuring the code if vulnerable behavior is suspected.

The safety of funds is ensured just before they fall victim to loss.

Frequently Asked Questions

What is a reentrant attack?

A reentrancy attack occurs when a function in a vulnerable contract calls an untrusted contract. An untrusted contract becomes an attacker’s contract that recursively calls vulnerable contracts until the funds are completely depleted.

What is Reentrancy?

Retyping, also known as retyping, means interrupting code execution and starting the process over.

What is reentrant guard?

Reentrancy guards use modifiers that prevent a function from being called repeatedly. Read the blog above to find examples of reentrancy guards.

What are some attacks against smart contracts?

Smart contracts are subject to numerous vulnerabilities such as reentrancy, timestamp dependencies, arithmetic overflows, and DoS attacks. Auditing to ensure there are no bugs that break the logic of the contract is therefore mandatory.

697 view


Like it? Share with your friends!

0
Conor the Tech Veteran
He previously spent 6 years publishing research on tech stocks, and believes in using a combination of fundamental, technical, and quantitative analysis. Prior to a career in tech stocks journalism he was a technology and semiconductor analyst with a research team.

0 Comments

Your email address will not be published. Required fields are marked *