以太坊的智能合约出现重入漏洞(Reentrancy)已是老生常谈了,其中最著名的一次莫过于2016年6月发生的the DAO事件,此次事件导致了价值约6千万美元的以太币被盗,并直接促使了当年7月以太坊的硬分叉,即以太经典ETC和目前的以太坊ETH.
最近,降维安全实验室(johnwick.io)监测到成人娱乐系统spankchain的支付通道(payment channel)关联的智能合约 LedgerChannel也遭到了此类攻击. 某黑客发现了该支付通道合约的重入漏洞(Reentrancy),并于北京时间2017年10月7日上午8时许创建了恶意攻击合约,随后成功从该合约窃取了165.38 ETH,约合3.8万美元价值的以太币.
分析
导致payment channel合约发生重入漏洞的函数是 createChannel(bytes32,address,uint256,address,uint256[2])和 LCOpenTimeout(bytes32).
用户可以通过 createChannel向合约存入以太币/代币, 并通过 LCOpenTimeout()让合约返还自己之前存入的以太币/代币,下面我们对这两个函数逐一分析,看看问题出在哪里.
392~395行: 如果用户提供的入参以太币余额 _balance[0]不为0,那么用户需要提供等量的以太币ETH,即 msg.value==_balance[0].
396~400行: 如果用户提供的入参代币余额 _balance[1]不为0,那么合约将调用用户提供的入参ERC20标准合约地址 _token内函数 transferFrom(),将代币存入合约内. 注意这里的 _token是用户可控的
406行: Ledger Channel开启超时 LCopenTimeout由 now和用户提供的入参 _confirmTime相加得到,即用户可控这个超时值.
407行: 将 _balance[0]和 _balance[1]保存到 initialDeposit.
我们来看导致重入问题的关键函数.
414行: 需要检查当前块的时间戳 now超过 LCopenTimeout, 如前所述,这个超时值用户可控,直接pass.
416~418行: 如果用户存入的以太币不为0, 那么合约会通过以太坊虚拟机内置的 transfer()函数将所有该用户存入以太币退还给用户. 请注意这里, 转账结束后并没有立即将用户以太币余额 Channels[_lcID].ethBalances[0]清零! 我们接着看.
419~421行: 如果用户存入的代币不为0, 那么将调用用户提供的ERC20标准合约地址 token里的 transfer()函数,将所有该用户存入的代币退还给用户. 注意,如前所述,这里的 transfer()转账函数完全由用户控制.
426行: 终于完成了以太币和代币操作,合约在链上删除用户的信息 Channels[_lcID],包括以太币余额 ethBalances[0]等.
以上是正常的执行流程,如果一个攻击者部署的是一个恶意合约 token,并在其中的 transfer()函数中调用 LCOpenTimeout()函数, 那么 LCOpenTimeout()会在412~421行间不断循环,并不会执行到426行去清除该用户的以太币余额数据,导致合约重复将以太币退还给攻击者,造成重入漏洞攻击.
来源:
发布人:降维安全
声明:该文观点仅代表作者本人,不代表火讯财经立场。火讯财经系信息发布平台,仅提供信息存储空间服务。
如文章涉及侵权, 请及时致函告之,本站将第⼀时间删除⽂章。邮箱:840034348@qq.com