Weather Witness

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

Missing Access Control in fulfillMintRequest()

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • Explain the specific issue or problem in one or more sentences

// Root cause in the codebase with @> marks to highlight the relevant section

Risk

Likelihood:

  • Reason 1: This will occur when a user initiates a mint request, and the Chainlink oracle returns a response to the contract, placing the result in a public, observable state variable. At this point, the function fulfillMintRequest(requestId) becomes callable by anyone.


  • Reason 2: This will occur when another user monitors the mempool or blockchain events, identifies that a specific requestId is ready to be fulfilled, and submits a transaction with a higher gas price to call fulfillMintRequest(requestId) before the original requester. This results in the NFT being minted to the attacker rather than the paying user.

Impact:

  • Impact 1: A malicious actor can front-run the rightful user’s mint finalization and receive the NFT that the user paid for, leading to a direct asset theft and financial loss for the original requester.

  • Impact 2: The original requester has no way to recover their funds or NFT, resulting in a broken user experience and potential trust loss in the protocol. This could also expose the system to large-scale exploitation by bots.

Proof of Concept

//Alice initiates a mint:
weatherNft.requestMintWeatherNFT("10001", "US", false, 0, 0);
//Chainlink fulfills with a valid weather response:
// Simulated by Chainlink callback:
bytes32 requestId = ...;
s_funcReqIdToMintFunctionReqResponse[requestId] = MintFunctionReqResponse({
response: abi.encode(uint8(2)), // e.g. RAINY
err: ""
});
//Bob sees requestId is ready and front-runs:
weatherNft.fulfillMintRequest(requestId); // Bob calls before Alice
//Result:
// Inside fulfillMintRequest()
_mint(msg.sender, tokenId); // msg.sender = Bob
//Alice calls later:
weatherNft.fulfillMintRequest(requestId); // But NFT already minted

Recommended Mitigation

// No restriction on who can call
UserMintRequest memory _userMintRequest = s_funcReqIdToUserMintReq[requestId];
// No require check
_mint(msg.sender, tokenId);
Add this:
UserMintRequest memory _userMintRequest = s_funcReqIdToUserMintReq[requestId];
require(msg.sender == _userMintRequest.user, "Only requester can fulfill");
_mint(msg.sender, tokenId);
Updates

Appeal created

bube Lead Judge 23 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.