区块链黑客第三讲:call注入攻击

2018年6月20日,ATN代币团队发布《ATN抵御黑客攻击的报告》,报告指出黑客利用call注入攻击漏洞修改合约拥有者,然后给自己发行代币,从而造成 ATN 代币增发,造成数千万美金的损失。

call()函数,对于写合约的人来说并不陌生,对合约感兴趣的呢更是必备了解。

研究一个函数的攻击点,要从它的特性下手。

📕call() 函数的特性

call()是调用第三方合约函数的底层接口,有两种方式可以调用它:

方式一:call(方法选择器, arg1, arg2, …)

pragma solidity ^0.8.0;

contract A{

    address public owner;
   
    constructor(){
     owner = msg.sender;
    }
    
    modifier onlyOwner(){
        require(msg.sender == owner,"only owner can call this func");
        _;
    }

    function useCall() public onlyOwner{

        (bool ret,) = B.call(abi.encodeWithSignature("setNum(uint256)", 10,"12"));
    }

}

contract B {
    
    uint256 public num;

    function setNum(uint256 _num) public { 
        if(msg.sender != A.owner){
            revert();
        }
        num = _num;
    }

}

方式二:call(bytes)

pragma solidity ^0.8.0;

contract A{

    address public owner;
   
    constructor(){
     owner = msg.sender;
    }
    
    modifier onlyOwner(){
        require(msg.sender == owner,"only owner can call this func");
        _;
    }

    function useCall() public onlyOwner{

        (bool ret,) = B.call(bytes(keccak256("setNum(uint256)")),10,"12");
    }

}

contract B {
    
    uint256 public num;

    function setNum(uint256 _num) public { 
        if(msg.sender != A.owner){
            revert();
        }
        num = _num;
    }

}

以上两个例子都是在合约A中使用call调用合约B的func()函数。

需要注意的是,func()函数只接受一个参数_num,但在调用中我们填入了数字10和字符串"12"这两个参数,但call()将会识别过滤掉多余的参数,将数字10作为调用参数。

这便是该call的第一个特性!

⚽特性一

call()可以接受任何长度、任何类型的参数,其传入的参数会被填充至 32 字节最后拼接为一个字符串序列,由 EVM 解析执行;

并且call()函数能够自动过滤掉多余的参数。

再回到上面的两个例子,在setNum()函数中我加了一个判断 if(msg.sender != A.owner)来确定调用者是否为合约B的拥有者,实际上调用者一定会是合约B的拥有者,这便是call()的第二个重要特性。

⭐特性二(重要)

在call()调用的过程中,Solidity中的内置变量 msg 会随着调用的发起而改变,msg 保存了调用方的信息包括:

  • 调用发起的地址(msg.sender)
  • 交易金额(msg.value)
  • 被调用函数标识符(msg.sig)

使用call()进行跨合约的函数调用后,内置变量 msg 的值会修改为调用者,执行环境为被调用者的运行环境

利用call() 特性

利用call函数的特性,可以在特定的场景中触发巨大安全漏洞。

  • 一是权限绕过
  • 二是代币窃取

🥁经典攻击模型——权限绕过

这是一个非常经典的攻击模型(现实中将会复杂数倍,但结构相同);

contract CallBug{

    function callFunc(bytes data) public{
        this.call(data);
    }
    
    //内部转账函数
    function authorityTranser(uint256 _amount) internal{
        //该方法要求调用者是本合约
        require(this == msg.sender);
        
        //一系列转账操作...
    }
}

攻击思路

  1. 在某些情况下,合约将会预留一个接口函数 callFunc 方便后续操作,我们可以利用 特意构造好的字节数据 传入callFunc接口调用authorityTranser() 转账函数;
  2. 利用特性二: msg.sender 此时会等于 this.address,使我们成功绕过require(this == msg.sender);
  3. 发生一系列转账操作;
example

调用callFunc传入参数:bytes(keccak256("authorityTranser(uint256)"), 1)

🥁经典攻击模型——代币窃取

contract CallBug{
    function transfer(address _to, uint256 _value) public {
        require(_value <= balances[msg.sender]);
        balances[msg.sender] -= _value;
        balances[_to] += _value;
    }

    function callFunc(bytes data) public {
        this.call(data);
        //this.call(bytes4(keccak256("transfer(address,uint256)")), target, value); //利用代码示意
    }
}

攻击思路同上

example

调用callFunc传入参数:bytes(keccak256("authorityTranser(uint256)"), 1)

预防安全漏洞

  1. call调用的自由度极大,并且call会发生msg值的改变,需要谨慎的使用这些底层的函数;同时在使用时,需要对调用的合约地址、可调用的函数做严格的限制。

  2. call调用会改变msg的值,会修改msg.sender为调用者合约的地址,所以在合约中不能轻易将合约本身的地址作为可信地址。

  3. 如果合约逻辑无法避免跨合约的函数调用,可以采用 new 合约,并指定 function_selector 的方式,指定调用的合约及合约方法,并做好函数参数的检查。

  constructor() {
        b =  new  B();
    }

🚀扩展阅读

ERC223

ERC223标准中解决了很多ERC20标准中一些潜在的问题,同时该标准也代入了Call注入问题。

//例如ERC223的转账函数
    function transfer(address to,uint value,bytes data,string custom_fallback) public returns (bool success){
    _transfer(msg.sender,to,value,data);

    if(isCoontract(to)){
        ContractReceiver rx = ContractReceiver(to);
        require(address(rx).call.value(0)(bytes4(keccak256(custom_fallback)),msg.sender,value,data),"not success!");
     }
}

其中 require(address(rx).call.value(0)(bytes4(keccak256(custom_fallback)),msg.sender,value,data),"not success!");就是对call注入攻击的预防判断;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值