RabbitHole Quest Protocol contest - __141345__'s results

A protocol to distribute token rewards for completing on-chain tasks.

General Information

Platform: Code4rena

Start Date: 25/01/2023

Pot Size: $36,500 USDC

Total HM: 11

Participants: 173

Period: 5 days

Judge: kirk-baird

Total Solo HM: 1

Id: 208

League: ETH

RabbitHole

Findings Distribution

Researcher Performance

Rank: 96/173

Findings: 1

Award: $18.70

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

18.6976 USDC - $18.70

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
edited-by-warden
duplicate-107

External Links

Lines of code

https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L219-L229

Vulnerability details

Impact

If the some quest is created for multiple chain, the signature could be replayed. When the task requires certain amount of resources such as staking, the signature replay could trick the system, getting the rewards without doing the work and spending the cost.

Proof of Concept

Imagine, some quest is created for multiple chain, Ethereum, Polygon and BSC, and the task is similar on each chain, staking 1000 USDC for 3 month. As soon as the user finishes the task on Ethereum by taking 1000 USDC, the signature could be generated by the claimSigner/indexer. Then even if no USDC is staked on Polygon and BSC, the same signature could still be applied to mint the receipt and claim the rewards.

In mintReceipt(), the chainid is not included, only the msg.sender and questId. Which means the same signature could be used for different chains multiple times, without the quest being performed.

File: quest-protocol/contracts/QuestFactory.sol
219:     function mintReceipt(string memory questId_, bytes32 hash_, bytes memory signature_) public {
220:         if (quests[questId_].numberMinted + 1 > quests[questId_].totalParticipants) revert OverMaxAllowedToMint();
221:         if (quests[questId_].addressMinted[msg.sender] == true) revert AddressAlreadyMinted();
222:         if (keccak256(abi.encodePacked(msg.sender, questId_)) != hash_) revert InvalidHash();
223:         if (recoverSigner(hash_, signature_) != claimSignerAddress) revert AddressNotSigned();
224: 
225:         quests[questId_].addressMinted[msg.sender] = true;
226:         quests[questId_].numberMinted++;
227:         emit ReceiptMinted(msg.sender, questId_);
228:         rabbitholeReceiptContract.mint(msg.sender, questId_);
229:     }

Tools Used

Manual analysis.

Include the chain id in the signed hash in mintReceipt(). And the signature from the claimSigner/indexer need to add this info also.

File: quest-protocol/contracts/QuestFactory.sol
219:     function mintReceipt(string memory questId_, bytes32 hash_, bytes memory signature_) public {

-222:         if (keccak256(abi.encodePacked(msg.sender, questId_)) != hash_) revert InvalidHash();
+222:         if (keccak256(abi.encodePacked(msg.sender, chainid(), questId_)) != hash_) revert InvalidHash();

#0 - c4-judge

2023-02-05T06:12:45Z

kirk-baird marked the issue as duplicate of #45

#1 - c4-judge

2023-02-14T09:36:08Z

kirk-baird changed the severity to 2 (Med Risk)

#2 - c4-judge

2023-02-14T09:36:29Z

kirk-baird marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter