blockchainfreemedium

NotADemocraticElection

HackTheBox

In the post-apocalyptic wasteland, the remnants of human and machine factions vie for control over the last vestiges of civilization. The Automata Liberation Front (ALF) and the Cyborgs Independence Movement (CIM) are the two primary parties seeking to establish dominance. In this harsh and desolate

$ ls tags/ techniques/
abi_encodepacked_collisionvoter_weight_hijacking

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

NotADemocraticElection - HackTheBox

Description

In the post-apocalyptic wasteland, the remnants of human and machine factions vie for control over the last vestiges of civilization. The Automata Liberation Front (ALF) and the Cyborgs Independence Movement (CIM) are the two primary parties seeking to establish dominance. In this harsh and desolate world, democracy has taken a backseat, and power is conveyed by wealth. Will you be able to bring back some Democracy in this hopeless land?

Goal: Make CIM win the election (reach 1000e18 votes)

Analysis

Contract Overview

Two contracts are provided:

  1. Setup.sol - Deploys the election contract and deposits 100 ETH for voter "Satoshi Nakamoto"
  2. NotADemocraticElection.sol - Main voting contract with weighted voting based on ETH deposits

Key Data Structures

mapping(bytes3 _id => Party) public parties; // Party info & vote count mapping(bytes _sig => Voter) public voters; // Voter weight by signature mapping(string _name => mapping(string _surname => address _addr)) public uniqueVoters;

The Vulnerability: abi.encodePacked() Hash Collision

The critical vulnerability is in the getVoterSig() function:

function getVoterSig(string memory _name, string memory _surname) public pure returns (bytes memory) { return abi.encodePacked(_name, _surname); }

Problem: abi.encodePacked() with multiple dynamic types (strings, bytes) does NOT add length prefixes or delimiters. This causes hash collisions:

abi.encodePacked("Satoshi", "Nakamoto")  = "SatoshiNakamoto"
abi.encodePacked("SatoshiNaka", "moto")  = "SatoshiNakamoto"  // SAME!
abi.encodePacked("S", "atoshiNakamoto")  = "SatoshiNakamoto"  // SAME!

Exploitation Logic

  1. Setup deposits 100 ETH for "Satoshi" + "Nakamoto":

    • uniqueVoters["Satoshi"]["Nakamoto"] = Setup.address
    • voters["SatoshiNakamoto"].weight = 100e18
  2. We register with collision names (e.g., "S" + "atoshiNakamoto"):

    • uniqueVoters["S"]["atoshiNakamoto"] = our_address (NEW entry)
    • voters["SatoshiNakamoto"].weight += 0 (SAME voter signature!)

...

$ grep --similar

Similar writeups