構造函數負責智能合約所有者 owner 等數據的初始化,正確使用構造函數對智能合約安全至關重要。

原文標題:《成都鏈安智能合約安全實踐(二)| 練就「火眼金睛」,真假構造函數一眼看清》
撰文:成都鏈安

在《西遊記》中,六耳獼猴冒充猴王孫悟空,以假亂真,騙過了唐僧,騙過了衆神,縱使是照妖鏡也分不出真假。現在,智能合約遇上了「六耳獼猴」,又會擦出怎樣的火花?

智能合約安全實踐:開發者如何正確使用構造函數?

在智能合約中,構造函數負責一些數據的初始化工作,owner 值一般也會放在構造函數中進行初始化。owner 是智能合約擁有者的稱呼,也常被用來作爲該合約的超級管理員。對代幣合約來說,owner 可能被分配的權限有:鑄造 / 銷燬代幣、凍結代幣等。如果開發者以錯誤的語法創建「構造函數」,造成構造函數缺失,致使「六耳獼猴」以假亂真,瞞過了開發者,最後使得攻擊者成爲合約的擁有者(owner),那麼攻擊者便可依賴 owner 的權限,對代幣進行增發或銷燬等操作,進而可能造成整個代幣的崩盤。

構造函數簡介

在 Solidity 語言中,當函數名和合約名相同時,此函數就是合約的構造函數,在合約對象創建時,會先調用構造函數對相關的數據進行初始化。

以太坊 Solidity 0.4.22 版本中引入了關鍵字 constructor,新的構造函數聲明形式:constructor() public { },引入的目的是用以替代低版本中將合約名作爲構造函數名的語法形式,從而避免開發者筆誤造成構造函數命名錯誤的問題。引入的這個關鍵字看似平淡無奇,實則意蘊深刻,且聽我慢慢道來。

Fal1out「以假亂真?」– 漏洞分析

下面以 ethernaut 靶場的 Fallout 題目爲例進行分析。一眼看去,這似乎是一個正常沒有漏洞的合約代碼,但經過仔細觀察發現,該合約存在一個致命錯誤——構造函數名稱與合約名稱不一致,Fallout 合約的構造函數被寫錯成了 Fal1out (字母 l 和數字 1 的差異),這樣的錯誤使其成爲了一個被 public 修飾的普通函數,失去了構造函數僅在合約部署時被調用的特性,使得任何人都可以調用。該題目源碼如下圖所示:

智能合約安全實踐:開發者如何正確使用構造函數?圖 1

在 Fal1out 函數中直接指定了函數調用者的地址即爲 owner,所以只需要調用 Fal1out 函數即可實現對合約 owner 的更改。如下圖所示:

智能合約安全實踐:開發者如何正確使用構造函數?圖 2

「假猴王」Fal1out 想借着一些字體類型的相似字符的視覺差異混淆視聽,可最終還是沒能逃過我們的「火眼金睛」。

前車之覆:MorphToken 事件分析

在過去也曾發生過類似的安全事件,包含着假構造函數的合約被成功發佈到主鏈上,其中比較出名的是「MorphToken 事件」,其因爲一個看似很小的問題而造成了數千萬市值的代幣被增發。(合約代碼地址)

在 Owned 合約中,由於首字母大小寫的錯誤,導致本該成爲構造函數的 Owned 成爲了普通函數 owned,且被 public 修飾,可供任何人調用。如下圖所示:

智能合約安全實踐:開發者如何正確使用構造函數?圖 3

MorphToken 合約繼承了 Owned 合約,並在自己的構造函數內進行了 owner 的初始化,但是父合約 Owned 的 owned 函數是可供任何人調用的,攻擊者便可通過調用 owned 函數更改合約的所有者 owner。owner 的初始化代碼如下圖所示:

智能合約安全實踐:開發者如何正確使用構造函數?圖 4

由上述可知,任何人都可以通過調用合約的 owned 函數,成爲合約的擁有者 (owner)。如下圖所示:

智能合約安全實踐:開發者如何正確使用構造函數?圖 5

失之毫釐,差之千里,一個小小的字母錯誤,卻導致了合約的代幣的崩盤。代幣也被惡意增發。如下圖所示:

智能合約安全實踐:開發者如何正確使用構造函數?圖 6

後車之鑑:開發者應如何正確使用構造函數

建議更換 Solidity 0.4.22 及以上版本,並使用正確的 constructor() 語法。如下圖所示:

智能合約安全實踐:開發者如何正確使用構造函數?圖 7

切記: constructor() 前並無 function,function constructor() public { }爲錯誤的構造函數形式。

如果要使用低於 0.4.22 的版本,則一定要着重檢查函數名是否和合約名一致。如下圖所示:

智能合約安全實踐:開發者如何正確使用構造函數?圖 8

安全建議

在智能合約中因開發者粗心,而造成安全漏洞的事件層出不窮,「千里之堤,潰於蟻穴」,成都鏈安-安全實驗室在此給出如下建議:

  1. 開發者在編寫智能合約敏感函數(如構造函數、回退函數 fallback)時,應嚴格按照官方要求的代碼書寫規範,注意不要出現字符錯誤等情況。
  2. 在某些情況下,編譯器會對 constructor 的錯誤使用發出警告,開發者應予以正確對待,不可認爲其只是警告信息而忽略不處理。
  3. 在合約正式上線前一定要找專業可信的機構做好合約代碼的審計工作。