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.
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:
Deploy the ForceActionsFacet contract.
Deploy the ReplayAttack contract, passing the address of ForceActionsFacet and a valid HighLowPriceSig signature.
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:
The same position can be force-closed multiple times, which might lead to incorrect balances, unexpected state updates, and system-wide inconsistencies.
If the closing of a position results in settlement or liquidation, replaying the signature can cause unintended liquidations or withdrawals of funds.
If the contract continues processing the repeated requests, it could cause unnecessary load on the system, preventing legitimate actions from being processed.
Impact
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.
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.
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
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
}
Maintain a mapping of processed transactions or signatures to prevent them from being reused.
mapping(bytes32 => bool) public processedSignatures;
safdie
High
Signature replay attack in
forceClosePosition
function allow an attacker reuse a valid signature inForceActionsFacet.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 andsettleAndForceClosePosition()
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 theMuon signature
(from theHighLowPriceSig
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 bothpartyA
andpartyB
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 callingforceClosePosition
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:
ForceActionsFacet
contract.ReplayAttack
contract, passing the address ofForceActionsFacet
and a validHighLowPriceSig
signature.replayAttack(quoteId)
from theReplayAttack
contract.Expected behavior: If no replay protection is implemented, like in
ForceActionsFacet.sol
, the attacker will be able to repeatedly callforceClosePosition
on the samequoteId
, replaying the signature and triggering theForceClosePosition
event multiple times. Each call will unnecessarily update the status of the same quote, causing unexpected behavior in the system.Actual Impact:
Impact
PoC
Mitigation
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 { bytes32 signatureHash = keccak256(abi.encodePacked(sig)); require(!processedSignatures[signatureHash], "Signature already processed"); processedSignatures[signatureHash] = true;
}