ERC1155 Unsafe BatchTransfer
id: LS35M
title: ERC1155 Unsafe BatchTransfer
baseSeverity: M
category: token-transfer
language: solidity
blockchain: [ethereum]
impact: Loss of tokens, skipped hooks, or denial of service
status: draft
complexity: medium
attack_vector: external
mitigation_difficulty: medium
versions: [">=0.5.0", "<=0.8.25"]
cwe: CWE-703
swc: SWC-135
π Description
- ERC-1155 defines safeBatchTransferFrom() as the standardized method to transfer multiple token IDs and amounts atomically. When projects implement non-standard batch transfer functions, or skip the required onERC1155BatchReceived() hook, it creates serious risks:
- Tokens are lost when transferred to contracts that expect proper hook invocation.
- Receiver logic is skipped, breaking accounting or trustless asset reception.
- Malicious contracts can front-run transfers or trap tokens by rejecting callbacks.
- Most issues arise from:
- Implementing a raw batchTransfer() function without safe mechanics
- Using transferFrom() patterns from ERC20/ERC721 without 1155 compliance
- Assuming msg.sender is always from, violating proxy or operator logic
π¨ Vulnerable Code
pragma solidity ^0.8.0;
contract UnsafeBatch1155 {
mapping(address => mapping(uint256 => uint256)) public balances;
function batchTransfer(address to, uint256[] calldata ids, uint256[] calldata amounts) external {
require(ids.length == amounts.length, "Length mismatch");
for (uint256 i = 0; i < ids.length; ++i) {
uint256 id = ids[i];
uint256 amt = amounts[i];
balances[msg.sender][id] -= amt;
balances[to][id] += amt;
}
// β Missing call to onERC1155BatchReceived
}
}
π§ͺ Exploit Scenario
- Alice calls batchTransfer() to send tokens to a contract that expects ERC1155 compliance.
- Because onERC1155BatchReceived() is never called, the receiving contract:
- Fails to register the tokens
- May revert when attempting to use them
- Alternatively, an attacker uses batchTransfer() to bypass a receiverβs accept logic, depositing tokens the receiver never intended to receive.
Assumptions:
- Transfer is made to a contract address.
- No hook is triggered, leading to broken logic or token loss.
β Fixed Code
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
contract SafeBatch1155 is ERC1155 {
constructor() ERC1155("https://api.example.com/meta/{id}.json") {}
function safeBatchTransfer(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) external {
safeBatchTransferFrom(msg.sender, to, ids, amounts, data); // β
standard and safe
}
}
π§ Contextual Severity
- context: "Batch transfer to arbitrary user-provided addresses"
severity: M
reasoning: "Tokens may be silently lost if recipient is not compatible"
- context: "Batch transfer only to known EOAs or whitelisted system contracts"
severity: L
reasoning: "Controlled environment reduces risk of token lock-up"
- context: "Transfer handled via audited, standards-compliant `safeBatchTransferFrom`"
severity: I
reasoning: "Conforms to ERC1155 with enforced safety"
π‘οΈ Prevention
Primary Defenses
- Never create custom batch transfer logic without calling onERC1155BatchReceived.
- Use OpenZeppelinβs ERC1155 or a compliant audited base contract.
Additional Safeguards
- Use Address.isContract(to) to guard transfers to contracts.
- Always match ids.length == amounts.length and validate amount > 0.
Detection Methods
- Review for non-standard batchTransfer() functions.
- Check that onERC1155BatchReceived() is called on external to addresses.
- Tools: Slither (erc1155-compliance), MythX, ConsenSys Diligence linter
π°οΈ Historical Exploits
- Name: Revest Finance FNFT Reentrancy Exploit
- Date: 2022-03-27
- Loss: ~$2
- Post-mortem: Link to post-mortem
π Further Reading
- ERC-1155 Specification
- OpenZeppelin ERC1155 Docs
- SWC-135: Incorrect Logic
- Trail of Bits β ERC Standards Gotchas
β Vulnerability Report
id: LS35M
title: ERC1155 Unsafe BatchTransfer
severity: M
score:
impact: 4
exploitability: 3
reachability: 4
complexity: 2
detectability: 4
finalScore: 3.55
π Justifications & Analysis
- Impact: Tokens may be irretrievably lost or misused.
- Exploitability: Any user can send tokens to break recipient logic.
- Reachability: Frequent in custom NFT contracts or bridging code.
- Complexity: Not difficult to understand, but easy to miss.
- Detectability: Highly detectable via review or standard detectors.