防范智能合约攻击 — “DELEGATECALL”

通过一段有漏洞的智能合约代码,看看攻击是如何发生的,以及修复它的预防技术

防范智能合约攻击 — “DELEGATECALL”
photo credit: Arnold Francisca

翻译:团长(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 改为攻击合约的地址。然后,每当用户调用 withdrawfallback 函数时,恶意合约就会运行,它将窃取合约的全部余额。

这类攻击合约的一个例子是:

预防措施

Solidity 为实现库合约提供了library关键字。这确保了库合同是无状态的和非自毁的。

真实世界的黑客实例: Parity Multisig Wallet (2nd Hack)

库合约:

钱包合约:

请注意,Wallet合约通过一个委托调用将所有调用传递给WalletLibrary合约。这段代码中的常数_walletLibrary地址作为实际部署的WalletLibrary合约的占位符(它在0x863DF6BFa4469f3ead0bE8f9F2AAE51c91A907b4)。


原文链接:Preventing Smart Contract Attacks on Ethereum — “DELEGATECALL”