Ignoring ChainID in Signatures
id: LS18H
title: Ignoring ChainID in Signatures
baseSeverity: H
category: signature-verification
language: solidity
blockchain: [ethereum]
impact: Valid signatures from one chain may be replayed on another chain
status: draft
complexity: medium
attack_vector: cross-chain
mitigation_difficulty: easy
versions: [">=0.6.0", "<latest"]
cwe: CWE-347
swc: SWC-121
๐ Description
- Ignoring
chainIdin signature validation allows malicious actors to replay valid signed messages across different EVM-compatible chains. - When signature schemes do not bind the signed message to a specific chain, the same signed payload (e.g.,
approve(),mint(),withdraw(),executeMetaTx()) can be reused on another chain, resulting in: - Duplicate executions,
- Drained balances or unauthorized operations,
- Critical multi-chain desynchronization bugs.
๐จ Vulnerable Code
function executeMetaTx(address user, bytes calldata data, bytes calldata signature) external {
bytes32 hash = keccak256(abi.encodePacked(user, data)); // โ No chainId binding
require(recover(hash, signature) == user, "Invalid signature");
(bool success, ) = address(this).call(data);
require(success, "Execution failed");
}
๐งช Exploit Scenario
Step-by-step attack:
- A user signs a valid meta-transaction on Chain A (e.g., Polygon).
- Attacker captures the signature and resubmits it on Chain B (e.g., BNB Chain).
- Since the message is valid and chainId was not part of the hash, it executes successfully.
- If the protocol uses the same contract on both chains, attacker replays the action (e.g., withdraw, claim, mint) and drains tokens on the second chain.
Assumptions:
- Signature payload is not bound to a specific chainId.
- The same contract logic exists on multiple chains with similar state.
โ Fixed Code
function getMetaTxHash(address user, bytes calldata data, uint256 nonce, uint256 chainId) public pure returns (bytes32) {
return keccak256(abi.encodePacked(user, data, nonce, chainId));
}
function executeMetaTx(address user, bytes calldata data, uint256 nonce, bytes calldata signature) external {
bytes32 hash = getMetaTxHash(user, data, nonce, block.chainid); // โ
Includes chainId
require(recover(hash, signature) == user, "Invalid signature");
// Execute logic
}
๐งญ Contextual Severity
- context: "Permit or approval logic used across multiple chains"
severity: H
reasoning: "Allows unauthorized replay and token manipulation on unintended chains"
- context: "Only used for internal chain logic or EOA-bound tx"
severity: M
reasoning: "Risk lower but still affects forked chains or users switching networks"
- context: "Proper EIP-712 implementation with chainId bound"
severity: I
reasoning: "No signature replay risk"
๐ก๏ธ Prevention
Primary Defenses
- Always include block.chainid (or EIP-712 domain with chain ID) in the signed message.
- Use EIP-712 domain separators that bind to:
- name
- version
- chainId
- verifyingContract
Additional Safeguards
- Reject messages where chainId does not match block.chainid.
- Monitor for repeated signatures across chains (using bridges or relayer infra).
- Use EIP-712 or eth_signTypedDataV4 for all critical signature flows.
Detection Methods
- Slither: missing-chainid-in-hash, signature-replay-cross-chain detectors.
- Manual audit of all signature logic for keccak256(...) and ecrecover(...).
- Test signature replay scenarios across forked chain environments.
๐ฐ๏ธ Historical Exploits
- Name: EPProgramManager Replay Vulnerability
- Date: November 2024
- Loss: Not publicly disclosed
- Post-mortem: Link to post-mortem
๐ Further Reading
- SWC-121: Missing Protection Against Signature Replay
- EIP-712: Typed Structured Data Hashing
- EIP-2612: Permit with Nonce and ChainID
- Slither โ Signature Replay & Domain Detectors
โ Vulnerability Report
id: LS18H
title: Ignoring ChainID in Signatures
severity: H
score:
impact: 5
exploitability: 4
reachability: 3
complexity: 2
detectability: 4
finalScore: 4.2
๐ Justifications & Analysis
- Impact: Critical โ cross-chain replay can lead to theft or duplicated actions.
- Exploitability: Trivial for an attacker once the signature is observed.
- Reachability: Found in many multi-chain deployments and unguarded signature flows.
- Complexity: Simple to fix but easy to miss during development.
- Detectability: High โ static tools and manual review will surface it.