President Elector

First Flight #24
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: medium
Valid

Signature Replay Vulnerability Allows Voting in Multiple Elections

[H-01] Signature Replay Vulnerability Allows Voting in Multiple Elections

Summary

The smart contract is vulnerable to a signature replay attack, where a valid signature from a past election can be reused in subsequent elections. This enables unauthorized voting in future presidential elections using a previously valid signature. As a result, anyone can submit an old signature from a previous election to cast a vote in the current election, leading to a significant security issue.

Vulnerability Details

  • Signature Replay: The contract allows the use of a valid signature from a previous election without verifying if the signature has already been used in a prior election round. This means a malicious user can reuse the same signature to vote in subsequent elections without generating a new signature.

  • No Election Number in Signature: The contract does not include s_voteNumber, which tracks the election round, in the signature scheme. This makes it possible for a signature from one election round to remain valid in future rounds.

Proof of Concept:

Add the following to existing test suit, and modify the setup function:

address voter;
uint256 voterPK;
function setUp() public {
for (uint256 i = 0; i < MAX_VOTERS; i++) {
voters.push(address(uint160(i + VOTERS_ADDRESS_MODIFIER)));
}
(voter, voterPK) = makeAddrAndKey("signVoter");
voters.push(voter);
rankedChoice = new RankedChoice(voters);
for (uint256 i = 0; i < MAX_CANDIDATES; i++) {
candidates.push(address(uint160(i + CANDIDATES_ADDRESS_MODIFIER)));
}
}
function testVoteSig() public {
orderedCandidates = [candidates[0], candidates[1], candidates[2]];
vm.prank(voter);
rankedChoice.rankCandidates(orderedCandidates);
bytes32 TYPEHASH = keccak256("rankCandidates(address[])");
bytes32 hash = rankedChoice.getSig(orderedCandidates);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(voterPK, hash);
bytes memory signature = abi.encodePacked(r, s, v);
rankedChoice.rankCandidatesBySig(orderedCandidates, signature);
assertEq(rankedChoice.getUserCurrentVote(voter), orderedCandidates);
vm.warp(block.timestamp + rankedChoice.getDuration());
rankedChoice.selectPresident();
assertEq(rankedChoice.getCurrentPresident(), candidates[0]);
// Reusing the old signature to vote in the new election
rankedChoice.rankCandidatesBySig(orderedCandidates, signature);
assertEq(rankedChoice.getUserCurrentVote(voter), orderedCandidates);
}

Impact

This vulnerability allows a high-severity attack, where an attacker can reuse old signatures to vote in new elections. This breaks the integrity of the voting system and can result in unauthorized votes being counted multiple times across different election cycles.

Tools Used

Manual Review

Recommendations

Include s_voteNumber in Signature to Prevent Reuse

To mitigate this issue, the contract should include the state variable s_voteNumber, which is the counter of elections, in the signature scheme. This ensures that signatures are only valid for the specific election they were generated for.

  1. Update the TYPEHASH to include s_voteNumber:
    Modify the rankCandidates function to include the election round (s_voteNumber) in the signature hash:

    bytes32 public constant TYPEHASH = keccak256("rankCandidates(address[],uint256 s_voteNumber)");
  2. Update Signature Hash Generation:
    Include the s_voteNumber when generating the hash to ensure signatures are only valid for the current election round:

    function getSig(address[] memory orderedCandidates, uint256 voteNumber) public view returns (bytes32) {
    return keccak256(
    abi.encode(
    TYPEHASH,
    keccak256(abi.encodePacked(orderedCandidates)),
    voteNumber
    )
    );
    }
  3. Verify the s_voteNumber in rankCandidatesBySig:
    When verifying signatures, ensure that the signature corresponds to the current election round (s_voteNumber):

    function rankCandidatesBySig(address[] memory orderedCandidates, bytes memory signature) external {
    // Retrieve the signature hash including the current vote number
    bytes32 hash = getSig(orderedCandidates, s_voteNumber);
    // Verify the signature and record the vote
    // Signature verification and vote recording logic...
    }

By incorporating the s_voteNumber in the signature, you ensure that signatures are unique to each election and prevent their reuse across different election rounds, eliminating the signature replay vulnerability.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Replay Attack - The same signature can be used over and over

Support

FAQs

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