Arbitrary ERC-20 Transfer Remediation
Overview
A contract that holds ERC-20 tokens and exposes a transfer function whose recipient and amount come from calldata, with no authorization, can be drained by anyone. An attacker simply calls it with their own address and the contract’s full balance. The fix is to require authorization before any token leaves the contract.
Related Detector: Arbitrary ERC-20 Transfer
Recommended Fix
Before (Vulnerable)
// Anyone can transfer tokens out
function sendTokens(address token, address to, uint256 amount) external {
IERC20(token).transfer(to, amount);
}
After (Fixed)
// Only owner can transfer
function sendTokens(address token, address to, uint256 amount) external onlyOwner {
IERC20(token).transfer(to, amount);
}
The onlyOwner modifier reverts for any caller other than the authorized account, so the recipient and amount can no longer be set by an attacker. Use a role check instead of a single owner when several accounts must be able to move funds.
Alternative Mitigations
- Use OpenZeppelin
AccessControlwith a dedicated role for treasury movements rather than a single owner. - Constrain the destination to a fixed allowlist of addresses when the set of valid recipients is known.
- Route withdrawals through a pull pattern where each account can only claim its own recorded balance.
Common Mistakes
- Adding a check on the token address but leaving the recipient and amount fully caller-controlled.
- Using
tx.origininstead ofmsg.senderfor the authorization check, which is phishable. - Ignoring the boolean return value of
transfer, so a failed transfer is treated as success; useSafeERC20.