DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: high
Invalid

The signature used in the `L2ContractMigrationFacet::redeemDepositsAndInternalBalances` function can be reused since the signed message doesn't include nonce

Summary

L2ContractMigrationFacet contract is vulnerable to signature replay attacks due to the lack of nonce in its signature verification process. This vulnerability allows a malicious user to reuse a valid signature multiple times, potentially leading to unauthorized actions.

Vulnerability Details

Take a look at the verifySignature function. It uses ECDSA for signature verification but does not implement any mechanism to prevent signature reuse.

function verifySignature(
address owner,
address reciever,
uint256 deadline,
bytes calldata signature
) internal view {
require(block.timestamp <= deadline, "Migration: permit expired deadline");
bytes32 structHash = keccak256(
abi.encode(REDEEM_DEPOSIT_TYPE_HASH, owner, reciever, deadline)
);
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, signature);
require(signer == owner, "Migration: permit invalid signature");
}

The function checks the deadline but doesn't use a nonce or mark the signature as used after verification. This allows the same signature to be reused in multiple transactions as long as the deadline hasn't passed.

Impact

If not fixed, this signature replay ability will lead to unauthorized and repeated execution of the redeemDepositsAndInternalBalances function. This can result in:

  1. Duplicate redemption of deposits and internal balances.

  2. Incorrect allocation of assets to the receiver address.

  3. Manipulation of account stalk and roots balances.

Take a look at the redeemDepositsAndInternalBalances function:

function redeemDepositsAndInternalBalances(
address owner,
address reciever,
AccountDepositData[] calldata deposits,
AccountInternalBalance[] calldata internalBalances,
uint256 ownerRoots,
bytes32[] calldata proof,
uint256 deadline,
bytes calldata signature
) external payable fundsSafu noSupplyChange nonReentrant {
// ... (other checks)
@> verifySignature(owner, reciever, deadline, signature);
// Set deposits for `reciever`
uint256 accountStalk;
for (uint256 i; i < deposits.length; i++) {
accountStalk += addMigratedDepositsToAccount(reciever, deposits[i]);
}
// Set stalk for account
setStalk(reciever, accountStalk, ownerRoots);
}

Consider a scenario where:

  1. Alice, a contract owner, signs a valid message to redeem deposits and internal balances to Bob's address.

  2. The transaction is executed successfully using Alice's signature, transferring assets to Bob and updating stalk balances.

  3. An attacker observes this transaction and its signature.

  4. The attacker replays the same transaction with the same signature before the deadline expires.

  5. The contract processes the replayed transaction as valid, leading to:

    • Duplicate calls to addMigratedDepositsToAccount, potentially double-crediting deposits to Bob's account:

LINK:

```solidity
accountStalk += addMigratedDepositsToAccount(reciever, deposits[i]);
```
  • Repeated execution of setStalk, incorrectly increasing Bob's stalk and roots balances:

    setStalk(reciever, accountStalk, ownerRoots);

This bug could be exploited multiple times, leading to inflated balances for the receiver (Bob in this case).

The impact is further exacerbated by the fact that the redeemDepositsAndInternalBalances function is marked as payable and uses nonReentrant, suggesting it handles valuable assets and state changes as seen here:

function redeemDepositsAndInternalBalances(
// ... parameters ...
) external payable fundsSafu noSupplyChange nonReentrant {
// ... function body ...
}

Tools Used

Manual code review

Recommendations

Consider adding a nonce to the signature data structure. Include the nonce in the signed message. Then verify and increment the nonce during signature verification. Additionally, consider exposing a function that marks the current nonce as used, providing a means for the user to revoke signatures.

Another way to fix this is by using OpenZeppelin library for signature verification.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Replay attack vulnerability in `redeemDepositsAndInternalBalances` function - it could be replayed

Appeal created

deadrosesxyz Judge
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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