Exploring DeFi's Most Famous Hack

Amitai Ruskin October 18,2021

Ethereum was the first blockchain to introduce smart contract capabilities. Developers use smart contract technology to upload immutable scripts of code to the blockchain, enabling the creation of innovative DeFi platforms. Unfortunately, this capability introduced an inherent weakness that attackers have exploited to steal more than $1.3B from DeFi platforms. One of the first hacks in Ethereum's history and the most famous was against The DAO where it fell victim to a reentrancy attack. In this post, we are going to explore what reentrancy is and how attackers use it to steal funds. Finally, we will look specifically into the attack against The DAO, and learn how platforms can protect themselves from this type of hack.

What is Reentrancy?

Reentrancy is a phrase that originates from the world of general computing and isn't strictly unique to crypto. Reentrancy is a case where program execution is interrupted mid-operation, re-initiated, and both runs complete without any errors in execution. In DeFi, attackers can take advantage of this bug to steal funds that are held in smart contracts. This weakness is considered very dangerous since attackers will often execute this attack multiple times, draining all the funds.

Reentrancy Attacks

A reentrancy attack can occur when a victim contract calls an external contract before all of the state changes have been executed. In this case, a malicious contract can use this external call to make unexpected changes to the victim contract allowing the attacker to steal funds.

An example of a vulnerable smart contract:

contract Victim{ mapping(address => uint) balances; function withdraw() public { uint balance = balances[msg.sender]; require(balance > 0); (bool sent, ) = msg.sender.call{value: balance}(""); require(sent, "Failed to send Ether"); balances[msg.sender] = 0; } }

To inexperienced Solidity developers, this code snippet looks fine. In fact, however, this is not the case. This smart contract is vulnerable to a reentrancy attack because the withdraw function uses the call method to send funds to an external contract before changing the balance of the external contract.

All an attacker needs to exploit this function is to get some amount of balance mapped to their smart contract address and create a fallback function that recalls the same withdraw function.

The solidity fallback function is executed when none of the other functions match the function identifier or no data was provided with the function call. In the context of a reentrancy attack, the fallback function is ideal since it is triggered whenever a contract receives Ether without any other data associated with the transaction. Each contract can be assigned with only one fallback function.

The steps for the attack are as follows:

Step 1: The attacker creates a malicious smart contract to call the victim contract’s withdraw function.

Step 2: The malicious smart contract calls the victim's withdraw function, sending funds to the malicious contract.

Step 3: Upon receiving funds from the victim, a fallback function in the malicious smart contract will be triggered, calling the withdraw function a second time to receive more funds. The victim's smart contract does not have the opportunity to reduce the attacker's balance since the program flow is interrupted mid-run before reducing the attacker's balance.

Step 4: This process is looped indefinitely until the victim's funds are completely drained.

An example of an attackers contract:

