建議專案方在編寫代幣合約時不要同時使用 Multicall 和 ERC2771Context。

作者:九九

背景

2023 年 12 月 5 日,Web3 基礎開發平臺 thirdweb 表示在預構建智慧合約中發現了安全問題,所有使用預構建智能合約部署的 ERC20、ERC721、ERC1155 代幣均受到了影響。(具體受影響的合約代碼版本可以參考:https://blog.thirdweb.com/security-vulnerability/)

根據慢霧安全團隊情報,在 2023 年 12 月 7 號,ETH 主網上的 Time 代幣正是因為該漏洞遭受到攻擊,攻擊者獲利約 19 萬美金。 目前仍有不少存在漏洞的代幣合約正在被攻擊,慢霧安全團隊第一時間介入分析,並將結果分享如下:

前置知識

1. ERC-2771 是元交易的標準。 用戶可以將交易的執行委託給第三方 Forwarder,通常稱為中繼器或轉發器。

通常合約中直接調用者的位址是使用 msg.sender 獲取的,但在使用了 ERC-2771 的情況下,如果 msg.sender 是轉發器角色的話,那麼會截斷傳入的 calldata 並獲取最後 20 個字節來作為交易的直接調用者位址。

2. Multicall 是一個智慧合約庫,其作用是允許批量執行多個函數調用,從而減少交易成本。 這個庫通常用於優化 DApp 的性能和用戶體驗,特別是當需要進行多個讀取操作時。

從代碼中可以看到,thirdweb 專案存在漏洞的合約使用的 Multicall 庫是通過迴圈調用 DelegateCall 函數來執行引用了該庫的合約中的其他函數。

根本原因

漏洞的根本原因是代幣合約同時使用了 ERC-2771 和 Multicall 庫。 攻擊者通過 Forwarder 合約的 execute 函數調用代幣合約的 multicall 函數,執行合約中的其他函數(如燃燒代幣)。 這種方式成功通過了 ERC-2771 的 isTrustedForwarder 判斷,最終將函數的調用者解析為惡意 calldata 的最後 20 個字節。 因此,攻擊者成功欺騙了合約,使其誤認為調用者是其他使用者的位址,進而導致燃燒了其他使用者的代幣。

攻擊步驟分析

此處以攻擊交易 0xecdd11... f6b6 為例進行分析:

1. 攻擊者首先用 5 個 WETH 在 Uniswap V2 池子中兌換成 345,539,9346 枚 Time 代幣。

2. 接著調用 Forwarder 合約的 execute 函數,構造惡意的 data 去調用代幣合約的 multicall 函數,此時代幣合約會根據攻擊者傳入的惡意 data 去 delegateCall 執行代幣合約的 burn 函數,燃燒掉池子位址中的 62,227,259,510 枚 Time 代幣。

3. 由於上一步 burn 掉了池子中大量的 Time 代幣,使得 Time 代幣的價格被瞬間拉高,所以攻擊者最後可以反向 swap 第一步獲取的 Time 代幣,掏空了池子中的 94 枚 WETH。

攻擊原理剖析

在 Forward 合約的 execute 函數中,驗證 req.from 的簽名過後會用 call 交互 req.to(代幣位址)。 其中攻擊者傳入的 req.data 為

因為 0xac9650d8 為 multicall 函數的函數簽名,故會調用代幣合約的 multicall 函數,並且 multicall 函數傳入的 data 值為 0x42966c680000000000000000000000000000000000000000c9112ec16d958e8da8180000760dc1e043d99394a10605b2fa08f123d60faf84。

為什麼傳入 multicall 函數的 data 值中沒有 req.from 呢? 這是因為 EVM 底層處理 call 調用時會根據其中的偏移量而截斷所需要的值,而攻擊者傳入的 calldata 值中設定偏移量是 38,數值長度為 1,所以剛好截取出來的 data 值是 42966c680000000000000000000000000000000000000000c9112ec16d958e8da8180000760dc1e043d99394a10605b2fa08f123d60faf84。

具體可以參考 EVM opcode 中對 call 的描述(https://www.evm.codes/?fork=shanghai)。

由於 0x42966c68 為 burn 函數的函數簽名,所以會根據攻擊者構造的 data 值去 delegatecall 調用代幣合約的 burn 函數。

其中 _msgSender()函數被 ERC-2771 庫重寫。

由於 multicall 是通過 delegatecall 進行調用的,所以 isTrustedForwarder 傳入的 msg.sender 其實是 Forward 合約的地址,從而通過了判斷,最終導致 _msgSender()傳入的 calldata 的最後 20 個字節,即池子的位址 0x760dc1e043d99394a10605b2fa08f123d60faf84。

結論

本次攻擊的根本原因在於合約同時引用了 Multicall 和 ERC2771Context,攻擊者可以在轉發請求中插入惡意 calldata,利用 Multicall 的 delegatecall 功能來通過受信任轉發器的判斷,並操縱子調用中 _msgSender()的解析,從而可以操控任意使用者的代幣。

慢霧安全團隊建議專案方在編寫代幣合約時,不要同時使用 Multicall 和 ERC2771Context,如果預期需求需要同時引用的話,則必須檢查 calldata 長度是否符合預期或使用 openzeppelin 官方最新版本的 Multicall 和 ERC2771Context 合約。

參考

攻擊者位址:0xfde0d1575ed8e06fbf36256bcdfa1f359281455a

攻擊合約:0x6980a47bee930a4584b09ee79ebe46484fbdbdd0

相關攻擊交易:https://etherscan.io/tx/0xecdd111a60debfadc6533de30fb7f55dc5ceed01dfadd30e4a7ebdb416d2f6b6

受影響的版本詳情:https://blog.thirdweb.com/security-vulnerability/

緩解工具:https://mitigate.thirdweb.com/

免責聲明:作為區塊鏈資訊平臺,本站所發佈文章僅代表作者及嘉賓個人觀點,與 Web3Caff 立場無關。 文章內的資訊僅供參考,均不構成任何投資建議及要約,並請您遵守所在國家或地區的相關法律法規。