基础知识
Call是一个底层的接口,用来向第三方合约发送消息以此完成第三方合约中内部函数的调用,调用的方式大致如下
方式一:call(方法选择器, arg1, arg2, …) 方式二:call(bytes)
第一种通过参数传递的方式调用,将方法选择器,参数进行传递,其中方法选择器是4个字节,通过bytes4(keccak256(func()))生成。第二种方式直接调用,bytes数据由自己来构建,标准场景如下:
pragma solidity ^0.4.10;
contract ContractA{
event Result(bool);
function extCall(address _sc) {
bool ret = _sc.call(bytes(keccak256("func()")));
Result(ret);
}
}
contract ContractB {
function func() { revert();}
}
攻击场景
注入漏洞可以从三个方向进行攻击
1. 参数列表
2. 方法选择器
3. bytes
下面我们将进行bytes注入方式模拟攻击
function approveAndCallCode(address _spender, uint256 _value, bytes _extraData) public returns(bool) {
...
if(!_spender.call(_extraData)) { revert();}
...
}
function transfre(address _to, uint256 _value) public returns (bool) {
...
}
在合约代码函数 approveAndCallCode中,通过_spender.call 调用_spender合约的某些方法并传递一些数据。
如果_spender为合约自身的地址,那么就可以调用本合约内部的方法,比如调用transfer方法,通过构造bytes数据,可以将_to 地址指向本身账户地址,通过这种方式就可以将合约账户中的代币轻而易举转到自己账户中。
ERC223标准为了解决ERC20中的一些潜在问题而出的升级版,但很多ERC223的标准实现中就带入了Call注入问题
总结
针对call本身所存在的一些潜在风险,在合约中尽量避免使用call的方式调用,如果一定要执行的话,则可以对方法选择器的字符串进行强制的限制。