For a good-faith CORRUPTED outcome, flagOutcome() requires only that attacker_ is nonzero. The moderator can name itself, another address it controls, the pool contract, a burn address, or an address unable to receive the token. claimAttackerBounty() then treats that value as the sole identity proof and makes the entire snapshotted pool balance claimable by it.
The moderator is intentionally trusted to classify outcomes and identify the attacker, so self-dealing is not an unprivileged exploit. However, the same role can unilaterally choose a full-pool payout recipient without independent registry binding, attestation, delay, or basic semantic validation.
Good-faith validation establishes only zero/nonzero shape:
The payout function authenticates only by equality with the same unverified value:
The PoC deposits 100 tokens of stake and 50 tokens of bonus, moves the registry through an observed attack into CORRUPTED, and has the moderator flag good faith with attacker_ = moderator. The moderator immediately calls claimAttackerBounty() and receives all 150 tokens.
Other accepted values have different failure modes. address(this) can never be an external msg.sender, leaving the bounty unclaimable until the 180-day recovery sweep. Burn or inaccessible addresses permanently lose the bounty if they can execute or receive it, while blacklisted or hook-reverting recipients can block the claim until the deadline. Those cases reinforce that nonzero validation is not sufficient for a value-bearing identity.
Likelihood:
Only the configured outcome moderator can create the designation, and the registry must genuinely report CORRUPTED.
Exploitation therefore requires moderator compromise, collusion, or intentional abuse of a broad trusted role.
The path is otherwise immediate and requires no approval from the pool owner, stakers, registry moderator, or named attacker.
Impact:
A self-appointed moderator or controlled confederate can receive all staked principal and contributed bonus.
An unclaimable or destructive address can delay recovery for 180 days or permanently destroy the intended attacker payout.
On-chain observers cannot distinguish a legitimate attacker identity from a moderator-selected address because no evidence or registry binding is stored.
Create test/audit/CP036ModeratorControlledAttacker.t.sol with the following contents:
Run the PoC from the repository root:
Execute forge test --offline --match-path test/audit/CP036ModeratorControlledAttacker.t.sol -vv.
Confirm that the test passes. The moderator is accepted as the good-faith attacker and claims all 150 tokens of principal and bonus in the next call.
At minimum, reject structurally unsafe and conflicted recipients. These checks prevent direct self-appointment and obvious liveness traps, although they cannot stop a moderator from using a second controlled address.
For meaningful protection against recipient substitution, bind attacker_ to independent incident evidence: for example, a value reported by the AttackRegistry, an EIP-712 designation approved by a separate security council, or a timelocked quorum decision that stakers can inspect before payout. Use a multisig for the moderator role and emit an incident identifier or evidence hash with the designation. If unilateral moderator selection is the intended trust model, document explicitly that the moderator can direct the full pool to any controlled nonzero address; address filtering alone must not be presented as preventing collusion.
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.
The contest is complete and the rewards are being distributed.