SharkTeam:Move语言安全性分析及合约审计要点之三明治攻击
SharkTeam在之前的“十大智能合约安全威胁”系列课程中,根据历史发生的智能合约安全事件,总结分析了在智能合约领域中出现较多、危害最大的前10大漏洞。这些漏洞之前通常出现在Solidity智能合约中,那么对于Move智能合约来说,会不会存在相同的危害呢?
SharkTeam【Move语言安全性分析及合约审计要点】系列课程将带您逐步深入,内容包括权限漏洞、重入漏洞、逻辑校验漏洞、函数恶意初始化、回退攻击、提案攻击、合约升级漏洞、操纵预言机、三明治攻击、重放攻击。本章内容【三明治攻击】。
三明治攻击(Sandwich Attack)是DeFi领域中常见的攻击手段,而且是一种区块链层面跟智能合约无关的前端攻击方式。为了达成三明治攻击,攻击者会在交易内存池中找到一个待处理的受害者交易,然后通过发送前置(front-run)和后置(back-run)交易,试图夹击受害者交易,构成“三明治”交易结构。本质上,三明治攻击是一种抢先交易的行为。区块链交易内存池中交易的透明性以及共识机制下执行交易的延迟(尤其是网络拥堵的情况下),使得抢先交易更容易发生,这给三明治攻击提供了条件。攻击者发起三明治攻击的目的是掠夺受害者的收益,一般利用价格的差异来达成目的。
如上图,三明治攻击流程图中所示,三明治攻击主要流程如下:
(1)攻击者通过观察交易内存池中的待处理交易,获得了攻击者提交的交易Tx,在Tx执行前洞悉了受害者的交易意图,认为有利可图。然后,攻击者以比Tx更高的Gas Price提交前置交易Tx1,通过利用10个TokenX兑换了100个TokenY(1TokenX = 10TokenY, 1TokenY=0.1TokenX),抬高了TokenY的价格,引起了意外的价格滑点。
(2)受害者在什么都不知的情况下,通过意外的价格滑点,以5个TokenX兑换了40个TokenY,原本5个TokenX可以兑换50个TokenY(1TokenX=8TokenY, 1TokenY=0.125TokenX)。此时,TokenY的价格被进一步提高。
(3)攻击者提交Tx2,将步骤(1)中兑换的TokenY兑换成TokenX。因为TokenY价格被两次太高(1TokenX=7TokenY, 1TokenY=0.143TokenX),此时100个TokenY可以兑换成14.286个TokenX,相比兑换时支付的10个TokenX,获利4.286个TokenX。
攻击者通过三明治攻击,引起了意外的价格滑点,增大了受害者交易时的实际价格滑点,从而掠夺了受害者的收益。
以上面的攻击过程为例,比如受害者期望的交易价格为TokenX=11TokenY:
三明治攻击(前置交易)引起的意外滑点为:
即,因为三明治攻击,TokenX的价格意外下跌了2,TokenY的价格意外上涨了0. 025。
图:三明治兑换价格
三明治攻击就是通过前置交易让受害者的实际交易产生了意外滑点,增大了交易执行时的价格差,然后从中获利。
攻击者发起了两笔交易,需要支付Gas费用作为成本,尤其是Tx1的Gas Price更高。若是获利的2.5个Token的价值比两笔交易的Gas费要高,那么攻击者在此次三明治攻击中将获得纯利润;否则,将产生损失。
因此,三明治攻击并不一定会产生纯收益,也可能产生损失,这跟三明治攻击的成本(即Gas费)有关,也跟Token的价格有关。
图3 TokenX价格波动
如上面的TokenX价格波动图,在以恒定乘积做市商X * Y = k的去中心化交易所DEX生态中,每次购买TokenX时候,其价格都会上涨。因此,攻击者先购买TokenX,TokenX价格上涨,然后其他人(受害人)购买TokenX,TokenX的价格会再次上涨。攻击者随后将买入的TokenX立即卖出,此时TokenX价格上涨两次,攻击者低买高卖,因此,攻击者获得额外利润。通过这种方式实现了TokenX的套利。
套利在某种程度上可以维护市场价格生态的平衡。但是,三明治攻击会牺牲正常交易者(受害人)的利益,最终掠夺了正常交易者(受害人)的利润。
通过攻击流程以及价格滑点的分析,我们发现三明治攻击要想成功掠夺受害者的收益,必须在被夹击交易执行前通过前置交易产生意外滑点,从而增大被夹击交易执行时的价格滑点,进而扩大前置交易和后置交易时代币的价格差。攻击者利用该价格差掠夺受害者的收益。
在允许的交易价格范围内,价格滑点增大的越多,前置和后置交易的代币交易差越大,三明治攻击的收益越大。若被夹击交易在执行时的价格超过了允许范围,则该交易不会被执行,则攻击者为前置交易和后置交易所支付的成本将成为损失,攻击者不会有任何获益。
对于AMM相关交易,增大价格滑点然后扩大价格差的方式有两种,即抢先兑换代币与抢先降低流动性。
(1)抢先兑换代币
攻击者在前置交易中抢先以TokenX兑换TokenY,跟上文中的攻击流程一样,在前置交易执行后,TokenY的价格被抬高。当被夹击的交易执行TokenX兑换TokenY时,因为TokenY的价格因抢先交易被抬高,所以会产生意外的价格滑点。再加上原本的价格滑点,使得实际滑点被增大,交易完成后TokenY的价格被再次抬高。攻击者在后置交易中将前置交易兑换的TokenY以被两次抬高的价格卖出(兑换成TokenX),从而可以从中获利。
(2)抢先降低流动性
对于恒定乘积模型的AMM,交易池中的流动性越少,价格波动越大,价格滑点越大。因此,流动性提供者可以通过降低流动性的方式来增加价格滑点,进而增大价格差,实现获利的目的。这种方式实现过程如下:
我们以TokenX/TokenY交易对为例。假设交易池流动性为;攻击者持有的流动性为;受害者提交的交易为将100个TokenX兑换成TokenY。
这种方式的三明治攻击,攻击者需要持有交易池中的流动性,然后在被夹击交易执行前移除流动性,同时放弃了受害者交易的手续费提成。
此外,三明治攻击也可能跟业务有关,比如一些拍卖NFT的场景,如果存在价格差,就有可能遭受三明治攻击。攻击者通过调整交易顺序,在卖出和买入两笔交易之间插入前置和后置交易,实现低价从拍卖者手中买入NFT,然后高价卖给竞拍者,掠夺拍卖者的收益,从而实现获利的目的。
这种情况发生的条件比较高,跟场景业务相关,比如拍卖的价格差较大,拍卖成交的结果必须跟交易顺序有关。因此对于这种情况下的三明治攻击是很容易防范的,只需要在业务和实现上进行严格设计,使得结果不依赖与交易顺序,就可以避免三明治攻击。
三明治攻击跟智能合约并没有关系,不能通过编写智能合约来预判收益。而且三明治攻击发起需要比受害者交易更高的交易成本,因此,三明治攻击具有一定的风险,不是每一次攻击都可以获得净利润的,可能会产生损失。没有净利润的攻击是失败的,若要使攻击成功,需要同时满足两个条件:
(1)从结果角度
三明治攻击获得利润需要高于Gas费(发起攻击的成本),如果收益低于成本,攻击者不仅没有净利润,还会产生经济损失。
影响最终收益净利润的因素有两个:Gas费和价格滑点。GasPrice决定交易顺序,交易顺序会影响交易时的价格滑点。Gas费由交易消耗的Gas以及GasPrice计算所得,是攻击最大的成本,价格滑点直接影响攻击的收益。Gas费和价格滑点对攻击净收益的影响具有不确定性,而是这种影响是不可预测的。这就造成三明治攻击在攻击发起前难以评估其成功率以及净利润。相比利用合约漏洞发起的攻击,这种不确定性也会降低三明治攻击发生的频率,尤其是对一些小额交易。
(2)从交易角度
首先需要保证交易执行顺序为前置交易、受害者被夹击交易、后置交易;其次要保证三笔交易都可以执行成功。比如,如果受害者被夹击交易对滑点有要求,可能因为前置交易执行后滑点过大是的受害者被夹击交易无法执行,则三明治攻击将会失败,并且攻击者会承担损失。
通过分析三明治攻击的成功条件,针对三明治攻击,我们可以采用以下3种预防措施:
(1)限制Gas费用
三明治攻击之所以会发生,因为更高的gas费用交易优先于其他交易。对Gas费用设置一定的限制,可以降低Gas费对交易顺序的影响,提高三明治攻击的难度。
(2)避免低流动性交易池
缺乏良好的流动性对三明治攻击是有利的。因为交易池流动性越低,价格波动越大,价格滑点越大。而滑点越高,攻击者可以从三明治攻击中获得的利润就越多。因此,低流动性交易池更容易遭遇到三明治攻击。因此,建议尽可能地选择高流动性的交易池进行交易,这样可以降低三明治攻击的可能性。
(3)选择小额交易
三明治攻击对大额交易更感兴趣,因为交易额越大,利润空间越大。避免三明治攻击的一种方法是将我们的交易分成几个较小的交易,因为这些小额交易对于攻击者来说不太感兴趣,因为小额交易可能会产生损失。
三明治攻击是区块链层面的操纵价格的前端攻击手段,跟智能合约无关,只跟区块链交易执行顺序有关。因此,对于Move生态的公链是否存在三明治攻击,需要分析公链的共识机制以及交易内存池中的交易执行顺序。
区块链验证节点(矿工)收集区块链网络中广播的交易信息,这些交易将会被保存在交易内存池中等待验证与执行。不同的区块链采用了不同的共识协议,而不同的共识协议也会有着不同的交易验证与执行方案。比如比特币采用的PoW协议以及以太坊采用的PoS协议都是顺序执行交易,内存池中的交易按GasPrice的高低进行排序,GasPrice高的交易先执行,GasPrice低的交易后执行甚至是被遗弃无法执行。这也给投机者造就了抢先交易的条件,同时也是三明治攻击的前提。
Move生态公链,比如Sui,为了提高公链性能,选择了改进的拜占庭协议,使用了并行与顺序相结合的交易执行方案。没有共享状态的无关交易可以并行执行,而共享了状态的相关交易只能进行顺序执行。由上文中的分析可知,三明治攻击过程中的3笔三明治交易共享了AMM交易池的状态,因此,无法并行执行只能够顺序执行。公链是否构成三明治攻击的条件则取决于顺序执行的排序是否存在抢先交易的可能性:
(1)若交易最终的执行顺序跟提交的时间有关,先提交的交易优先执行,则从一开始就杜绝了三明治攻击的可能性,无法实现对受害者交易的抢先交易;
(2)若交易最终的执行顺序跟GasPrice有关,跟以太坊PoS机制中的交易执行顺序相同,GasPrice高的交易优先执行,那么这样的公链同样无法避免三明治攻击;
(3)若交易最终的执行顺序是有GasPrice按一定算法计算排列的,那么这样的公链可能存在三明治攻击的条件,这取决于排列算法,但三明治攻击的难度会比直接按照GasPrice高低进行排序要高一些,但不能排除没有三明治攻击的可能性。
Move生态的公链包括Starcoin、Aptos、Sui、0L、Pontem等,这些公链又选择了不同的共识协议,并且还做出了改进,比如Starcoin选择了增强的PoW协议并对其进行了改进;Sui采用了无关交易并行执行、相关交易顺序执行的方案。这些公链中是否可能存在三明治攻击的条件需要分析其交易执行方式,会不会出现三明治攻击还要看公链的活跃度,尤其是基于AMM的去中心化交易的锁仓、交易额和交易量。一般三明治攻击者不会去选择一个锁仓低、交易额小、交易量低的AMM,因为纯利润低,甚至损失大于收益。
目前,Move生态的公链正处于发展阶段,从项目覆盖率、项目成熟度、锁仓等指标来看,Move项目遭受三明治攻击的可能性比Solidity项目要小的多。Move语言本身的安全性就比Solidity要高的多,Move智能合约中可能存在的漏洞更少。此外,Move生态公链多样化,每一个公链都有独特的特性,需要单独对每一个公链进行分析。因此,三明治攻击是Move生态中一个值得思考的安全问题。