President Elector

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

rankCandidatesBySig Does Not Include The Current Vote Number

Summary

The function rankCandidatesBySig does not consider the current s_voteNumber or any sort of expiration time on the signature. A malicious actor can store signatures from a previous vote number and resubmit them for future votes to influence results.

Vulnerability Details

function rankCandidatesBySig(
address[] memory orderedCandidates,
bytes memory signature
) external {
bytes32 structHash = keccak256(abi.encode(TYPEHASH, orderedCandidates));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, signature);
_rankCandidates(orderedCandidates, signer);
}

The function rankedCandidatesBySig copied above does not account for the current vote number. This means that a malicious actor can store previous signature, orderedCandidates inputs and re use them in future votes. If the TYPEHASH, orderedCandidatesare the same, the structHashwill be the same and the signature will be considered valid since the domain separator constructed in the EIP712 contract remains unchanged.

In particular, a sophisticated and motivated actor, such as the current president, could store results from the previous round where they won, and near the end of the voting time block replay all the votes that were in their favor to possibly swing the election towards them winning again. It may be possible to even bundle these transactions together and call selectPresident as the last transaction in the bundle to prevent anyone from noticing or stopping this sort of attack.

Impact

This is a HIGH impact vulnerability as it ruins the integrity of the voting process past the first round and breaks the core functionality of the contract.

Tools Used

Foundry

Recommendations

Include the s_voteNumber when creating the structHash. This will prevent replay in future rounds. A modified version of rankCandidatesBySig that protects against this is shown below.

function rankCandidatesBySig(
address[] memory orderedCandidates,
bytes memory signature
) external {
bytes32 structHash = keccak256(abi.encode(TYPEHASH, orderedCandidates, s_voteNumber));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, signature);
_rankCandidates(orderedCandidates, signer);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months 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.