Exploring Smart Contract Vulnerabilities through Ethernaut (OpenZeppelin) — Part 2

p6rkdoye0n
Park DoYeon
Published in
8 min readJun 9, 2024

The author strictly opposes black hat activities and solely advocates for white hat approaches. This article is intended for those who hope to engage in bug hunting on bug bounty platforms such as Immunefi or HackerOne from an offensive security perspective, or aspire to become smart contract auditors. Please note that any damages resulting from the exploitation of the mentioned vulnerabilities are the sole responsibility of the perpetrator, both legally and in terms of compensation.

summary

Please note that this article is Part 2 of “Exploring Smart Contract Vulnerabilities through Ethernaut (OpenZeppelin) — Part 1”.

Attack Vector

  1. Block Timestamp Manipulation
  2. Integer Overflow / Underflow
  3. Denial-of-Service (DoS)
  4. Reentrancy Attack
  5. Storage Collision
  6. Insufficient Validation

4. Reentrancy Attack

A Reentrancy Attack is a vulnerability that occurs when a smart contract’s function is called, and during its execution, it re-enters itself through another function call.

This attack exploits the lack of gas limitations in the call function and the chaining of fallback functions when the contract's internal functions are called. It is a severe vulnerability capable of draining funds, and is classified as Critical in the Common Vulnerability Scoring System (CVSS).

Example of Vulnerable Code

Below is a smart contract code that contains a Reentrancy attack vector:

This smart contract is a simple Vault contract, and its components are as follows:

  • deposit: When this function is called, it adds the amount of Ether sent with the function call to the balance of the calling EOA (Externally Owned Account) in the mapping.
  • withdraw: When this function is called, it checks if the calling EOA has sufficient balance. If sufficient, it transfers the requested amount to the EOA using the call function. After the call is completed, it deducts the withdrawn amount from the balance.

At first glance, this code might seem to have no vulnerabilities, but the smart contract contains a Reentrancy Attack vector.

When the call function is used to send Ether or call the contract without any specific function signature in the call data (an empty byte state), the contract can shift the transaction flow to the receive function or fallback function within the contract during the execution of the call function.

This can be illustrated in the following diagram:

Reentrancy Attack Diagram

When the withdraw function is called for the first time, the call function inside the Vault contract will send a request to the hacker's contract address (CA). This, in turn, triggers the receive function within the hacker's CA, allowing the hacker to re-enter the withdraw function and control the transaction flow. By continuously repeating this process, the hacker can eventually drain all the funds from the Vault.

Below is the exploit code that reflects the above attack scenario:

Attack Transaction

When the exploit code is executed, you can observe an attack transaction that continuously re-enters the withdraw function.

This attack ultimately drains all the funds from the Vault contract.

Attack Result

Real-World Case

Grim Finance Exploit (2021): The attacker used a flash loan to borrow funds and then executed a reentrancy attack. With each reentry step, the amount of liquidity secured tokens increased. Finally, the attacker repaid the flash loan and drained approximately $30 million.

Vulnerable Code

Mitigation

  • Using noreentrant modifier: This is the most commonly used method in the real world. When the modifier is executed, it controls a globally defined reentrant state value to prevent reentrancy attacks.
  • Adjusting the Order of Call Functions: Even with a modifier in place to prevent single reentrancy attacks, there are still several cases where reentrancy attacks can be executed (not covered here). The best method is to place the reentrancy attack vector (the function that triggers the callback) at the end of the function. This way, even if a reentrancy attack is triggered, it does not provide any advantage to the attacker.

5. Storage Collision

Storage Collision is a vulnerability that occurs when two or more contracts use different storage layouts and a delegatecall is made, causing the storage slots to collide.

reference: https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies#unstructured-storage-proxies

This vulnerability can lead to various negative consequences such as unexpected behavior, draining of funds, manipulation of the proxy admin, and more.

Example of Vulnerable Code

Below is an example of code that contains a Storage Collision vulnerability:

The following code implements the logic of a Proxy contract. It is designed to store data in the appropriate storage slots of the Storage Token contract using the logic of the Token contract via the execution function.

Due to the number of functions, detailed explanations of each are omitted.

However, within the Proxy contract, the admin’s address is stored in an incorrect storage slot. This slot coincides with the totalBalance variable in the Token contract, leading to a Storage Collision vulnerability.

