Sablier

Sablier
DeFiFoundry
53,440 USDC
View results
Submission Details
Severity: low
Valid

Merkle Tree related contracts will be subject to Cross Chain Replay attacks

Summary

Considering Sablier is compatible with any evm chain, this unfortunately opens up the below scenario.
In multi-chain deployments, the claim function in the SablierV2MerkleLL & SablierV2MerkleLT contract is vulnerable to front-running and Cross Chain replay attacks.

Vulnerability Details

The claim function in SablierV2MerkleLL relies on Merkle proofs to validate claims. The function generates a Merkle tree leaf by hashing the claim parameters, which are then checked against the Merkle root.

function claim(uint256 index, address recipient, uint128 amount, bytes32[] calldata merkleProof)
external override returns (uint256 streamId) {
// Generate the Merkle tree leaf by hashing the corresponding parameters.
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(index, recipient, amount))));
// Check: validate the function.
_checkClaim(index, leaf, merkleProof);
// Effect: mark the index as claimed.
_claimedBitMap.set(index);
// Interaction: create the stream via {SablierV2LockupLinear}.
streamId = LOCKUP_LINEAR.createWithDurations(
LockupLinear.CreateWithDurations({
sender: admin,
recipient: recipient,
totalAmount: amount,
asset: ASSET,
cancelable: CANCELABLE,
transferable: TRANSFERABLE,
durations: streamDurations,
broker: Broker({ account: address(0), fee: ud(0) })
})
);
// Log the claim.
emit Claim(index, recipient, amount, streamId);
}

Multi-Chain Deployment and Forks:

The SablierV2MerkleLL contract is designed to be deployed on multiple EVM-compatible chains.
This creates a risk scenario where the same Merkle proof could be used to claim tokens on different chains or forks.

An attacker monitors the mempool across different chains. When a recipient submits a claim transaction on one chain, the attacker replicates it on another chain where the contract is also deployed. This allows the attacker to potentially claim tokens on the other chain before the legitimate recipient.

During a chain fork, the attacker monitors transactions on the shorter fork. If a recipient’s claim transaction appears on the shorter fork, the attacker can replicate it on the longer fork. The attacker's transaction on the longer fork is processed, allowing them to claim tokens before the legitimate recipient when the shorter fork is discarded.

Impact

If a claim is made with an incorrect recipient address due to differences across chains, the tokens will be lost or misdirected.

Tools Used

Foundry

Recommendations

EIP-712 Signatures.

Use EIP-712 signatures to ensure each claim is unique to the specific chain and contract. This prevents attackers from replaying transactions across different chains.

// Example EIP-712 implementation
bytes32 domainSeparator = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("SablierV2MerkleLL")),
keccak256(bytes("1")),
block.chainid,
address(this)
)
);
bytes32 structHash = keccak256(
abi.encode(
keccak256("Claim(uint256 index,address recipient,uint128 amount)"),
index,
recipient,
amount
)
);
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
domainSeparator,
structHash
)
);
address signer = ecrecover(digest, v, r, s);
require(signer == recipient, "Invalid signature");

Chain ID Verification
Include chain ID verification within the claim function to ensure that claims are only valid on the intended chain.

function claim(uint256 index, address recipient, uint128 amount, bytes32[] calldata merkleProof, uint256 chainId)
external override returns (uint256 streamId) {
require(chainId == block.chainid, "Invalid chain ID");
// Generate the Merkle tree leaf by hashing the corresponding parameters.
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(index, recipient, amount, chainId))));
// Check: validate the function.
_checkClaim(index, leaf, merkleProof);
// ... rest of the logic
}
Updates

Lead Judging Commences

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

Cross Chain Replay Attacks

bladesec Auditor
about 1 year ago
ethersky Auditor
about 1 year ago
0xaman Auditor
about 1 year ago
nmirchev8 Auditor
about 1 year ago
0xspryon Auditor
about 1 year ago
0xgenaudits Judge
about 1 year ago
golanger85 Submitter
about 1 year ago
ethersky Auditor
about 1 year ago
golanger85 Submitter
about 1 year ago
golanger85 Submitter
about 1 year ago
ethersky Auditor
about 1 year ago
holydevoti0n Judge
about 1 year ago
golanger85 Submitter
about 1 year ago
bladesec Auditor
about 1 year ago
golanger85 Submitter
about 1 year ago
golanger85 Submitter
about 1 year ago
bladesec Auditor
about 1 year ago
golanger85 Submitter
about 1 year ago
golanger85 Submitter
about 1 year ago
golanger85 Submitter
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Cross Chain Replay Attacks

Support

FAQs

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