浅谈智能合约evilReflex漏洞
字数 1032 2025-08-22 12:23:12
智能合约evilReflex漏洞深度解析与防御指南
0x01 漏洞概述
漏洞名称:evilReflex漏洞(call注入攻击)
漏洞危害:攻击者可通过该漏洞将存在漏洞的合约中的任意数量代币转移到任意地址,或执行其他未经授权的操作
影响范围:多个基于ERC223标准的智能合约
0x02 预备知识
智能合约的外部调用方式
call基础用法
<address>.call(bytes) // 基本调用方式
<address>.call(函数选择器, arg1, arg2, ...) // 带参数的调用
函数选择器(Function Selector)
函数选择器是函数签名的Keccak哈希的前4字节。例如:
function baz(uint32 x, bool y) public pure returns (bool r) {
r = x > 32 || y;
}
计算函数选择器:
sha3.keccak256(b'baz(uint32,bool)').hexdigest()[0:8] // 结果为cdcd77c0
0x03 漏洞原理
基本攻击模式
contract evilreflex {
function info(bytes data) {
this.call(data);
}
function secret() public {
require(this == msg.sender); // 只能合约自身调用
// 敏感操作...
}
}
攻击方法:
this.call(bytes4(keccak256("secret()"))); // 绕过权限检查
bytes注入攻击
function approveAndCallcode(address _spender, uint256 _value, bytes _extraData) returns (bool success) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
if(!_spender.call(_extraData)) {
revert();
}
return true;
}
攻击者可利用:
- 调用
address.transfer()转移代币 - 调用
approval()实现任意代币授权
方法选择器注入
function logAndCall(address _to, uint _value, bytes data, string _fallback) {
...
assert(_to.call(bytes4(keccak256(_fallback)), msg.sender, _value, _data));
...
}
EVM的call函数特性:
- 自动忽略多余参数
- 允许参数类型不严格匹配
示例:
pragma solidity ^0.4.0;
contract A {
uint256 public aa = 0;
function test(uint256 a) public {
aa = a;
}
function callFunc() public {
this.call(bytes4(keccak256("test(uint256)")), 10, 11, 12);
}
}
尽管test()只需要1个参数,但传入3个参数仍能成功调用。
0x04 真实案例分析:ATN代币增发漏洞
漏洞代码
function transferFrom(address _from, address _to, uint256 _amount, bytes _data, string _custom_fallback)
public returns (bool success) {
...
if (isContract(_to)) {
ERC223ReceivingContract receiver = ERC223ReceivingContract(_to);
receiver.call.value(0)(bytes4(keccak256(_custom_fallback)), _from, _amount, _data);
}
...
}
function setOwner(address owner_) public auth {
owner = owner_;
emit LogSetOwner(owner);
}
modifier auth {
require(isAuthorized(msg.sender, msg.sig));
_;
}
function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
if (src == address(this)) {
return true;
} else if (src == owner) {
return true;
} else if (authority == DSAuthority(0)) {
return false;
} else {
return authority.canCall(src, this, sig);
}
}
攻击步骤
-
调用
transferFrom()函数,设置:_custom_fallback= "setOwner(address)"_from= 攻击者地址_to= 合约自身地址
-
执行流程:
receiver.call()将msg.sender设为合约地址- 绕过
isAuthorized()检查(因为src == address(this)) - 成功调用
setOwner()修改合约所有者
0x05 漏洞防御方案
最佳实践
-
避免使用call函数:尽可能使用更安全的调用方式如
transfer()或send() -
严格控制参数:如果必须使用call,确保传入的参数不可被外部完全控制
-
使用固定函数选择器:避免动态生成函数选择器
-
严格权限检查:不要仅依赖
msg.sender == address(this)作为权限验证
安全代码示例
// 安全版本的approveAndCallcode
function safeApproveAndCallcode(address _spender, uint256 _value, bytes _extraData) returns (bool) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
// 限制可调用的函数
bytes4 allowedFunc = bytes4(keccak256("receiveApproval(address,uint256,bytes)"));
require(_extraData.length >= 4 && bytes4(_extraData) == allowedFunc);
(bool success, ) = _spender.call(_extraData);
require(success);
return true;
}
0x06 相关漏洞扩展
evilReflex漏洞属于call函数滥用的一类问题,其他相关漏洞包括:
- 重入攻击(Reentrancy):通过fallback函数递归调用
- DoS攻击:通过call失败导致整个交易回滚
- Gas限制攻击:通过复杂call消耗所有gas
开发者应全面了解call函数的风险,在必要时使用更安全的模式如"检查-生效-交互"(Checks-Effects-Interactions)模式。