Eggstravaganza

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

Unprotected Authorization Pattern in EggstravaganzaNFT Contract

Summary

This report details a security vulnerability in the EggstravaganzaNFT contract's authorization pattern, which could potentially allow unauthorized NFT minting through manipulation of the game contract address. The severity of this vulnerability varies depending on the deployment context, with decentralized deployments facing higher risk due to the potential for token dilution and loss of control.

Vulnerability Details

Location

The vulnerability exists in the setGameContract function and its interaction with the mintEgg function

Root Cause

The vulnerability stems from two primary issues:

  1. The setGameContract function allows any address to be set as the game contract without proper validation or security controls

  2. The mintEgg function trusts the game contract implicitly, allowing it to mint unlimited NFTs without restrictions

Impact

The impact varies significantly based on deployment context:

Decentralized Deployment (High Risk):

  • Potential for unlimited NFT minting

  • Risk of token dilution

  • Loss of control over token supply

  • Financial impact through value manipulation

Centralized Deployment (Medium Risk):

  • Governance risk through key compromise

  • Potential for unauthorized minting

  • Limited by centralized trust model

Tools Used

  • Foundry for testing and verification

  • Solidity compiler for contract analysis

  • Static analysis tools for vulnerability identification

Proof of Concept (PoC)

The vulnerability can be demonstrated through the following sequence:

  1. An attacker gains control of the owner key

  2. The attacker calls setGameContract with a malicious contract address

  3. The malicious contract repeatedly calls mintEgg to create unlimited NFTs

  4. The total supply increases without restriction

Recommended Mitigation

Based on deployment context, implement one of the following solutions:

For Centralized Deployments:

contract EggstravaganzaNFT is ERC721, Ownable {
struct GameContractProposal {
address proposedAddress;
uint256 proposedTimestamp;
bool active;
}
mapping(address => GameContractProposal) public pendingProposals;
address public gameContract;
uint256 public constant PROPOSAL_TIMELOCK = 24 hours;
event GameContractProposalCreated(address indexed proposer, address indexed proposedAddress);
event GameContractUpdated(address indexed oldAddress, address indexed newAddress);
function proposeGameContract(address _newGameContract) external onlyOwner {
require(_newGameContract != address(0), "Invalid game contract");
require(pendingProposals[_newGameContract].proposedTimestamp == 0,
"Existing proposal found");
pendingProposals[_newGameContract] = GameContractProposal({
proposedAddress: _newGameContract,
proposedTimestamp: block.timestamp,
active: true
});
emit GameContractProposalCreated(msg.sender, _newGameContract);
}
function confirmGameContract(address _newGameContract) external onlyOwner {
GameContractProposal storage proposal = pendingProposals[_newGameContract];
require(proposal.active, "No active proposal found");
require(block.timestamp >= proposal.proposedTimestamp + PROPOSAL_TIMELOCK,
"Timelock period not expired");
address oldGameContract = gameContract;
gameContract = _newGameContract;
emit GameContractUpdated(oldGameContract, _newGameContract);
}
}

For Decentralized Deployments:

contract EggstravaganzaNFT is ERC721 {
struct Signature {
address signer;
uint256 timestamp;
}
mapping(bytes32 => mapping(address => Signature)) public signatures;
address[] public owners;
uint256 public requiredSignatures;
address public gameContract;
modifier onlyOwner() {
bool isOwner = false;
for(uint i = 0; i < owners.length; i++) {
if(owners[i] == msg.sender) {
isOwner = true;
break;
}
}
require(isOwner, "Only owners can call this");
}
function setGameContract(address _newGameContract) external onlyOwner {
bytes32 hash = keccak256(abi.encodePacked("SET_GAME_CONTRACT", _newGameContract));
signatures[hash][msg.sender] = Signature({
signer: msg.sender,
timestamp: block.timestamp
});
uint256 sigCount;
for(uint i = 0; i < owners.length; i++) {
if(signatures[hash][owners[i]].timestamp > 0) {
sigCount++;
}
}
require(sigCount >= requiredSignatures, "Insufficient signatures");
gameContract = _newGameContract;
}
}

The recommended mitigation approach depends on your deployment context:

  • For centralized deployments, implement the two-step proposal pattern with timelock

  • For decentralized deployments, implement the multi-signature governance pattern

  • Consider implementing both patterns for maximum security in hybrid deployments

Updates

Lead Judging Commences

m3dython Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

Trusted Owner

Owner is trusted and is not expected to interact in ways that would compromise security

Support

FAQs

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