安全事件一直都是区块链生态最大的威胁之一,需要项目方时刻保持警惕。
作者:Sivan,Beosin 安全研究专家
封面:Photo by Mika Baumeister on Unsplash
上一篇文章我们介绍了近期区块链生态中发生的骗局类资金安全问题,包括后门以及貔貅盘。除了这些骗局事件,近期安全事件也对整个区块链生态造成了巨大的影响。
最近发生大量的安全攻击事件,这些事件对于项目方来说具有重大影响。攻击事件的发生主要原因之一是业务逻辑设计不当,其中可能存在漏洞或弱点,被黑客利用进行攻击。另外,价格操控也是导致安全攻击事件的因素之一,黑客可能通过操纵价格或市场行为来实施攻击。
本篇文章我们一起来盘点一下近期常见攻击都有些什么特点,以及项目方如何避免这些问题。
一、业务逻辑设计不当
Pair 代币意外销毁
近期因为业务逻辑设计不当发生的安全事件,大多都是由于 pair 代币余额异常导致的。
一些代币项目,会在业务设计中添加交易手续费或代币通缩的功能,也就是在代币转移过程中,会收取相关比例的手续费或者直接销毁部分转移的代币。这本是一个项目的创收或激励用户持有代币或其他有助于项目发展的业务逻辑,但如果代码设计不够完善,便会出现严重的问题。
例如,在转账过程中,有些项目进行额外代币扣除时,没考虑到 pair 意外扣除将导致严重后果。该类项目的转账函数逻辑一般是发送方和接收方进行正常的代币转移,并且额外扣除某个地址部分费用,用于手续费或销毁。但额外扣除地址为 pair 时,会导致 pair 中其中一个代币余额通过非交易的方式变少,k 值异常变化,从而使用少量代币便能兑换出大量另一个代币。
以下列项目代码为例:
该代币合约中存在意外销毁 pair 余额的问题,当代币转移发起者不是 pair 合约时,会进行更新池子的操作,该更新池子的方式是将 pair 的代币余额扣除交易金额的 1%,再更新储备量。黑客可以通过调用 transfer 函数自己给自己转账,反复操作,将会使得 pair 中的一个代币消耗得极少,最后利用极少代币将 pair 中大量的价值币兑换出来。
以下是真实安全事件过程:
首先使用 2555 枚 WBNB 兑换了 1390 亿枚 Bamboo 代币;
不断通过 transfer 将代币发送给自己,导致 pair 中 Bamboo 代币异常减小;
使用 139B 枚 Bamboo 代币兑换回 2772 枚 WBNB,完成攻击。
再以另一个意外销毁 pair 代币的例子说明一下,如下图代码。合约会判断当前交易类型,如果为 2(to 地址为 pair 合约,相当于卖币),合约会记录一个交易量的 20% 作为销毁数量,后续可以调用 goDead() 函数将 pair 中这个累积数量销毁,该功能对稳定币价能起部分作用。
但是合约没考虑到一些特殊情况,例如直接向 pair 里面转币,这时会使得合约误以为用户是在卖币,这部分币可以通过 pair 的 skim 函数全部提取出来。相当于 pair 没有任何变化,再次调用 goDead() 函数时,却能使得 pair 中的代币意外减少,重复操作,便能耗尽 pair 其中一种代币。
以下同样是真实安全事件过程:
不停通过 transfer 与 skim 操作,将 amountToDead 值控制得异常大。
调用 goDead() 函数将 pair 中的代币销毁掉。
使用少量代币兑换了大量的 USDT。
总结:
代币的 transfer 函数一定要认真设计代币转移逻辑,一定要避免 pair 余额意外扣除的情况,pair 余额仅在添加流动性、移除流动性以及交易过程中才能改变,并且改变数量尽量不要大于或小于传入数量,才能保障 pair 资金健康。
二、价格操控问题
对于价格操控的攻击事件,主要是由于项目方合约在获取价格的时候没有考虑到价格被意外控制的情况。特别是对于存在两个逆向计算的业务逻辑函数,如 stake&unstake、deposit&withdraw 等,两种操作一般都使用相同的参数但相反的计算逻辑。
而在这两种操作之间,如果能通过其他函数或合约将其中的部分或全部参数进行不对等的控制,那么两种操作之间便可能存在价格差,从而导致资产被盗。
1、只读重入
重入一直是区块链生态中最常见也是危害最大的漏洞之一。在以太坊最开始盛行的时候,重入攻击大多是因为以太坊转账调用的目标函数 fallback() 中再次发起了对该项目合约的调用,此时项目合约变量还未更新,可以绕过相关检查,如 DAO 事件。
随着以太坊生态越来越丰富,各个项目方更加注重这一方面的防护以及 solidity0.8.0 版本之后对溢出的主动检查,现在更多的攻击事件是通过重入来控制某些价格或参数,来达到攻击的目的。
例如近期发生频繁的只读重入,便是通过重入前后变量未修改完全,使得重入中调用的计算价格的函数出现异常,从而导致资产被盗。
有兴趣的读者可以参考如下链接:安全审计必备知识 | 难以防范的 “只读重入攻击” 是什么?
2、Pair 余额依赖
Pair 可以最直观的表现出代币价格变化,也是最准确的价格获取渠道,但 pair 中的价格是瞬时价格,并且可以通过大量代币兑换来控制,导致瞬间暴涨或暴跌。如果依赖 pair 中的价格作为业务中的参数参与计算,那么便可能导致异常的结果。
如下代码,burnForEth 函数通过依赖 pair 中能兑换出的数量来作为本合约发送给调用者的 ETH 数量,该数量是通过 pair 中两种代币余额来进行计算。正常情况下这是一个最准确的价格,但是如果提前使用 ETH 将 pair 中的另一个代币大量兑换出来,会导致 ETH 在该 pair 中的价格暴跌,从而计算出能兑换的 ETH 就会异常大,使得合约给调用者发送更多的 ETH。最后,调用者通过 pair 将 ETH 兑换回来,使价格恢复正常。
以下是真实安全事件过程:
攻击者使用闪电贷的 ETH 将大量 WAX 代币兑换出来,导致 WAX 价格暴涨。
攻击者不断调用 burnForEth 函数,销毁 WAX 来取出 ETH,此时使用的 WAX 价格时暴涨过后的,可以获得更多的 ETH。
最后,攻击者将 ETH 兑换出来,归还闪电贷并获利。
总结:
安全事件一直都是区块链生态最大的威胁之一,需要项目方时刻保持警惕。对于需要计算价格的业务逻辑要有严格的流程把控,尽量避免直接使用 pair 余额进行价格计算,调用外部函数获取价格也需要考虑其是否能被操控。项目方也可以与专业的安全合作伙伴合作,及时发现并应对潜在的安全威胁。