SharkTeam:十大智能合约安全威胁之函数恶意初始化
问:我们常提到的智能合约漏洞真的是实际中威胁最大、发生最频繁的安全漏洞吗?
答:完全不是那样。例如“溢出”、“外部调用”等常提到的智能合约安全漏洞并不是最常发生,威胁最大的。
到底哪些安全威胁从发生频率和危害性上能称为Top10的呢?SharkTeam合约安全系列课程之【十大智能合约安全威胁】和您一起讨论和深入。第六课【详解函数恶意初始化】。
初始化在计算机编程领域中指为数据对象或变量赋初值的做法,如何初始化则取决于所用的程序语言以及所要初始化的对象的存储类型等属性,用于进行初始化的程序结构则称为初始化器或初始化列表。
初始化函数的意思是,当你创建一个实例的时候,这个函数就会被调用。我们在部署合约后进行初始化合约时,一般不会采用传递构造函数参数进行初始化合约。
为什么不会采用构造函数方式constructor(uint256 _a)进行初始化,却采用上面所展示的方式进行初始化呢?
但是,采用这种方式进行初始化会有一些问题:
(1)权限未严格鉴别:未严格设置初始化函数调用者的权限,攻击者会自由利用权限漏洞进行恶意初始化。
(2)多次调用自定义初始化函数:自定义了不安全的初始化函数,没有对调用次数进行限制,攻击者利用该漏洞重复调用初始化函数,导致攻击者可以轻易的重新初始化资金池。
2021年3月9日消息,DODO的wCRES/USDT资金池遭受攻击,攻击者转走了价值约90万美元的wCRES和价值约113万美元的USDT。DODO是一个近期很热门的DeFi项目。攻击者利用资金池合约的初始化函数漏洞,从资金池盗取了133,548.93858472505wCRES和1,139,456.204397USDT。DODO官方发推表示,此次攻击只和DODO V2众筹池有关,作为预防措施,已经关闭DODO资金池创建入口。本次事件攻击流程如下:
本次攻击的执行trace如下图所示,上面红框表示攻击者向资金池转入FDO和FUSDT的过程,下面的红框表示攻击者向资金池借贷的过程。
可以看到攻击者在调用flashLoan函数的过程中执行了init初始化函数,并且输入的baseTokenAddress参数为FDO, quoteTokenAddress参数为FUSDT。通过分析源码中的init函数可知,攻击者利用该函数将资金池重新进行了初始化,此时资金池被重新初始化成了FDO/FUSDT池。因此,攻击者可以规避闪电贷的贷款归还机制。
问题分析:DODO资金池的初始化函数init没有对调用者的权限进行严格的校验,也没有做一些防止重复调用的措施,这才导致攻击者可以轻易的重新初始化资金池。
2021年8月10日,去中心化年金协议 Punk Protocol遭到攻击,损失 890 万美元,后来团队又找回了 495 万美元(向黑客2提供了 100 万美元作为漏洞发现费,黑客2归还495万美金)。
在这次攻击事件中,黑客1有两笔攻击交易,一个是试探攻击,一个是正式攻击。黑客2也有两笔攻击交易,不过其中一笔是inputdata带有通知项目方的信息。
接下来,以黑客1正式攻击交易为例,展开详细分析:
(1)首先,黑客的攻击执行了delegateCall,将攻击者的合约地址写入到compoundModel中initialize函的forge_参数。setForge(address) 函数在初始化函数中执行。这是一个修改Forge地址的功能
(2)其次,它执行withdrawToForge函数并将所有资金发送到攻击者的合约,然后在调用initialize函数发现forge参数已经被替换成攻击者合约的地址。链接到forge的所有CompoundModel都使用相同的代码,因此所有资产都转移到攻击者的合约中。目前,导致黑客入侵的代码已被项目方修补。添加了两个Modifiers(仅Creator,initializer),这样只有Contract Creator可以调用Initialize函数并控制它只被调用一次。
问题分析:本次攻击的根本原因在于 CompoundModel 合约中缺少对初始化函数的安全控制,可以被重复初始化。
2022年8月2日,跨链协议Nomad遭受到黑客攻击,损失超过1.9亿美元。该事件有多笔交易,我们选择其中一笔交易进行分析。交易hash:0xb1fe26cc8892f58eb468f5208baaf38bac422b5752cca0b9c8a871855d63ae28
交易调用了process函数:
进一步分析后发现,在调用acceptableRoot函数时,输入的参数为0,即messages中并没有对应的消息,结果返回ture。
confirmAt本身的表示root被确认的时间,当root为0时,实际是没有被确认过的,confirmAt的值应该也为0,最后会返回false。正是这一点小失误造成了巨大的经济损失,对整个项目造成了近乎毁灭性的打击。
从合约中可以看出,合约的初始化函数initialize会将confirmAt的值初始化为1,初始化函数需要在合约部署后即刻调用,一般由合约的部署地址即owner账户来调用。我们查看了owner账户以及其所有的交易,发现是在初始化时将confirmAt[0]设置成了1。
该合约是升级后的合约,初始化函数是在合约升级后对合约进行初始化。对比于升级前的实现合约(0x7f58bb8311db968ab110889f2dfa04ab7e8e831b),初始化函数相同,但process函数是不同的,升级前的process函数如下:
问题分析:本次安全事件的根本原因是可升级合约在合约升级的过程中存在失误,容易被攻击者利用初始化函数漏洞,进行恶意攻击。
我们应该采取哪些措施去避免函数恶意初始化?