【grccoin交易充值申诉中是怎么回事?】 ERC223及ERC827实现代码欠缺安全考虑 —— ATN Token中
本文部分内容基于安比(SECBIT)实验室团队与吴玉会(轻信科技)的讨论
本文结论:ERC223, ERC827的部分实现代码引入了任意函数调用缺陷,可能会对使用这部分代码的合约带来安全漏洞。如果需要实现上述规范接口,请仔细检查实现代码。这种合约本身允许用户自定义 call() 任意地址上任意函数的设计,十分危险。攻击者可以很容易地借用当前合约的身份来进行任何操作,比如盗取Token或者绕开权限检查等。
影响范围:截止目前检测到以太坊上部署的受影响的ERC20合约数量:146
最新更新:
-
火币网已经暂停了已经上线交易的相关问题Token[9][10]
-
ATN团队已经修复漏洞[1]
CUSTOM_CALL 滥用事件回顾与分析
2018 年 6 月 20 日,AI Technology Network (ATN) 和慢雾团队披露了一起针对 ATN 智能合约的攻击事件,黑客于 2018 年 5 月 11 日利用 ATN Token 合约存在的漏洞,将自己地址设为 owner 并增发获利 1100 万 ATN。ATN 技术团队迅速发现问题、定位攻击方法并完成合约的升级修复 [1]。黑客利用了 ERC223 合约可传入自定义的接收调用函数与 ds-auth 权限校验等特征,在 ERC223 合约调用这个自定义函数时,合约调用自身函数从而造成内部权限控制失效。随后,百度安全的“隐形人真忙”也在先知安全大会上进行了“以太坊智能合约 call 注入攻击”的主题分享 [2]。这个漏洞源于一个较为常见的做法:在调用合约函数之后,可以再次调用一次另一个合约的任意函数,并且这个任意函数可以由合约调用发起者指定。但是 ATN 的合约漏洞恰恰暴露了这一常见做法非常危险的一面:合约调用者可能通过该功能绕开权限检查,或者以合约的身份发起对其它合约的攻击等等。
有安全隐患代码链接:
-
https://github.com/Dexaran/ERC223-token-standard/blob/16d350ec85d5b14b9dc857468c8e0eb4a10572d3/ERC223_Token.sol#L70
-
https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC827/ERC827Token.sol
ATN 事件漏洞分析
ERC223 是由 Dexaran 于 2017 年 3 月 5 日提出的一个 Token 标准草案 [3],用于改进 ERC20,解决其无法处理发往合约自身 Token 的这一问题。ERC20 有两套代币转账机制,一套为直接调用 transfer() 函数,另一套为调用 approve() + transferFrom() 先授权再转账。当转账对象为智能合约时,这种情况必须使用第二套方法,否则转往合约地址的 Token 将永远无法再次转出。
下面代码为 ERC223 草案中的一段 正确示例,调用 transfer() 函数时,合约判断目标地址 to 是否是合约,如果是合约,则调用目标合约的 tokenFallback() 方法,从而实现合约对转入 Token 的处理。这段代码并没有 CUSTOM_CALL 滥用的问题。
// 提案中的正确示例代码 contract ERC223 { function transfer(address to, uint value, bytes data) { uint codeLength; assembly { codeLength := extcodesize(_to) } balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(_value); if(codeLength>0) { // Require proper transaction handling. ERC223Receiver receiver = ERC223Receiver(_to); receiver.tokenFallback(msg.sender, _value, _data); } } }
ERC223 合约是 ERC20 合约的超集,目标为取代 ERC20 合约,成为新的 Token 合约标准。但提出以来至今一年多的时间仍未得到广泛接受,仅有少数项目采用了该提案。
下面是 ERC223 错误实现代码,ATN Token不幸地采用了这一段代码。用户被允许传入任意自定义的 _custom_fallback,从而任意调用目标 _to 地址上的任意方法!
// 此代码有 CUSTOM_CALL 滥用问题function transferFrom( address _from, address _to, uint256 _amount, bytes _data, string _custom_fallback ) public returns (bool success){ ... ERC223ReceivingContract receiver = ERC223ReceivingContract(_to); receiving.call.value(0)(byte4(keccak256(_custom_fallback)), _from, amout, data); ... }
ATN 的漏洞分析报告中称其 Token 合约参考了 ERC223 标准的推荐实现 [4]。经过我们调查,发现其的确与 Dexaran 维护的 ERC223-token-standard 中 Recommended 分支的 transfer() 方法实现类似 [5]:
// 此代码有 CUSTOM_CALL 滥用问题function transfer( address _to, uint _value, bytes _data, string _custom_fallback ) public returns (bool success) { ... assert(_to.call.value(0)(bytes4(keccak256(_custom_fallback)), msg.sender, _value, _data)); ... }
这其实是很危险的行为!ConsenSys 维护的「以太坊智能合约 —— 最佳安全开发指南」中曾明确提示,要尽量避免合约的外部调用。但在此次攻击事件中,黑客传入的 _custom_fallback 为 setOwner(address),传入的目标地址 _to 恰好是 ATN 合约本身,间接调用了 ATN 的 setOwner(address) 方法,使得 msg.sender 变为 ATN Token 合约本身,从而通过 ds-auth 库的 isAuthorized() 鉴权校验。
EVM 读取参数时并不会校验参数个数,在上述例子中,黑客调用了 setOwner(adddress) 函数,EVM 仅会读取最左边的 _from 参数。因此使用底层 call() 方法传参时,参数个数与函数所需不一致并不会引发报错,黑客很容易精心构造出所需的攻击参数。
CUSTOM_CALL 滥用的危害
再回到 _custom_fallback 接口实现上。我们认为作为一个通用 Token 标准接口设计,设计者必须尽可能多地考虑整个系统生态的安全性,尽可能规避因使用不当引入的风险。倘若上面这种 _custom_fallback 接口设计得到广泛采纳,未来势必会出现更多类似的安全性问题。一个良好的接口设计,最好能做到精简、易用和无歧义。作者提案中 tokenFallback() 接口完全可以应对其原本想要解决的 ERC20 问题。而实现上引入自定义的 _custom_fallback,很容易对开发者产生误导并被滥用。
function approveAndCall( address _spender, uint256 _value, bytes _data ) public payable returns (bool) { // require(_spender != address(this)); approve(_spender, _value); require(_spender.call.value(msg.value)(_data)); return true; }
通常当我们调用 ERC20 的 approve() 函数给一个智能合约地址后,对方并不能收到相关通知进行下一步操作,常见做法是利用 接收通知调用(receiverCall)来解决无法监听的问题。上面代码是一种实现方式,很不幸这段代码有严重的 CUSTOM_CALL 滥用漏洞。调用 approveAndCall() 函数后,会接着执行 _spender 上用户自定义的其他方法来进行接收者的后续操作。
下面敲黑板!
【危害】:这种合约本身允许用户自定义 call() 任意地址上任意函数的设计,十分危险。攻击者可以很容易地借用当前合约的身份来进行任何操作。
这通常会导致两种危险的后果:
-
后果一:允许攻击者以缺陷合约身份来盗走其它 Token 合约中的 Token
-
后果二:与 ds-auth 结合,绕过合约自身的权限检查
-
后果三:允许攻击者以缺陷合约身份来盗走其它 Token 账户所授权(Approve)的 Token
后果一举例:假设缺陷 Token 合约 A 自身账户中拥有各种 Token B、 C、 D 等,攻击者只需将 _spender 设为想要盗取的目标 Token (如 B 的地址),再构造用于调用 transfer(address,uint256) 的 _data,即可轻松以合约 A 的身份将合约 A 中的各类 Token 转走。上面代码中对 _spender != address(this) 的校验,也仅能保护 A Token。
管理各种 Token 的智能合约,倘若也允许自定义 call(),其合约上的各种 Token 就十分危险了。
后果二举例:如 ATN 安全事件中,黑客也是借此漏洞利用 ATN 合约的身份,绕过了 ds-auth 的权限控制。
后果三举例:假设缺陷 Token 合约 A 被用户 X 授权(Approve)管理 10,000 个Token B,那么黑客也是借此漏洞调用transferFrom()函数来盗取Token B。
- 免责声明
- 世链财经作为开放的信息发布平台,所有资讯仅代表作者个人观点,与世链财经无关。如文章、图片、音频或视频出现侵权、违规及其他不当言论,请提供相关材料,发送到:2785592653@qq.com。
- 风险提示:本站所提供的资讯不代表任何投资暗示。投资有风险,入市须谨慎。
- 世链粉丝群:提供最新热点新闻,空投糖果、红包等福利,微信:juu3644。