前言
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的注入问题,需要检查确认。
(作者:区块链安全档案。本文仅代表作者观点,不代表链得得官方立场。)
郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。
郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。