Weather Witness

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

Missing Access Control in `fulfillMintRequest` Allows NFT Theft

Root + Impact

Description

  • Normal Behavior: After a Chainlink Functions request has been fulfilled, only the user who initiated requestMintWeatherNFT should be able to call fulfillMintRequest and receive the minted NFT.

  • Issue: The fulfillMintRequest function is publicly callable without any check on msg.sender, allowing an attacker to front‑run the rightful user and mint the NFT to themselves.

function fulfillMintRequest(bytes32 requestId) external {
bytes memory response = s_funcReqIdToMintFunctionReqResponse[requestId].response;
bytes memory err = s_funcReqIdToMintFunctionReqResponse[requestId].err;
require(response.length > 0 || err.length > 0, WeatherNft__Unauthorized());
if (response.length == 0 || err.length > 0) {
return;
}
UserMintRequest memory _userMintRequest = s_funcReqIdToUserMintReq[requestId];
uint8 weather = abi.decode(response, (uint8));
uint256 tokenId = s_tokenCounter;
s_tokenCounter++;
emit WeatherNFTMinted(
requestId,
msg.sender, // @> mints to caller, not original user
);
_mint(msg.sender, tokenId); // @> no check on msg.sender
s_tokenIdToWeather[tokenId] = Weather(weather);
}

Risk

Likelihood:

  • Monitoring of WeatherNFTMintRequestSent events and mempool transactions is trivial for automated bots.

  • No on‑chain guard exists, so every fulfilled request is exploitable until the rightful user mints.

Impact:

  • Attackers can permanently steal newly minted NFTs, causing direct financial loss to the user.

  • Protocol reputation suffers due to loss of user trust and potential legal liabilities.

Proof of Concept

  1. User calls requestMintWeatherNFT(...) → emits WeatherNFTMintRequestSent(requestId).

  2. Attacker observes requestId off‑chain.

  3. Attacker calls attack(requestId) in front of the user’s transaction.

  4. NFT is minted to the attacker's EOA instead of the legitimate user.

Recommended Mitigation

function fulfillMintRequest(bytes32 requestId) external {
+ UserMintRequest memory req = s_funcReqIdToUserMintReq[requestId];
+ require(msg.sender == req.user, "Caller not original requester");
bytes memory response = s_funcReqIdToMintFunctionReqResponse[requestId].response;
bytes memory err = s_funcReqIdToMintFunctionReqResponse[requestId].err;
require(response.length > 0 || err.length > 0, WeatherNft__Unauthorized());
if (response.length == 0 || err.length > 0) {
return;
}
UserMintRequest memory _userMintRequest = s_funcReqIdToUserMintReq[
requestId
];
uint8 weather = abi.decode(response, (uint8));
uint256 tokenId = s_tokenCounter;
s_tokenCounter++;
emit WeatherNFTMinted(
requestId,
- msg.sender,
+ req.user,
Weather(weather)
);
- _mint(msg.sender, tokenId);
+ _mint(req.user, tokenId);
+ // Prevent reuse of the same requestId
s_tokenIdToWeather[tokenId] = Weather(weather);
}

The mitigation ensures that only the original requester can mint their NFT by:

  1. Access Control: Verifies that the caller (msg.sender) is the original requester before fulfilling the request.

  2. Proper Ownership Assignment: Explicitly mints the NFT to the original requester’s address, not the caller.

Updates

Appeal created

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