ABI encodePacked Collision
id: TBA
title: LS29H encodePacked Collision
baseSeverity: H
category: encoding
language: solidity
blockchain: [ethereum]
impact: Signature spoofing, hash collision, or critical logic bypass
status: draft
complexity: low
attack_vector: external
mitigation_difficulty: medium
versions: [">=0.4.0", "<=0.8.25"]
cwe: CWE-180
swc: SWC-136
π Description
- abi.encodePacked() is a low-level encoding function that returns a tightly packed byte array without padding.
- While this is gas-efficient and useful in some scenarios (e.g., keccak256 hashing), it can introduce dangerous collisions when used with variable-length arguments such as strings, bytes, or uint concatenated with user-controlled input.
- A classic example: keccak256(abi.encodePacked(a, b)) may result in the same output for two different combinations of (a, b) due to tight packing, especially when types like string, bytes, and uint are concatenated without separators or fixed lengths.
- When used in signature verification, identity hashing, or uniqueness constraints, this can lead to:
- Forged signatures
- Identity collisions
- Front-running or replay attacks
π¨ Vulnerable Code
pragma solidity ^0.8.0;
contract CollidingHasher {
mapping(bytes32 => bool) public used;
function markUsed(string memory prefix, uint256 id) public {
bytes32 hash = keccak256(abi.encodePacked(prefix, id));
require(!used[hash], "Already used");
used[hash] = true;
}
}
π§ͺ Exploit Scenario
- Contract generates a unique hash using abi.encodePacked(userInput, amount) for anti-replay or uniqueness.
- Attacker finds multiple combinations of input parameters that result in the same keccak256 hash.
- They reuse or spoof a previously valid hash to bypass require(!used[hash]).
- This allows unauthorized actions, such as double minting, duplicated identity claims, or replayed off-chain signatures.
Assumptions:
- The contract uses encodePacked + keccak256 to construct unique identifiers or signed messages.
- The input includes dynamic-length or ambiguous types (string, bytes, uint, etc.).
- No delimiter or structured type separation is enforced.
β Fixed Code
pragma solidity ^0.8.0;
contract SafeHasher {
mapping(bytes32 => bool) public used;
function markUsed(string memory prefix, uint256 id) public {
bytes32 hash = keccak256(abi.encode(prefix, id)); // β
Use abi.encode with padding
require(!used[hash], "Already used");
used[hash] = true;
}
}
π§ Contextual Severity
- context: "Hash used in signature validation, claim ID, or unique key"
severity: H
reasoning: "Hash collision allows spoofing, multi-claiming, or key overwrites."
- context: "Hash used for low-risk UI or analytics"
severity: L
reasoning: "Impact minimal if not tied to security or fund movement."
- context: "abi.encode() or hashed single arguments only"
severity: I
reasoning: "Collision risk negligibleβwell-structured input."
π‘οΈ Prevention
Primary Defenses
- Use abi.encode() for all signature, hashing, and uniqueness purposes.
- Avoid abi.encodePacked() when dealing with multiple dynamic or user-controlled inputs.
Additional Safeguards
- Add delimiters or length prefixes when using encodePacked.
- Normalize input types or use struct encoding where possible.
- Validate all parameters thoroughly when decoding signed data.
Detection Methods
- Static analysis to flag abi.encodePacked() inside keccak256(...) or signature generators.
- Review hashing schemes involving variable user inputs.
- Tools: Slither (dangerous-packed-encoding), MythX, manual audit
π°οΈ Historical Exploits
- Name: Poly Network Exploit
- Date: 2021-08-10
- Loss: $610 million
- Post-mortem: Link to post-mortem
π Further Reading
- SWC-136: Unencrypted Sensitive Data
- Understanding Hash Collisions: abi.encodePacked in Solidity β Nethermind
- The trap of using encodePacked in Solidity β OpenZeppelin Forum
β Vulnerability Report
id: LS29H
title: ABI encodePacked Collision
severity: H
score:
impact: 4
exploitability: 4
reachability: 3
complexity: 2
detectability: 4
finalScore: 3.75
π Justifications & Analysis
- Impact: Can break uniqueness guarantees, signature schemes, and lead to fund duplication or misuse.
- Exploitability: Attackers only need to identify a collision pair to exploit weak encodePacked usage.
- Reachability: Hashes and signatures are typically user-input dependent and publicly callable.
- Complexity: Low complexity exploit, but requires input crafting or brute-force collisions.
- Detectability: Subtle, especially when developers use encodePacked for optimization without recognizing ambiguity.