The objective of this protocol is to grant the status of ChoosingRam::isRamSelected
to one of the participants that enters the protocol through Dussehra::enterPeopleWhoLikeRam()
. The ChoosingRam::increaseValuesOfParticipants()
function or the ChoosingRam::selectRamIfNotSelected()
function both have the ability to select a participant to be the ChoosingRam:selectedRam
. This lucky participant will get 50% of the entrance fees required to mint a RamNFT
(Dussehra::totalAmountGivenToRam
)
Both of the functions mentioned above have an important modifier: ChoosingRam::
ChoosingRam::isRamSelected
is a boolean that should change to true when ChoosingRam:selectedRam
has been chosen. We can see that in ChoosingRam::selectRamIfNotSelected()
, the value is updated:
Only the RamNFT::organizer
can call this function.
Presumably, this change should also occur in ChoosingRam::increaseValuesOfParticipants()
. We can make this assumption because the function has the modifier mentioned earlier. The problem is, ChoosingRam::increaseValuesOfParticipants()
does not update ChoosingRam::isRamSelected
.
The function chooses an address for ChoosingRam::selectedRam
, but it does not update ChoosingRam::isRamSelected
.
ChoosingRam::selectRamIfNotSelected()
can be called by RamNFT::organizer
after this timestamp:
Since ChoosingRam::increaseValuesOfParticipants()
does not update ChoosingRam::isRamSelected
, RamNFT::organizer
will always be the actor that makes ChoosingRam::selectedRam
official.
I was thinking about submitting this as a bug, but on its own, it's a Low at best. It's not a huge deal if ChoosingRam::selectedRam
can constantly by changed until the timestamp deadline. The real threat lies in the attack opportunity provided to RamNFT::organiser
thanks to this oversight.
RamNFT::organizer
has more than enough information to dictate who the winner will be by exploiting weak randomness in ChoosingRam::selectRamIfNotSelected
:
This calculation will produce a number that will be used to choose a "random" RamNFT
id.
Along with the timestamps, RamNFT::tokenCounter
(the amount of participants in the protocol), determines the "random" number
The owner of this nft will become ChoosingRam::selectedRam
granting them claim to 50% of the mint fees.
The attack can occur in a few ways. In its simplest form, RamNFT::organiser
can collude with a participant and choose them to be ChoosingRam::selectedRam
. An attack contract may not be necessary, but it makes the attack much easier. If the RamNFT::organiser
chooses to go with the simple route, they would have to deploy their attack at very specific times. This function in Foundry shows how RamNFT::organiser
can figure out when to attack.
In this example, RamNFT::organiser
wants the first entry to be the ChoosingRam::selectedRam
: if random == 0
The function will keep track of all the timestamps that would produce 0 based on the value of RamNFT::tokenCounter
A counter of 101
will produce 0
849 times within the predetermined time constraints of ChoosingRam::selectRamIfNotSelected
Without an attack contract, RamNFT::organiser
would have to make a precise call ofChoosingRam::selectRamIfNotSelected()
during one of the returned timestamps
RamNFT::organiser
room for error:The malicious actor would use this contract to deploy RamNFT
which would mean that this contract would function as RamNFT::organiser
The malicious actor could enter the protocol and choose their RamNFT
to become ChoosingRam::selectedRam
(or collude with another participant)
Organizer deploys attack contract and uses the attack contract to deploy RamNFT
Organizer enters the protocol as first participant (tokenId: 0
)
We can create an arbitrary amount of users (100 in this example) setting the counter (101 in this example)
The test shows ChoosingRam::increaseValuesOfParticipants
maxing out the RamNFT
for entrant 5
It does not trigger ChoosingRam::isSelected
The organizer figures out which blocks they can exploit
In the wild, the attack function could be triggered near one of these timestamps
Without a contract, the malicious actor's call of ChoosingRam::selectRamIfNotSelected()
would need to be much more precise
This is a high rsk vulnerability that enables the RamNFT::organiser
to acquire all the funds.
Users enter this protocol with the belief that they have a chance to win 50% of the entrance fees if they become ChoosingRam:selectedRam
. This vulnerability erases the implied randomness of the protocol and gives all of the rewards to RamNFT::organiser
.
ChoosingRam::increaseValuesOfParticipants()
never sets the ChoosingRam::isRamSelected
boolean to true
. RamNFT::organiser
will always set ChoosingRam:selectedRam
with the ChoosingRam::selectRamIfNotSelected()
function.
The malicious actor can:
Use an attack contract to serve the role of RamNFT::organiser
while entering the contest themselves
Or collude with an entrant.
Either way, the malicious actor is in control of choosing ChoosingRam:selectedRam
removing randomness from the equation. With or without an attack contract, the actor pulling the strings of RamNFT::organiser
will receive a majority of the funds. They can choose to collude with a participant to get a share of Dussehra::totalAmountGivenToRam
along with the remaining 50%. Or, they can take 100%.
Manual Review
Foundry
When the conditions are met, ChoosingRam::increaseValuesOfParticipants
should set ChoosingRam::isSelected
to true
This will put some control of the outcome in the hands of the participants.
Without this change, RamNFT::organiser
has all of the influence
We can consider not allowing contracts to become RamNFT::organiser
RamNFT
RamNFT::constructor()
This is a step in the right direction, but it doesn't address the main problem
The most important mitigation step is to use off-chain methods such as ChainlinkVRF to create randomness.
This implementation of "randomness" gives the malicious actor all the information they need to get their desired output.
It should not be used
The organizer is trusted, but the function `ChoosingRam::selectRamIfNotSelected` uses a way to generate a random number that is not completely random.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.