中金网|贾瑶琪:攻击无处不在 区块链安全和隐私问题有点与众不同( 二 )


然而 , 恶意合约的执行逻辑则是 , 首先发起Withdrawal的函数 , 调用withdrawBalance函数 , 之后左边正常的智能合约会调用对应的withdrawBalance , 以太坊转账 , 当转账一旦完成 , 由于以太坊本身的特性 , 就会调用恶意智能合约里面的回调函数 , 回调函数就会进一步的调用左边的withdrawBalance 。
整体来看 , 按照这个箭头一二三四 , 形成了一个循环 。 导致的后果就是恶意合约在发起调用之后 , 可以一直不断、如此往复地进行转账 , 把左边智能合约里的余额源源不断地转到攻击者的地址 , 只要左边智能合约的燃料费是足够的以及还有余额 , 最终会把所有余额转给对应的攻击者 。

中金网|贾瑶琪:攻击无处不在 区块链安全和隐私问题有点与众不同
本文插图

这是一个简略的流程图 , 大家可以看一下实际代码 , 左边是正常的智能合约 , 右边是攻击者的智能合约 。 攻击者智能合约 , 在右上角进行发起 , 调用左边withdrawBalance函数 , 之后调用call.value() , call.value()就触发了恶意智能合约的默认payable的回调函数 , 默认回调函数会进一步的再触发循环 , 直至整个左边智能合约所有余额都转给恶意攻击者 。
由于重入攻击 , TheDAO的合约在当时损失了超过6000万美金的ETH 。 区块链有不可篡改的特性 , 这样的交易是不能回滚的 , 造成的损失是永久的 。 当时社区有想修复这样一个漏洞来退回对应损失的以太 , 也有一些社区成员是不想的 , 最终导致了以太坊当时的分叉 , 分叉出了现在大家熟知的ETC/以太经典 。
前事不忘后事之师 , 然而很多时候大家都会遗忘历史 , 即使是跟安全息息相关的 。 最近的DeFi或者LendF.me的攻击中 , 又见到了类似2016年的重入攻击 。 由于过程其实比之前2016年攻击要复杂很多 , 这里就简化说明一下 。
Lendf.me押金函数被回调函数中的withdraw反复调用 , 导致了智能合约中的攻击者抵押的总额是在没有足够抵押金的情况下持续上升的 。 相当于通过一直调用这样的循环 , 让攻击者本身的抵押总额一直上升 , 即使他没有对应的抵押物 , 最终导致攻击者积攒了足够多imBTC对应的抵押金额 。 之后攻击者借出所有可用的资产 , 超过2500万美金 。 当然 , 比较幸运的是通过一些方法攻击者返回对应的资产 , 也是不幸中的万幸 。
重入攻击是很严重 , 防御措施是怎样呢?其实在做转账之前可以先把内部的状态设置好 , 例如当智能合约要给某个用户转10个ETH , 在转账之前提前把用户对应的余额变量 , 减掉对应的10个ETH再做交互转账 , 就可以避免这样的攻击了 。 这就是一个比较好的规范 , 先去检查 , 然后执行 , 最后再做交互 。
溢出攻击
另一个想给大家分享的攻击也是过去几年中比较普遍出现的攻击 , 叫做"溢出攻击" 。 大家如果是程序员都知道 , 不管是用C++还是JavaScript , 当开发者操作算术运算不当就会出现越界 , 特别是整数溢出 。 当超出整数类型的最大范围的时候 , 数字会由极大值变为极小值或者直接归零 , 叫做"上溢";相反的超出最小值范围叫做"下溢" 。

中金网|贾瑶琪:攻击无处不在 区块链安全和隐私问题有点与众不同
本文插图

上图是一个样例 。 可以看到攻击者发了好多交易 , 其中_value的对应值特别大 。 具体到257行 , 当_value特别大的时候 , cnt如果大于2 , amount就直接向上溢出为零 , 导致之后的安全检查全部通过 。 259行的第二个检查正常来说是不应该通过的 , 但是由于溢出的amount变成0了 , 左边balances[msg.sender]的数值肯定大于等于零 , 导致第二部分的安全检查也通过了 。 最终攻击者成功获取超大数目的数字资产 , 即使他本身的余额可能会极其小 。
其他的一些例子大家可以查看multiOverflow(CVE-2018-10706) , transferFlaw(CVE-2018–10468) , proxyOverflow(CVE-2018-10376) 。 攻击者通常都是利用智能合约的溢出漏洞 , 制作出可以造成溢出的交易 , 之后通过溢出的变量来通过智能合约的安全检查 , 最终获取巨大的资产 。 作为防御措施 , 开发者尽量使用保证计算安全的库 , 比如SafeMath 。


推荐阅读