sherlock-audit / 2024-09-symmio-v0-8-4-update-contest-judging

0 stars 0 forks source link

safdie - Signature replay attack in `forceClosePosition` function allow an attacker reuse a valid signature in `ForceActionsFacet.sol` #10

Open sherlock-admin4 opened 1 week ago

sherlock-admin4 commented 1 week ago

safdie

High

Signature replay attack in forceClosePosition function allow an attacker reuse a valid signature in ForceActionsFacet.sol

Summary

In decentralized applications that rely on signatures to authorize actions, signature replay attacks can occur if proper protections are not implemented. A replay attack happens when a valid transaction signed by a user is captured by a malicious actor and reused (replayed) to perform the same action without proper validation (such as nonce checks or signature expiration). The forceClosePosition function in the contract relies on a signature from Muon (or another off-chain oracle) to close a position. If no protection against replay attacks is implemented, a valid signature can be reused indefinitely.

Root Cause

In the forceClosePosition() https://github.com/sherlock-audit/2024-09-symmio-v0-8-4-update-contest/blob/main/protocol-core/contracts/facets/ForceActions/ForceActionsFacet.sol#L43-L60 and settleAndForceClosePosition() https://github.com/sherlock-audit/2024-09-symmio-v0-8-4-update-contest/blob/main/protocol-core/contracts/facets/ForceActions/ForceActionsFacet.sol#L69-L99 functions, there is a use of the Muon signature (from the HighLowPriceSig struct) for price validation. However, there is no apparent mechanism to prevent signature replay attacks. An attacker could reuse a valid signature to repeatedly call these functions, forcefully closing positions or settling quotes multiple times.

Same in ForceActionsFacetImpl.sol contract, while there are various checks like ensuring the validity of the signature (LibMuonForceActions.verifyHighLowPrice), nonce management is only partially implemented. The nonces for both partyA and partyB are updated in the code: https://github.com/sherlock-audit/2024-09-symmio-v0-8-4-update-contest/blob/main/protocol-core/contracts/facets/ForceActions/ForceActionsFacetImpl.sol#L90-L91 However, there's no check to verify that the nonce has not been used already. Without checking the nonce before signature verification, a replay attack could occur. An attacker could reuse a valid signature with an old nonce, effectively replaying a transaction that has already been executed. The attacker could intercept a valid signature and replay it by calling forceClosePosition with the same parameters and an outdated nonce. Without nonce validation before action execution, the system would accept the replayed signature.

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

PoC below demonstrates a signature replay attack where an attacker reuses a valid signature to repeatedly force-close a position.

Attack execution:

  1. Deploy the ForceActionsFacet contract.
  2. Deploy the ReplayAttack contract, passing the address of ForceActionsFacet and a valid HighLowPriceSig signature.
  3. Call replayAttack(quoteId) from the ReplayAttack contract.

Expected behavior: If no replay protection is implemented, like in ForceActionsFacet.sol, the attacker will be able to repeatedly call forceClosePosition on the same quoteId, replaying the signature and triggering the ForceClosePosition event multiple times. Each call will unnecessarily update the status of the same quote, causing unexpected behavior in the system.

Actual Impact:

  1. The same position can be force-closed multiple times, which might lead to incorrect balances, unexpected state updates, and system-wide inconsistencies.
  2. If the closing of a position results in settlement or liquidation, replaying the signature can cause unintended liquidations or withdrawals of funds.
  3. If the contract continues processing the repeated requests, it could cause unnecessary load on the system, preventing legitimate actions from being processed.

Impact

  1. An attacker could replay the same valid signature multiple times to force-close positions repeatedly, potentially leading to a system that processes incorrect or unwanted actions. For example, if the signature is valid once, but used again, it might trigger duplicate force-close requests or multiple actions on the same quote.
  2. If force-closing a position is tied to liquidations, settlement, or other financial consequences, an attacker could repeatedly trigger these actions, leading to incorrect liquidations or funds being drained, causing financial loss for the affected parties.
  3. By continuously replaying a valid signature, an attacker could flood the system with requests, causing network congestion and preventing legitimate users from processing valid transactions.

PoC

pragma solidity ^0.8.18;

contract ForceActionsFacet {
    // Sample function from ForceActionsFacet vulnerable to replay attacks
    function forceClosePosition(uint256 quoteId, HighLowPriceSig memory sig) external {
        // Assuming this function relies on sig for authorization
        require(verifySignature(sig), "Invalid signature");

        // Rest of the force-close logic
        QuoteStorage.Layout storage quoteLayout = QuoteStorage.layout();
        Quote storage quote = quoteLayout.quotes[quoteId];

        // Check if the quote is already closed to avoid reprocessing
        require(quote.quoteStatus != QuoteStatus.CLOSED, "Quote already closed");

        // Force-close the position
        quote.quoteStatus = QuoteStatus.CLOSED;
        emit ForceClosePosition(quoteId, msg.sender, quote.partyA, quote.partyB);
    }

    // Signature verification function
    function verifySignature(HighLowPriceSig memory sig) internal pure returns (bool) {
        // Simplified signature check for PoC
        return sig.isValid;
    }
}

contract ReplayAttack {
    ForceActionsFacet forceActionsFacet;
    HighLowPriceSig validSignature;

    constructor(address _forceActionsFacet, HighLowPriceSig memory _validSignature) {
        forceActionsFacet = ForceActionsFacet(_forceActionsFacet);
        validSignature = _validSignature;
    }

    // Malicious function to replay the valid signature and force-close the position multiple times
    function replayAttack(uint256 quoteId) external {
        for (uint256 i = 0; i < 10; i++) {
            // Replaying the same signature to close the position multiple times
            forceActionsFacet.forceClosePosition(quoteId, validSignature);
        }
    }
}

Mitigation

  1. Include an expiration timestamp in the signed data, ensuring the signature is only valid for a limited time.

    function forceClosePosition(uint256 quoteId, HighLowPriceSig memory sig) external {
    require(block.timestamp < sig.expiration, "Signature expired");
    require(verifySignature(sig), "Invalid signature");
    
    // Proceed with force close logic
    }
  2. Maintain a mapping of processed transactions or signatures to prevent them from being reused.
    
    mapping(bytes32 => bool) public processedSignatures;

function forceClosePosition(uint256 quoteId, HighLowPriceSig memory sig) external { bytes32 signatureHash = keccak256(abi.encodePacked(sig)); require(!processedSignatures[signatureHash], "Signature already processed"); processedSignatures[signatureHash] = true;

// Proceed with force close logic

}