Sparkn

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

Signatures can be replayed when the implementation address has changed

Summary

The criteria for creating a contest, as per what's written in the code, is that effectively the combination of organizer, contestId, and implementation for a contest needs to be unique. This is because those values are used to create the salt which is used during the Proxy contract creation. This leads to an edge case where the implementation address is changed in the future, but a contest with the same contestId and organizer is created. When a signature was created by the organizer for the older contest, this signature can also be used for this newer contest.

This is because the digest in the deployProxyAndDistributeBySignature function only includes the data and contestId (and also the organizer implicitly, as they signed it). It doesn't include the implementation address. This means that any implementation address can be used for this signature, as long as the contestId and organizer are the same.

Let's consider the case where the malicious user won most of the rewards in the first contest, but did not participate in the second contest. They can re-use the first signature to send themselves the rewards from the second contest (assuming reward token is the same).

Vulnerability Details

Consider the deployProxyAndDistributeBySignature function which allows any user to take a signature by an organizer and deploy the Proxy & distribute the funds:

function deployProxyAndDistributeBySignature(
address organizer,
bytes32 contestId,
address implementation,
bytes calldata signature,
bytes calldata data
) public returns (address) {
bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(contestId, data)));
if (ECDSA.recover(digest, signature) != organizer) revert ProxyFactory__InvalidSignature();
bytes32 salt = _calculateSalt(organizer, contestId, implementation);
...
}

Since the implementation address isn't part of the digest, a signature will be accepted for any contest, as long as the organizer, contestId, and data are the same. This can lead to the following exploit:

  1. A first contest is created in which User A will be awarded the most tokens, this uses implementation-1

  2. The organizer for the first contest creates a signature, which another user calls deployProxyAndDistributeBySignature with; User A is awarded most of the rewards

  3. The same organizer decides to create another contest, but this time using implementation-2 (which has slightly different logic, but the API is the same) - this uses the same contestId as the first contest; User B is intended to get the most awards from this second contest

  4. User A uses the same signature from the first contest & receives most of the rewards from the second contest

Impact

Signatures can be replayed when a similar contest is created, which can lead to a direct loss of funds.

Tools Used

Manual review

Recommendations

Include the implementation address in the digest in the deployProxyAndDistributeBySignature function. Alternatively, provide a deadline value in the digest.

Support

FAQs

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

Give us feedback!