漏洞原理
在 Solidity 中,函數有可見性類型,訓示函數被允許如何被調用。可見性決定了函數是否可以由使用者、其他派生合約、僅在内部或外部調用。函數有四個可見性類型(關于函數的可見性詳見另一篇博文 Solidity語言詳解——函數和狀态變量的可見性及Getter函數)。Solidity 中的函數預設為公共的,即允許使用者或合約從外部調用它們。不正确地使用函數可見性可能會導緻智能合約中的一些破壞性漏洞。
安全隐患
Solidity 中函數的預設可見性是
public
。是以,沒有指定任何可見性的函數将被外部使用者或合約調用。當開發人員錯誤地忽略了應該是私有 (或僅在合約内部可調用) 的函數的可見性說明時,問題就出現了。
讓我們來看一下這個合約例子:
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.18;
contract HashForEther {
function withdrawWinnings() {
// Winner if the last 8 hex characters of the address are 0.
require(uint32(msg.sender) == 0);
withdraw();
}
function withdraw() {
msg.sender.transfer(this.balance);
}
}
這個簡單的合約被設計成一個位址猜測賞金遊戲。為了赢得合約的餘額,使用者必須生成一個最後 8 個十六進制字元為 0 的以太坊位址。一旦獲得,它們可以調用
withdraw()
函數來獲得獎勵。
不幸的是,合約中函數的可見性沒有被指定。特别是,
withdraw()
函數是公共的,是以任何合約都可以調用該函數來竊取獎金。
如何預防
最好總是在合約中指定所有函數的可見性,即使它們是有意公開的。Solidity 的最新版本現在會在編譯過程中對沒有顯式指定可見性的函數顯示警告或錯誤提示,這樣就可以預防此類錯誤的發生。
以下是經修複後的合約代碼,通過将
withdraw()
的可見性設定為
private
來限制該函數僅允許在合約内部調用。
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.18;
contract HashForEtherFixed {
function withdrawWinnings() public {
// Winner if the last 8 hex characters of the address are 0.
require(uint32(msg.sender) == 0);
withdraw();
}
function withdraw() private {
msg.sender.transfer(this.balance);
}
}