President Elector

First Flight #24
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Invalid

Critical: Unscheduled President Election via Timestamp and Storage Manipulation

Summary

A critical vulnerability has been identified in the RankedChoice smart contract. An attacker can manipulate the contract's state to force an unscheduled election, potentially changing the president outside of the intended voting cycle. This is achieved through a combination of exploiting the reliance on block.timestamp and directly manipulating the contract's storage.

Vulnerability Details

The vulnerability stems from two main issues:

  1. Reliance on block.timestamp: The contract uses block.timestamp to determine when a new election can be held. This value can be manipulated by miners to some extent.

  2. Unprotected state variables: The s_previousVoteEndTimeStamp is stored as a private variable, but it's not actually protected from direct storage manipulation.

The selectPresident() function checks if enough time has passed since the last election using:

if (block.timestamp - s_previousVoteEndTimeStamp <= i_presidentalDuration) {
revert RankedChoice__NotTimeToVote();
}

An attacker can bypass this check by manipulating both the block.timestamp and s_previousVoteEndTimeStamp.

Impact

The impact of this vulnerability is severe:

  • Unauthorized elections can be triggered at any time.

  • The attacker can potentially install their preferred candidate as president.

  • The integrity of the entire voting system is compromised.

  • User trust in the contract would be severely damaged.

Proof of Concept

function test_ExploitSelectPresident() public {
// Initial state
address initialPresident = rankedChoice.getCurrentPresident();
console.log("Initial president:", initialPresident);
// Simulate some voting
simulateVoting();
// Try to select president immediately (should fail)
vm.expectRevert(RankedChoice.RankedChoice__NotTimeToVote.selector);
rankedChoice.selectPresident();
// Exploit: Manipulate the block.timestamp
vm.warp(block.timestamp + rankedChoice.getDuration() + 1);
// Exploit: Access and manipulate s_previousVoteEndTimeStamp
uint256 slot = 1; // Slot for s_previousVoteEndTimeStamp (may need adjustment)
bytes32 previousTimestamp = vm.load(address(rankedChoice), bytes32(slot));
console.log("Previous timestamp:", uint256(previousTimestamp));
// Set s_previousVoteEndTimeStamp to a very old timestamp
bytes32 manipulatedTimestamp = bytes32(uint256(0)); // Set to 0 for demonstration
vm.store(address(rankedChoice), bytes32(slot), manipulatedTimestamp);
// Now the attacker can call selectPresident
vm.prank(attacker);
rankedChoice.selectPresident();
// Check the result
address newPresident = rankedChoice.getCurrentPresident();
console.log("New president after exploit:", newPresident);
// Verify that the president has changed
assert(newPresident != address(0));
assert(newPresident != initialPresident);
}

Tools Used

  • Foundry (Forge) for contract testing and exploitation

  • Manual code review

Recommendations

  1. Use a more secure timing mechanism: Instead of relying solely on block.timestamp, consider implementing a block number-based timing system or using an oracle for more secure timekeeping.

  2. Use OpenZeppelin's ReentrancyGuard: To prevent potential reentrancy attacks during the president selection process.

  3. Improve state variable protection: While Solidity's private keyword doesn't truly protect variables, consider implementing a more robust state management system that makes direct storage manipulation more difficult.

  4. Add events and timelock: Implement events for important state changes and consider adding a timelock mechanism for critical operations to allow for community oversight.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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