President Elector

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

[M-02] Signature Replay Vulnerability in `rankCandidatesBySig`

Summary

The rankCandidatesBySig function allows voters to submit signed messages to rank candidates. However, the current implementation does not adequately prevent replay attacks, enabling a signer to reuse the same signature multiple times across different voting periods, potentially altering past or future vote rankings maliciously.

Vulnerability Details

  • Signature Structure:

    • The function encodes the data using the TYPEHASH which includes the orderedCandidates array but does not incorporate the s_voteNumber.

      bytes32 structHash = keccak256(abi.encode(TYPEHASH, orderedCandidates));
      bytes32 hash = _hashTypedDataV4(structHash);
      address signer = ECDSA.recover(hash, signature);
  • Replay Potential:

    • Since the s_voteNumber is not part of the signed data, the same signature can be reused for multiple voteNumber instances.

    • An attacker can submit the same signature in different voting rounds, overwriting previous rankings or influencing future elections unintentionally.

Impact

  • Vote Manipulation: Attackers can alter voter rankings across multiple voting periods, skewing election results.

  • Loss of Trust: The integrity of the election process is undermined, leading to reduced trust in the system.

  • Potential for Collusion: Malicious actors can collude to submit multiple fraudulent votes using the same signature, amplifying their influence.

Tools Used

  • Manual Code Review: Examined the rankCandidatesBySig function for replay protection mechanisms.

  • Static Analysis Tools: Utilized tools like MythX to identify signature-related vulnerabilities.

Recommendations

  • Incorporate s_voteNumber into Signed Data:

    • Include the current s_voteNumber within the data structure being signed to bind signatures to specific voting periods.

bytes32 structHash = keccak256(abi.encode(TYPEHASH, s_voteNumber, orderedCandidates));

  • Implement Replay Protection:

    • Ensure that each signature can only be used once per voteNumber by tracking used signatures or incorporating unique identifiers.

      mapping(bytes32 => bool) public usedSignatures;
      function rankCandidatesBySig(
      address[] memory orderedCandidates,
      bytes memory signature
      ) external {
      bytes32 structHash = keccak256(abi.encode(TYPEHASH, s_voteNumber, orderedCandidates));
      bytes32 hash = _hashTypedDataV4(structHash);
      address signer = ECDSA.recover(hash, signature);
      require(!usedSignatures[hash], "Signature already used");
      usedSignatures[hash] = true;
      _rankCandidates(orderedCandidates, signer);
      }
  • Use Nonces or Unique Identifiers:

    • Integrate a nonce system where each signature includes a unique number that must be incremented with each vote submission.

      mapping(address => uint256) public nonces;
      function rankCandidatesBySig(
      address[] memory orderedCandidates,
      bytes memory signature
      ) external {
      uint256 currentNonce = nonces[msg.sender]++;
      bytes32 structHash = keccak256(abi.encode(TYPEHASH, s_voteNumber, currentNonce, orderedCandidates));
      bytes32 hash = _hashTypedDataV4(structHash);
      address signer = ECDSA.recover(hash, signature);
      _rankCandidates(orderedCandidates, signer);
      }
  • Emit Events for Signature-Based Actions:

    • Emit events when signatures are used to facilitate monitoring and auditing.

      event CandidateRankedBySignature(address indexed signer, uint256 voteNumber);
      function rankCandidatesBySig(
      address[] memory orderedCandidates,
      bytes memory signature
      ) external {
      ...
      emit CandidateRankedBySignature(signer, s_voteNumber);
      }

Proof of Concept

  • Implement unit tests to verify that signatures cannot be replayed across different voteNumber instances.

function testSignatureReplayProtection() public {
address[] memory orderedCandidates = new address[]();
orderedCandidates[0] = address(1);
orderedCandidates[1] = address(2);
orderedCandidates[2] = address(3);
bytes memory signature = signVote(voters[0], orderedCandidates, s_voteNumber);
// First submission should succeed
rankedChoice.rankCandidatesBySig(orderedCandidates, signature);
// Second submission with the same signature should fail
vm.expectRevert("Signature already used");
rankedChoice.rankCandidatesBySig(orderedCandidates, signature);
}
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.