The value of totalBalance is set when the deposit and withdraw functions are called. By passing parameters to the deposit function in a specific manner, the admin's address can be manipulated arbitrarily.

By exploiting the Storage Collision vulnerability to manipulate the admin address and changing the implementation address to a malicious contract, an attacker can drain all funds from the Proxy contract.

Below is the exploit code that reflects this attack scenario:

Storage Collision Result

By successfully using Storage Collision to change the admin address, the logic address can be modified to a malicious contract, allowing for the final drain of the funds. Below is the example of the malicious contract and how the exploit could be executed:

Attack Result

Real-World Case

Audius Governance Exploit (2022): On July 24, 2022, due to bugs in Audius’s governance, staking, and delegation systems, a governance proposal was passed to transfer $1.8M worth of $AUDIO tokens to the attacker’s wallet. The attacker successfully leveraged a Storage Collision vulnerability to transfer assets to their own wallet.

reference: https://blog.audius.co/article/audius-governance-takeover-post-mortem-7-23-22

Mitigation

  • Using unstructured storage: Instead of storing the address in the first storage slot of the _implementation proxy, choose a pseudo-random slot. This slot should be sufficiently random to minimize the likelihood of a logic contract declaring a variable in the same slot.
reference: https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies#unstructured-storage-proxies

Below is an example of using the EIP-1967 standard for implementing pseudo-random slot logic:

6. Insufficient Validation

Insufficient Validation is a type of logical bug that occurs when there is inadequate validation in a smart contract.

While I did not initially plan to cover logical bugs in this article, “Insufficient Validation” is a critical attack vector that always requires careful attention during real-world research. Therefore, I would like to share some insights related to “External Call Function Endpoints.”

External Call Function Endpoints

Insufficient Validation vulnerabilities typically occur when a function receives an address or contract as a parameter and makes external calls to these endpoints without proper validation.

external call function endpoints

When a function’s parameter is an address or contract, focusing on these External Call Function Endpoints during analysis makes it easier to identify and trigger Insufficient Validation vectors.

Example of Vulnerable Code

Below is an example of code containing an Insufficient Validation vulnerability:

The Vault contract is a smart contract designed to create and manage “MTN” tokens. The components of the Vault contract are as follows:

  • deposit: Receives the address of the token as a parameter and mints MTN tokens equivalent to the amount sent.
  • withdraw: Receives the address of the token and the amount to withdraw as parameters. It checks the balance and transfers the requested amount to the caller if sufficient funds are available.

However, this contract has an Insufficient Validation vulnerability. If a malicious contract is passed as the token address parameter, it can control the transaction flow.

Below is an example of the exploit code that reflects this attack scenario:

Attack Transaction

If the exploit code is executed, it can drain all the funds in the Vault contract as follows

Attack Result

Real-World Case

OlympusDao Exploit (2022): On October 21, 2022, the BondFixedExpiryTeller contract managed by OlympusDao was exploited by an attacker, resulting in the theft of $292K (30,500 OHM). This attack occurred because the redeem function did not apply any validation to the input, allowing the attacker to exploit this vulnerability.

PatchShield X

Mitigation

  • Sufficient Validation: This vulnerability typically arises due to inadequate validation of inputs. It is crucial to consider as many cases as possible and handle exceptions properly to mitigate this risk.

Conclusion

When engaging in bug hunting on platforms like Immunefi or HackerOne or performing audits in real-world scenarios, it is essential to approach vulnerabilities from an offensive security perspective, triggering them and creating a Proof-of-Concept (PoC).

OpenZeppelin’s Ethernaut provides an environment to study and research security vulnerabilities that occur in smart contracts through various cases. This has been beneficial for those aspiring to become Smart Contract Auditors or Bug Hunters, aiding their growth in this field.

Ethernaut

Ethernaut offers additional vulnerabilities and experiences not covered in this article. For those aspiring to become Smart Contract Auditors or Bug Hunters, I highly recommend completing the Ethernaut challenges.

Additionally, the vulnerable code and PoC examples used in this article are available on my GitHub repository. Please visit the repository for further reference.

Thank you.

✍ Medium: Park DoYeon | 🕊️ Twitter: p6rkdoye0n | 📧Mail: parkttule0305

--

--