contract Attacker{ constructor(address _victimAddress) { victim = Victim(_victimAddress); } function attack() external payable { victim.deposit{value: 1 ether}(); victim.withdraw(); } // Fallback is called when Victim sends Ether to this contract. fallback() external payable { if (address(victim).balance >= 1 ether) { victim.withdraw(); } } }

The DAO Hack

The DAO hack is one of the most significant events in crypto's history.

The DAO logo The DAO logo

What was The DAO?

In 2016, the Ethereum community initiated The DAO. The DAO is the name of the first DAO (Decentralized Autonomous Organization). DAOs are internet-native organizations collectively owned and managed by their members (for more information regarding DAOs see DeFi Guide in the DeFi Series).

The DAO was created to act as a virtual venture capital fund governed by its members. The idea was to invest The DAO’s treasury to fund projects being built on Ethereum. Each proposed project was inspected by select members of The DAO. Projects that passed the inspection would then be voted on by The DAO’s members to decide whether or not to fund the project. A project that received 20% or more of The DAO’s token supply would automatically be funded from The DAO’s treasury.

A unique feature that The DAO incorporated to protect the interest of the minority members was to enable The DAO to split and create a child DAO belonging to the minority. The minority’s funds would automatically be transferred to the child DAO. The process of splitting to create a new child DAO was hardcoded into The DAO’s code. It is important to note that there was a 41 day delay period from the child DAO’s creation until it could send funds out of the DAO to fund new proposals.

The DAO was very popular, raising over 12.7M ETH worth $150M (at the time) from more than 11,000 DAO members making it the largest crowdfunding in history.

The Hack

The DAO seemed very promising at the time. However, only three weeks after The DAO launched and before it officially started to operate, The DAO got hacked due to a weakness in the splitting process. This occurred when the split function was called, it first sent ETH to the child DAO and only afterward updated the balance of the user who called the split function, making it vulnerable to reentrancy. The attacker exploited this weakness recursively calling the split function multiple times before the program had a chance to update the attacker's balance. (see this post for a more detailed description of the attack).

The attack was complex and involved exploiting vulnerabilities in several different functions. One of the main weaknesses that was exploited is visible in the code snippet below. It is easy to see that the withdrawRewardFor function is called before the balance is updated.

function splitDAO( uint \_proposalID, address \_newCurator ) noEther onlyTokenholders returns (bool \_success) { ... uint fundsToBeMoved = (balances[msg.sender] \* p.splitData[0].splitBalance) / p.splitData[0].totalSupply; //**\*\***Since the balance is never updated the attacker can pass this modifier several times **\*\*** if (p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender) == false) throw; ... // Burn DAO Tokens //**\*\***Funds are transferred before the balance is updated\***\*\*\*\*\*** Transfer(msg.sender, 0, balances[msg.sender]); withdrawRewardFor(msg.sender); // be nice, and get his rewards // **\*\*\*\***Only now after the funds are transferred is the balance updated**\*\*\*\*** totalSupply -= balances[msg.sender]; paidOut[msg.sender] = 0; return true; }

The result of the attack was disastrous. The attacker managed to move 3.6 million Ether to a child DAO that was under his control. As mentioned before, the funds in the child DAO were locked and only after 41 days could the attacker transfer them to his personal wallet. During this waiting period, there was a huge debate regarding how to respond. Three options were debated, some in the Ethereum community believed that the network should soft fork. Others were lobbying for a hard fork, and some were calling not to do anything (for more information regarding this debate click here). On July 20th, A hard fork proposal was voted on and approved by a supermajority of 89% calling for a hard fork. The Ethereum network forked creating the ETH blockchain, which rolled back the Ethereum state to its state before the attack, making it as if the hack never happened. The original Ethereum blockchain still including the attack became ETC (Ethereum Classic) that still exists today.

How can dApps Prevent Reentrancy?

There are a few best practices developers can implement to prevent reentrancy attacks.

Ensure all state changes happen before calling external contracts

The most reliable method of protecting against reentrancy attacks is by using the checks-effects-interactions design. This design defines the order in which developers should structure their functions.

Step 1: The function should first perform all of the needed checks, ensuring that all the requirements are met. For example, is the caller authorized to call this function? Does he have enough funds?

Step 2: Once all checks are done, the function should settle all changes to the contract state.

Step 3: Only after all state changes are resolved should the function interact with other contracts. By calling external functions last, even if an attacker makes a recursive call to the original function, the state of the contract can not be manipulated.

For more information see Solidity’s documentation and ConsenSys’s Best Practices.

Reentrancy Guard

Not all contracts can effectively use the checks-effects-interaction pattern. Another solution available is a mutex that locks the current state and ensures that only the owner can change the state.

Applying this modifier to a function will render it “non-reentrant”. Any attempts to re-enter this function while lock is true will be reverted. By using this lock, an attacker can no longer exploit the withdraw function with a recursive call.

Example of a simple implementation of a mutex:

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

In this example, the code without the modifier is reentrant. Applying the mutex using the lock boolean makes it resistant to reentrancy attacks.

A common mutex application is the ReentrancyGuard.sol modifier from OpenZeppelin. It includes nonReentrant modifier, which can be used throughout the contract.

Mutex is helpful, but it still has its limitations. The mutex modifier can limit platform capabilities in a multi-contract architecture, which is often found in DeFi where each platform consists of several different smart contracts. In addition, this modifier can severely limit composability with other DeFi platforms.

Important to note

In the past, security measures recommended when possible to use the send and transfer method, rather than the call method. This recommendation was based on the assumption that the amount of gas per operation stays constant. Since fallback functions triggered by the send and transfer methods are limited to 2300 gas, it was thought that there would be insufficient gas for attackers to execute a reentrancy attack. However, the Istanbul upgrade affected gas prices for certain operations, upending the assumption of fixed gas per operation. This new reality means that developers can no longer assume that limited gas makes a platform resistant to reentrancy and should use preventive measures previously discussed. For more information, see stop using transfer now.

Conclusion

If you made it until here, then good for you! You now have a good understanding of what reentrancy is and how it can be used to steal funds from platforms. We also looked into the most famous reentrancy attack in crypto that resulted in creating the Ethereum we know today.

We also learned about the main methods that, if implemented correctly, can prevent reentrancy attacks.

Developers need to emphasize security during product development. Simple precautions can go a long way. It is also essential for platforms to undergo extensive testing by auditors to ensure there are no illegal entry points.

DeFi today is risky and investors should do extensive due diligence before investing. It is not enough to blindly rely on the platform being audited (I recommend reading Andre’s post calling for investors to do more research.) Instead, investors should actually read the audit, look into the team’s background, and use risk analysis tools.

,

You Might Also Like:

Permit Messages and Permit 2: Enhancing DeFi Security Amidst Emerging Risks

February 6,2024/5 min read

Explore the cutting edge of DeFi security with "Permit Messages and Permit 2: Enhancing DeFi Security Amidst Emerging Risks." This blog post delves into the innovative permit message technology and its evolution with Permit 2, highlighting their role in secure and efficient DeFi transactions. Despite their advancements, we uncover potential risks, including phishing attacks and smart contract vulnerabilities. Learn from past incidents and gain practical tips to protect your digital assets. This concise guide equips you with the knowledge to navigate the complex DeFi landscape safely.

Read More