Sparkn

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

ProxyFactory#deployProxyAndDistributeBySignature - Insufficient digest parameters could lead to replay attacks and loss of deposited funds for each new Distributor contract

Summary

The function deployProxyAndDistributeBySignature in the ProxyFactory contract may lead to unintended fund distributions due to the reuse of old signatures with new Distribution.sol contracts. Without sufficient restrictions on the signature's usage, an attacker can exploit this to deploy a new proxy and divert the funds to himself in new Distributor.sol contracts.

Vulnerability Details

The function uses the following digest for ECDSA signature verification:

bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(contestId, data)));

The digest can be recreated by a malicious user and exploited with a replay attack under the right circumstances. The vulnerabilities of the current digests are that there are no deadlines for how long the digests are valid. It also doesn’t distinguish which version of the implementation contract the digests use.

This means that the digests will be valid for any time in the future and for any new implementation contracts.

POC

  1. An organizer holds an annual contest. They ask the owner to help setContest and then send the ERC20 tokens to the to-be proxy address in preparation for the contest.

  2. The malicious attacker participates in the contest and wins.

  3. When the contest has ended, the organizer deploys a proxy contract (through their signature) and distribute the funds from the proxy contract to the winners array (including the malicious attacker) using the function deployProxyAndDistributeBySignature.

  4. So far, it is working as the contract is intended to work.

  5. One year later, the owner has deployed a new implementation contract.

  6. The organizer decides to hold their annual contest once again. They ask the owner to help set up the contest once again by calling setContest using the same contestId but this time with the new implementation contract. The organizer again sends the ERC20 tokens to the to-be proxy address in preparation for the contest. The proxy address will be another one since the implementation contract is new:

    //@audit new proxy address since implementation address is new
    address proxy = _deployProxy(organizer, contestId, implementation);
  7. And here's the attack. Immediately after the contest ends, a malicious attacker will be the first to call deployProxyAndDistributeBySignature with the signature and digest used for the last competition:

    //@audit the contestId will be the same, and the encoded data will include the malicious attacker from the last contest
    bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(contestId, data)));
  8. The malicious attacker will pass all the checks in deployProxyAndDistributeBySignature since the digest and signature will still correctly recover the organizer's address as per below. It will also pass the saltToCloseTime checks since the contest has been set (albeit with the new implementation address).

    if (ECDSA.recover(digest, signature) != organizer) revert ProxyFactory__InvalidSignature();
    ...
    if (saltToCloseTime[salt] == 0) revert ProxyFactory__ContestIsNotRegistered();
    if (saltToCloseTime[salt] > block.timestamp) revert ProxyFactory__ContestIsNotClosed();
  9. Lastly, the malicious attacker will have successfully deployed a new proxy contract and distributed the funds per the outcome of the old contest instead of the new one.

Impact

Malicious actors can benefit from the reuse of signatures, directing funds for distribution to themselves and/or others per an old competition result.

Severity: High

  • The severity of this issue is high, as a malicious attacker could divert funds from the intended recipients.

Likelihood: Low

  • The likelihood of performing this attack is low. It would require a new Distributor.sol contract to be deployed where a new contest is set with the same contestId as in the old Distributor.sol contract.

Tools Used

Manual Review.

Recommendations

Include a deadline and the contract address of the current implementation in the digest to make the signature time-sensitive and specific to the implementation:

bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(contestId, data, block.timestamp + DEADLINE_DURATION, implementation)));

Support

FAQs

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