Weather Witness

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

Malicious user can mint on behalf of the user who requested

Root + Impact

Description

  • Any user (not just the original requester) can call fulfillMintRequest with a valid requestId and mint the NFT, even if they did not initiate the original request. This allows malicious actors to "steal" NFTs that should belong to the original requester.

function fulfillMintRequest(bytes32 requestId) external {
// @> No check that msg.sender == s\_funcReqIdToUserMintReq\[requestId].user
// @> Any address can call this and mint the NFT for themselves
// ...existing code...

Risk

Likelihood

  • This will occur whenever a malicious user or automated bots monitor the blockchain for WeatherNFTMintRequestSent events and calls fulfillMintRequest with the observed requestId before the original requester does.

Impact

  • Malicious users can mint NFTs they did not pay/request for.

Proof of Concept

This test should pass since revert is expected when another user is trying to fulfill request, but it is failing

function testMintRequestByAnotherUser() public {
// Setup
string memory pincode = "125001";
string memory isoCode = "IN";
bool registerKeeper = false;
uint256 heartbeat = 12 hours;
uint256 initLinkDeposit = 5e18;
// uint256 tokenId = weatherNft.s_tokenCounter();
// User requests mint
vm.startPrank(user);
linkToken.approve(address(weatherNft), initLinkDeposit);
vm.recordLogs();
weatherNft.requestMintWeatherNFT{value: weatherNft.s_currentMintPrice()}(
pincode, isoCode, registerKeeper, heartbeat, initLinkDeposit
);
vm.stopPrank();
// Get requestId from logs
Vm.Log[] memory logs = vm.getRecordedLogs();
bytes32 reqId;
for (uint256 i; i < logs.length; i++) {
if (logs[i].topics[0] == keccak256("WeatherNFTMintRequestSent(address,string,string,bytes32)")) {
(, , , reqId) = abi.decode(logs[i].data, (address, string, string, bytes32));
break;
}
}
assert(reqId != bytes32(0));
// Another User claims NFT
address attacker = makeAddr("attacker");
vm.expectRevert();
vm.prank(attacker);
weatherNft.fulfillMintRequest(reqId);
}

Recommended Mitigation

  • Restrict fulfillMintRequest so that only the original requester can call it:

function fulfillMintRequest(bytes32 requestId) external {
+ require(
+ msg.sender == s_funcReqIdToUserMintReq[requestId].user,
+ WeatherNft__Unauthorized()
+ );
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.