ElectroCore is an online voting platform that allows users to create elections, nominate themselves for elections, or vote for nominated candidates in open elections.
Winning an election grants you access to all nominees’ private notes (which contained flags).
The goal was therefore simple: Win all elections.
Or at last sufficiently many.
To this goal, registering a new candidate and making them a nominee of an election is simple enough with a call to /register and /nominate.
The tricky part was the vote itself, as our user may only cast a single vote.
Voting used a custom homomorphic encryption system (defined in HomoKeyPair.cs and HomoCrypto.cs) that allowed users to send their ballot as an encrypted vector with one entry per candidate.
As the encryption allowed ciphertexts to be added, the server could then simply add the newly cast vote-vector to the votes tallied so far.
The private key is a just large random integer, and the public key is derived as follows:
In essence, the public key is an array of 16 elements of the form , where is a large random number, a small random value between 10 and 100, and corresponds to privateKey.MaxNum, which was hardcoded to 243.
The encryption then simply sums a random subset of these public-key elements (the first element is always taken), adds another random multiple of privateKey.MaxNum to it, and then adds the actual plaintext value val.
The resulting ciphertext is thus the sum of a random multiple of the private-key, a random multiple of MaxNum, and the actual plaintext-value.
The decryption then simply works by taking the modulo to the private key and then again taking the module of the result to MaxNum.
As the private-key is orders of magnitude larger than MaxNum (a 128-bit BigInteger vs the 8-bit value 243) this works as long as the plaintext value is less than 243.
A regular ballot consisted of a vector of zeros, with a one set at the position of the desired candidate.
For our exploit we simply cast a ballot that gave our desired candidate 200 additional votes.
Once this vote was counted we then simply waited until the election was over, logged in as the candidate, and read all the juicy private notes of the other nominees, which gave us some flags.
We used redis to keep track of our nominees credentials and whether we had already registered or voted for an election.
We believe it might also be possible to “reset” another candidates votes, by letting their vote count overflow to 0 modulo 243.
This should be possible, as nominees are sent the election’s private key, and all votes cast so far can be obtained publicly.
However, for this to be effective, the exploit would need to be scheduled to run just before the voting-phase ends, which might lead to heavy network load if multiple teams were to go that route.
In a similar vein, elections allowed a maximum of 243 ballots to be sent in total.
One could thus try to register sufficiently many users and send ballots from each of them.
However, again this leads to some sort a race-condition between teams where every team tries to get in the most votes before all 243 slots are taken, which would lead to heavy traffic.
Overall, a nice service that was relatively simple to exploit and thankfully did not cause a network-meltdown.