以太坊合约中Call注入函数的安全性问题

区块链安全档案
区块链安全档案 机构得得号

Sep 17, 2018 隶属于曲速未来安全区,安全问题深度分析、一手威胁情报披露

摘要: Call函数自由度过大,应谨慎使用作为底层函数,对于一些敏感操作或者权限判断函数,则不要轻易将合约自身的账户地址作为可信的地址。

前言

Solidity 是一种用与编写以太坊智能合约的高级语言。Solidity 编写的智能合约可被编译成为字节码在以太坊虚拟机上运行。

在 Solidity 中提供了 call、delegatecall、callcode 三个函数来实现合约之间相互调用及交互。正是因为这些灵活各种调用,也导致了这些函数被合约开发者“滥用”,甚至“肆无忌惮”提供任意调用“功能”,导致了各种安全漏洞及风险。

2018.5.11,ATN 技术人员收到异常监控报告,显示 ATN Token 供应量出现异常,通过分析发现 Token 合约由于存在漏洞受到攻击。由于 ATN 代币的合约中的疏漏,该事件中 call 注入不但绕过了权限认证,同时还可以更新合约拥有者。

6月24号(2018),安比(SECBIT)实验室与轻信科技发现了不少 ERC827 合约实现存在类ATN Token 漏洞。黑客可以利用该漏洞,以合约的身份调用任意合约地址上的任意函数。

背景知识

Solidity中,通过call方法实现对某个合约或者合约的某个方法实现调用。

调用方式大致如下:

通过传递参数的方式,将方法选择器,参数等进行传递,需要自己构造msg.data的结构。

同时,Call函数具有极大的自由度,

对于一个指定合约地址的 call 调用,可以调用该合约下的任意函数

如果 call 调用的合约地址由用户指定,那么可以调用任意合约的任意函数

在Solidity编程中,一般跨合约调用执行方都会使用msg.sender全局变量来获取调用方的以太坊地址。由于合约中有函数以 msg.sender 作为关键变量,会引发一些安全问题。

攻击模型

针对call函数进行攻击是在call进行调用时,将msg.data的值转为调用者的地址。

举个例子

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

攻击场景

「隐形人真忙」在先知大会提出了两种实际的攻击场景:

(1) bytes注入

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

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

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

(2) 方法选择器注入

比如这里有个logAndCall方法:

这里我们对_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,所以是可以调用成功的。这样就可以将合约账户中的代币授权给自己的账户了。

案例分析

2018年5月11日中午,ATN技术人员收到异常监控报告,显示ATN Token供应量出现异常,迅速介入后发现Token合约由于存在漏洞受到攻击。

ATN Token合约采用的是在传统ERC20Token合约基础上的扩展版本ERC223,并在其中使用了 dapphub/ds-auth 库。采用这样的设计是为了实现以下几个能力:

1.  天然支持Token互换协议,即ERC20Token与ERC20Token之间的直接互换。本质上是发送ATN时,通过回调函数执行额外指令,比如发回其他Token。

2.  可扩展的、结构化的权限控制能力。

3.  Token合约可升级,在出现意外状况时可进行治理。

单独使用 ERC223 或者 ds-auth 库时,并没有什么问题,但是两者结合时,黑客利用了回调函数回调了setOwner方法,从而获得高级权限。

ERC223转账代码如下:

当黑客转账时在方法中输入以下参数:

该交易执行的时候 receiver 会被 _to(ATN合约地址) 赋值, ATN 合约会调用 _custom_fallback 即 DSAuth 中的 setOwner(adddress) 方法,而此时的 msg.sender 变为 ATN 合约地址,owner_参数为_from(黑客地址)。

ds-auth库中setOwner 代码如下:

此时 setOwner 会先验证 auth 合法性的,而 msg.sender 就是ATN的合约地址。setOwner 的 modifier auth 代码如下:

通过利用这个ERC223方法与DS-AUTH库的混合漏洞,在执行过程中,由于 call 调用自动忽略多余的参数,黑客的地址将作为 setOwner() 的参数成功执行到函数内部,与此同时,call 调用已经将 msg.sender 转换为了合约本身的地址,也就绕过了 isAuthorized() 的权限认证,黑客成功将合约的拥有者改为了自己。获取 owner 权限后,黑客发起另外一笔交易对 ATN 合约进行攻击,调用 mint 方法给另外一个地址发行 1100wATN。

总结

1.智能合约在部署前必须通过严格的审计和测试。

2.Call函数自由度过大,应谨慎使用作为底层函数,对于一些敏感操作或者权限判断函数,则不要轻易将合约自身的账户地址作为可信的地址。

3.调用的函数应该做严格的限制,避开调用任意函数的隐患。

4.用到类似ERC223推荐实现的custom_fallback和ds-auth的合约,或者说内置有其他权限控制得合约的以太坊Token,很可能也存在这个call的注入问题,需要检查确认。

(作者:区块链安全档案。本文仅代表作者观点,不代表链得得官方立场。)

链得得仅提供相关信息展示,不构成任何投资建议
本文系作者 区块链安全档案 授权链得得发表,并经链得得编辑,转载请注明出处、作者和本文链接

更多精彩内容,关注链得得微信号(ID:ChainDD),或者下载链得得App

分享到:

相关推荐

    评论(0

    Oh! no

    您是否确认要删除该条评论吗?

    分享到微信