防范智能合约攻击 — “DELEGATECALL”
通过一段有漏洞的智能合约代码,看看攻击是如何发生的,以及修复它的预防技术
翻译:团长(https://twitter.com/quentangle_)
由于DELEGATECALL
的上下文保护性质,建立无漏洞的自定义库并不像人们想象的那样容易。库中的代码本身可以是安全和无漏洞的;但是,当在另一个应用程序的上下文中运行时,就会出现新的漏洞。让我们看一下斐波那契数这个相当复杂的例子。
让我们考虑一下FibonacciLib.sol
中的库,它可以生成Fibonacci序列和类似形式的序列。
这个库提供了一个函数,可以生成序列中的第n个斐波那契数。用户可以改变序列的起始数(start
),并计算出这个新序列中的第n个斐波那契数。
现在由一个使用该库的合约,如FibonacciBalance.sol
所示。
漏洞利用
这个合约允许参与者从合约中提取以太,以太的数量等于参与者提取顺序所对应的斐波那契数;也就是说,第一个参与者得到1个以太,第二个也得到1个,第三个得到2个,第四个得到3个,第五个得到5个,以此类推,直到合约的余额小于被提取的斐波那契数。
你可能已经注意到,状态变量start
在库和主调合约中都被使用。在库合约中,start
被用来指定斐波那契数列的开始,并被设置为0,而在主调合约中被设置为3。你还会注意到,FibonacciBalance
合约中的回退函数fallback允许将所有调用传递给库合约,这使得库合约的setStart
函数可以被调用。回顾我们保留了合约的状态,似乎这个函数将允许你改变本地FibonnacciBalance
合约中的start
变量的状态。如果是这样,这可以让人提取更多的以太,因为calculatedFibNumber
取决于起始变量(如在库合约中看到的)。事实上,setStart
函数并没有(也不能)修改FibonacciBalance
合约中的起始变量。这个合约内部的潜在漏洞比仅仅修改起始变量要严重得多。
现在注意到,在 withdraw
的第 21 行,我们执行了 fibonacciLibrary.delegatecall(fibSig,withdrawingCounter)
。这调用了setFibonacci
函数,它修改了slot[1]
,在我们当前的上下文中是calculatedFibNumber
。这当然是可以预期的(即执行后,calculatedFibNumber
被修改)。然而,记得FibonacciLib
合约中的起始变量位于slot[0]
中,即当前合约中的fibonacciLibrary
地址。这意味着函数 fibonacci
会给出一个意外的结果,因为它引用了 start(slot[0])
,在当前的调用环境中,它是 fibonacciLibrary
的地址(当解释为一个 uint
时,它通常会相当大)。因此,撤回的函数很可能会被退回,因为它不会包含uint(fibonacciLibrary)
数量的以太,这就是calculatedFibNumber
将返回的东西。
译注:delegateCall相关内容可参考
https://docs.soliditylang.org/en/v0.4.21/introduction-to-smart-contracts.html#delegatecall-callcode-and-libraries
https://solidity-by-example.org/delegatecall/
更糟糕的是,FibonacciBalance
合约让用户通过第26行的回退函数调用所有的fibonacciLibrary
函数。正如我们所讨论的,这包括setStart
函数。正如我们所讨论的,这个函数让任何人改变或设置slot[0]
。在这里,slot[0]
是 fibonacciLibrary
的地址。因此,攻击者可以创建一个恶意合约,将地址转换成一个uint
(这可以在Python中使用int('<address>',16)
轻松完成),然后调用setStart(<attack_contract_address_as_uint>)
。它将把 fibonacciLibrary
改为攻击合约的地址。然后,每当用户调用 withdraw
或 fallback
函数时,恶意合约就会运行,它将窃取合约的全部余额。
这类攻击合约的一个例子是:
预防措施
Solidity 为实现库合约提供了library
关键字。这确保了库合同是无状态的和非自毁的。
真实世界的黑客实例: Parity Multisig Wallet (2nd Hack)
库合约:
钱包合约:
请注意,Wallet
合约通过一个委托调用将所有调用传递给WalletLibrary
合约。这段代码中的常数_walletLibrary
地址作为实际部署的WalletLibrary
合约的占位符(它在0x863DF6BFa4469f3ead0bE8f9F2AAE51c91A907b4
)。
原文链接:Preventing Smart Contract Attacks on Ethereum — “DELEGATECALL”