美文网首页
区块链Solidity安全-call函数滥用漏洞

区块链Solidity安全-call函数滥用漏洞

作者: romakingwolf | 来源:发表于2018-08-23 16:46 被阅读0次

call函数介绍

在Solidity中,可以使用call、delegatecall、callcode三个函数实现跨合约的函数调用功能。

这里主要介绍call函数的使用,call函数的调用模型:

<address>.call(...) returns (bool)

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

在call函数调用的过程中,Solidity中的内置变量 msg 会随着调用的发起而改变,msg 保存了调用方的信息包括:调用发起的地址,交易金额,被调用函数字符序列等。

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

通过下面的例子,演示call函数的调用:

pragma solidity ^0.4.0;

contract A {
    address public temp1;
    uint256 public temp2;

    function fcall(address addr) public {
        temp1 = msg.sender;
        temp2 = 100;
        addr.call(bytes4(keccak256("test()")));
    }
}

contract B {
    address public temp1;
    uint256 public temp2;

    function test() public  {
        temp1 = msg.sender;
        temp2 = 200;
    }
}

在Remix中进行部署、调用测试:

  1. 部署合约,A合约地址: 0x100eee74459cb95583212869f9c0304e7ce11eaa , B合约地址: 0xe90f4f8aeba3ade774cac94245792085a451bc8e

    remix_1.png
  2. 调用A合约的fcall函数,使用B合约的地址作为参数


    remix_2.png
  3. 分别查看A合约和B合约的temp1和temp2变量的值:

  • A合约的temp1:0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c
  • A合约的temp2:100
  • B合约的temp1:0x100eeE74459CB95583212869f9c0304e7cE11EAA
  • B合约的temp2:200


    remix_3.png

通过上面的例子,可以看出:

在A合约中,msg.sender = address(调用者) ;在A合约中调用B合约的函数,函数中, msg.sender = address(A合约地址)

同时,在A合约中调用B合约的函数,调用的运行环境是被调用者的运行环境,即是B合约的运行环境。

call函数滥用漏洞说明

利用call函数滥用漏洞,可以执行 call注入 攻击。call注入 是一种新的攻击方式,原因是对call调用处理不当,配合一定的应用场景的一种攻击手段。

通常情况下,跨合约间的函数调用会使用call函数,由于call在相互调用过程中内置变量msg会随着调用方的改变而改变,这就成为了一个安全隐患,在特定的应用场景下将引发安全问题,被恶意攻击者利用,施行 call注入 攻击。

call函数的调用方式:

<address>.call(function_selector, arg1, arg2, ...)
<address>.call(bytes)

call函数拥有极大的自由度:

  1. 对于一个指定合约地址的call调用,可以调用该合约下的任意函数
  2. 如果call调用的合约地址由用户指定,那么可以调用任意合约的任意函数

同时,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);
    }
}

call函数调用中的参数11,12将会被自动忽略,test函数中 aa = 10

call注入攻击模型

下面的例子展示了call注入模型:

contract A {
    function info(bytes data) public{
        this.call(data);
        //this.call(bytes4(keccak256("secret()"))); //利用代码示意
    }
    function secret() public{
        require(this == msg.sender);
        // secret operations
    }
}

在合约A中存在 info()secret() 函数,其中 secret() 函数只能由合约自己调用,在 info() 中有用户可以控制的call调用,用户精心构造传入的数据(将注释转为字节序列),即可绕过 require() 的限制,成功执行 secret() 下面的代码。

call注入攻击引起的安全问题

权限绕过

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

function withdraw(address addr) public {
    require(isAuth(msg.sender));
    addr.transfer(this.balance);
}

function isAuth(address src) internal view returns (bool) {
    if (src == address(this)) {
        return true;
    }
    else if (src == owner) {
        return true;
    }
    else {
        return false;
    }
}

通过精心构造 callFunc() 的传入参数(如:this.call(bytes4(keccak256("withdraw(address)")), target); ),恶意攻击者可以绕过函数 withdraw() 的权限验证。

窃取代币

在代币合约中,往往会加入一个call回调函数,用于通知接收方以完成后续的操作。但由于call调用的特性,用户可以向call传入 transfer() 函数调用,即可窃取合约地址下代币。

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); //利用代码示意
}

漏洞预防

预发call函数滥用漏洞的最好方式是尽量减少使用call函数。

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

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

相关文章

网友评论

      本文标题:区块链Solidity安全-call函数滥用漏洞

      本文链接:https://www.haomeiwen.com/subject/bwkmiftx.html