Exploring Smart Contract Vulnerabilities through Ethernaut (OpenZeppelin) — Part 2
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
- Block Timestamp Manipulation
- Integer Overflow / Underflow
- Denial-of-Service (DoS)
- Reentrancy Attack
- Storage Collision
- 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 thecall
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:
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:
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.
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.
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.
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:
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:
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.
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.
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.
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:
If the exploit code is executed, it can drain all the funds in the Vault contract as follows
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.
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 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.
Reference
- https://ethernaut.openzeppelin.com/
- https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies#unstructured-storage-proxies
- https://medium.com/chainlight/en-audius-variant-scanner-scanning-storage-collisions-between-ethereum-contracts-4d2d64b77566
- https://medium.com/coinmonks/smart-contract-security-block-timestamp-manipulation-baec1b95c921
- https://medium.com/@xoredtwice/defi-vulnerabilities-fd6b5ffd449a
- https://medium.com/@solidity101/100daysofsolidity-073-understanding-denial-of-service-attacks-in-solidity-smart-contracts-a790de3d0943
- https://consensys.github.io/smart-contract-best-practices/development-recommendations/solidity-specific/timestamp-dependence/
✍ Medium: Park DoYeon | 🕊️ Twitter: p6rkdoye0n | 📧Mail: parkttule0305