Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: high
Likelihood: low
Invalid

[M-2] Malicious WETH address in Snow can allow reentrancy attacks

[M-2] Malicious WETH address in Snow can allow reentrancy attacks

Description

  • The Snow::buySnow() calls external functions from i_weth contract. The safeTransferFrom() function is a function from OpenZeppelin's SafeErc20 interface and internally it calls the transferFrom() function of the ERC20 token contract.

  • A malicious contract inheriting from ERC20 contract but overriding the transferFrom() function can contain attacker's own logic and cause reentrancy vulnerability

@> i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));

Risk

Likelihood:

  • Deployer of contract puts malicious contract address either deliberately or due to human errors

Impact:

  • User can call buySnow() once with the amount needed for buying, but then the contract will reenter the contract and transfer as many tokens as needed

Proof of Concept

Add the following testcase to the Snow.t.sol test suite:

function test_buyReentrant() public {
vm.startPrank(ashley);
TestWETH test_weth = new TestWETH();
uint256 fee = 1;
Snow newSnow = new Snow(address(test_weth), fee, collector);
newSnow.buySnow(10);
uint256 res = newSnow.balanceOf(ashley);
assertEq(res, 100);
}

Also add the following contract to the import list. This is the malicious reentrant contract:

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity 0.8.24;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Snow} from "../../src/Snow.sol";
contract TestWETH is ERC20 {
uint256 public counter;
address public immutable owner;
address public snow_addr;
constructor() ERC20("MockWETH", "mWETH") {
owner = msg.sender;
}
function transferFrom(address, address, uint256) public override returns (bool) {
Snow snow = Snow(msg.sender);
counter++;
if (counter < 10) {
snow.buySnow(10);
}
snow.transfer(owner, snow.balanceOf(address(this)));
return true;
}
}

Recommended Mitigation

Add reentrancy guards to the function. OpenZeppelin's reentrancy guard works by locking the function once it has been called, till the time the call has been fully executed. This means that once entered, nobody will be able to reenter the contract due to the locking mechanism

+import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
-contract Snow is ERC20, Ownable {
+contract Snow is ERC20, Ownable, ReentrancyGuard {
.
.
.
- function buySnow(uint256 amount) external payable canFarmSnow {
+ function buySnow(uint256 amount) external payable canFarmSnow nonReentrant {
Updates

Lead Judging Commences

yeahchibyke Lead Judge
5 months ago
yeahchibyke Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.