以太坊智能合约Call注入攻击

  • A+
所属分类:网络资源共享
阿里


推广者专属福利,新客户无门槛领取总价值高达2775元代金券,每种代金券限量500张,先到先得。

0x00 基础知识

以太坊中跨合约调用是指的合约调用另外一个合约方法的方式。为了好理解整个调用的过程,我们可以简单将调用发起方合约当做传统web世界的浏览器,被调用的合约看作webserver,而调用的msg则是http数据,EVM底层通过ABI规范来解码参数,获取方法选择器,然后执行对应的合约代码。

以太坊智能合约Call注入攻击

当然,实际上智能合约的执行一般在打包交易或者验证交易的时候发生,上面的比喻只是方便理解。

在solidity语言中,我们可以通过call方法来实现对某个合约或者本地合约的某个方法进行调用。

调用的方式大致如下:

  • <address>.call(方法选择器, arg1, arg2, …)
  • <address>.call(bytes)

如上所述,可以通过传递参数的方式,将方法选择器、参数进行传递,也可以直接传入一个字节数组,当然要自己去构造msg.data的结构。

Solidity编程中,一般跨合约调用执行方都会使用msg.sender全局变量来获取调用方的以太坊地址,从而进行一些逻辑判断等。

比如在ERC20标准中的transfer方法的实现中,就是使用msg.sender来作为扣款方:

  1. function transfer(address _to, uint256_value) returns (bool success) {
  2.     ….
  3.     balances[msg.sender]-= _value;
  4. balances[_to] += _value;
  5. ….
  6. }

0x01 攻击模型

Call方法注入漏洞,顾名思义就是外界可以直接控制合约中的call方法调用的参数,按照注入位置可以分为以下三个场景:

  1. 1. 参数列表可控
  2.     <address>.call(bytes4 selection, arg1, arg2, ...)
  3. 2. 方法选择器可控
  4.    <address>.call(bytes4selection, arg1, arg2, ...)
  5. 3. Bytes可控
  6.     <address>.call(bytesdata)
  7.     <address>.call(msg.data)

简单举个例子,比如存在一个合约B

  1. contract B{
  2.     function info(bytes data){
  3.           this.call(data) ;
  4.     }
  5.     function secret() public{
  6.         require(this ==msg.sender);
  7.         // secret operations
  8.     }
  9. }

其中有info和secret方法,secret方法中判断必须是合约自身调用才能执行。然而这里的info方法中有个call的调用,并且外界可以直接控制call调用的字节数组,因此如果外界精心构造一个data,这个data的方法选择器指定为secret方法,那么外部用户就可以以合约身份调用到这个secret方法,这样就会造成一定的风险。

0x02 具体场景

这里举两种实际的攻击场景:

(1)   bytes注入

在合约代码中,有个approveAndCallcode方法,这个方法中允许调用_spender合约的某些方法或者传递一些数据,通过引入了_spender.call来完成这个功能。

如果外界调用中指定_spender为合约自身的地址,就可以以合约的身份去调用合约中的某些方法。比如如果我们使用合约的身份去调用transfer方法:

只需要自己去构造bytes即可,比如把transfer的_to参数指定为我们自己的账户地址。这样其实就可以直接把合约账户中的代币全部转到自己的账户中,因为通过call注入,在transfer方法看来,msg.sender其实就是合约自己的地址。

 

(2)   方法选择器注入

比如这里有个logAndCall方法:

  1. function logAndCall(address _to, uint _value, bytes data, string_fallback){
  2.          …..
  3.          assert(_to.call(bytes4(keccak256(_fallback)),msg.sender, _value, _data)) ;
  4.          ……
  5. }

这里我们对_fallback参数可控,也就是说我们可以指定调用_to地址的任何方法,但是后面跟了三个参数,分别是msg.sender,_value, _data,类型分别为address,uint256以及bytes。那么我们是不是只能调用参数类型必须为这三个的方法呢?当然不是。这里涉及到EVM在处理calldata的一个特性。

比如Sample1合约中有个test方法,这个方法中有三个参数,都是uint256类型的。而Sample2通过call调用了Sample1的test方法,这里传入了5个参数,同样是可以调用成功的。这是因为EVM在获取参数的时候没有参数个数校验的过程,因此取到前三个参数1,2,3之后,就把4,5给截断掉了,在编译和运行阶段都不会报错。

利用这个特性,我们其实有很多攻击面,比如我们可以通过logAndCall中的call注入来调用approve

这里的approve方法有两个参数,而且类型为address和uint256,所以我们是可以调用成功的。这样就可以将合约账户中的代币授权给我们自己的账户了。

0x03 深远的问题

ERC223标准是为了解决ERC20中对智能合约账户进行转币场景缺失的问题,可以看作是ERC20标准的升级版。但是在很多ERC223标准的实现代码中就带入了call注入的问题

0x04 防护手段

针对本文提到的这个风险,作为开发者来说,需要对ERC223的实现进行排查,不要引入call注入问题,如果非要执行回调,则可以指定方法选择器字符串,避免使用直接注入bytes的形式来进行call调用。对于一些敏感操作或者权限判断函数,则不要轻易将合约自身的账户地址作为可信的地址。

CE安全网

发表评论

您必须登录才能发表评论!