iT邦幫忙

2022 iThome 鐵人賽

DAY 26
1
Web 3

Smart Contract Development Breakdown系列 第 26

Day 26 - Contract Vulnerability & Dev. Tool

  • 分享至 

  • xImage
  •  

Contract Vulnerability & Dev. Tool

Synchronization Link Tree


Intro.

終於來到最後一個大章節了,我把 Security & Extensions 定調為一個可以更熟悉語法與開發的一個環節,因為注重安全性這件事情除了對語法以及其底層架構的了解之外,還得有一定開發過程的邏輯與經驗。本篇文章主要是提出一些容易出現的流程錯誤或者漏洞,雖然程式碼能夠正常且有效率地運作,但不代表是安全的喔!

看完這些章節之後也推薦大家去學習使用 Hardhat 或 Foundry 撰寫測試,我之前也有在 TEM 發過一篇文寫 Foundry 測試:Quick Look DeFi Contract Testing With Foundry,大家可以當作延伸閱讀。

我自己認為要提升開發的安全性跟熟悉度有幾個點:

  1. 多做 Side Project
  2. 多寫測試
  3. 多玩 CTF
  4. 多與同伴進行 peer review 給意見與觀察別人的 code

我自己覺得要熟悉智能合約語法以及促進安全性,多玩 CTF 的幫助是很大的哈哈哈哈。

此外本文最後還有多出來的 debug list,其實一直以來都有不少的 Debug 需求出現在私訊,所以就大概整理了一些 DeBug List 來幫助初學者基本檢查一下是不是犯了哪些錯誤。此外還分享了一些開發小工具,算是這整個系列文的 Appendix!

Vulnerability & Error in Contract

在合約中最常發生的錯誤有幾個:

  1. 沒有完善定義函式的 owner 權限(onlyOwner)與可視性(public, private, internal, external)、可編輯性(view, pure)
  2. 變數或資料結構的 Overflow, Underflow
  3. 版本 Bug,需要特別注意 Breaking Change 與公告

那除了以上的錯誤還有許多各種容易發生的漏洞,大家可以多多鑽研!

Reentrancy Attack

第一個也是最該注意的攻擊就是重送攻擊,重送攻擊和重放(Replay)不同,Replay 會藉由複製交易資訊並重新發送達到一些目的,而重送則是在一筆交易(一個函式呼叫)中再塞一個函式呼叫,讓在「這筆交易」中可以執行兩次以上的 operations。

例如以下 Solidity by Example - Re-Entrancy 的例子:

在目標合約中我們可以藉由以下這個 withdraw() 來從合約之中提領資金,需要注意的是兩段程式碼的先後順序:(bool sent, ) = msg.sender.call{value: bal}("");balances[msg.sender] = 0;

function withdraw() public {
    uint bal = balances[msg.sender];
    require(bal > 0);

    (bool sent, ) = msg.sender.call{value: bal}("");
    require(sent, "Failed to send Ether");

    balances[msg.sender] = 0;
}

如果我們使用一個 Attack 合約來呼叫這個 withdraw() 時,在碰到目標合約的 (bool sent, ) = msg.sender.call{value: bal}(""); 時我們的 Attack 合約會接收到 ether,此時觸發 fallback(),藉此又能夠再呼叫一次目標合約的 withdraw()

然而這時候第一次呼叫 withdraw() 還沒有碰到 balances[msg.sender] = 0;,於是乎又可以再領一次了。

fallback() external payable {
    if (address(etherStore).balance >= 1 ether) {
        etherStore.withdraw();
    }
}

function attack() external payable {
    require(msg.value >= 1 ether);
    etherStore.deposit{value: 1 ether}();
    etherStore.withdraw();
}

Signature Replay

當我們在製作線上 Judging System 的時候,遇到一個最大的問題其實就是其他的同學可以藉由「重放其他人的交易資訊」來完成題目,所以無論是在函式中要避免別人使用他人的 tx data 或避免 transaction approve 被重放,我們都可以考慮加上 nonce 或者是「用戶的地址」當作部分元素。nonce 的部分我們前面的文章內容已經提到不少了,使用用戶地址的原因是,在限制 msg.sender 的情況下,除非用戶把私鑰進行轉移或者販賣給他人,否則他人是沒有辦法做出同一筆交易的。

e.g. 某個資優生利用一把新的私鑰完成作業後把這個私鑰跟答案賣給其他同學。

Selfdestruct

selfdestruct() 可以讓一個合約被從區塊鏈刪除(實際上是停止更新),然後可以把這個合約中的剩餘資產轉給想轉的人,主要是用於把舊版合約停掉以避免用戶仍在使用舊版的合約。selfdestruct() 的存在其實還蠻危險的,例如有心人士可以刻意地毀掉一個合約或者是利用 selfdestruct() 強迫執行某些交易。

最容易發生問題的就是誤把 selfdestruct() 設為 public 或者 external 又沒有掛上 onlyOwner 等 modifier。或者是「利用 address(this).balance 來作為 condition statement 或 require() 的敘述」時可能會發生問題。簡單來說 attacker 可以透過幾種方式讓一個合約強迫接收 ether,而 selfdestruct() 就是一個。如果今天 address(this).balance 因為接收了某個合約的 selfdestruct() 剩餘資金,可能就會脫離 owner 當初寫這些 condition statement 的掌控。

Front Running

交易被送出之後並不會直接被執行和打包進區塊中,在交易真正被礦工寫到區塊鏈上之前,合約中的狀態都是不會改變的。因此我們在觀察 tx pool 之後嘗試使用更高的 gas fee 搶先在其他人之前被打包到區塊裡面。這邊有兩個技術點:一是要查看 tx pool 中正確答案的交易,二是要選擇一個適合的 gas fee 獲得優先權。

Dev. Tool

Framework

Static Analysis Tool

目前最知名的靜態分析器就是 Slither,會用來檢查合約裡面的漏洞,在許多審計公司做審計的時候也會對合約用各種靜態分析器進行分析,然後給出各種分數。不過靜態分析器終究是個工具,用「眼睛」以及「測試」檢查合約才是最重要的!

Slither 除了本身的分析功能,還可以透過插件檢查各種合約的設計與狀況,例如 slither-check-upgradeability 能夠檢查可升級合約(有沒有 storage collision 之類的),而 slither-check-erc 可以檢查代幣合約的狀況(例如繼承合約沒有破壞一致性等等的)。

另外一個分析器是 Mythril,同樣也是一個智能合約的安全分析工具。由於是直接分析 bytecode,Mythril 也能直接對其他 EVM-Compatible 的鏈上合約進行分析!這邊主要是使用 MythX 來分析 Solidity,能夠在 Remix 或者上面我們提到的開發框架中加上 MythX 的 Extensions 作分析用。

還有一個看起來超酷的分析工具是 DeFiSafety 但我沒使用過,大家可以自己看看!

Integration

根據不同項目還有公司的需求,可能會需要做 Gnosis / The GraphSanpshot / Tally 的整合。


Closing

那除了以上我提到的合約漏洞,明天在審計的部分我也會把一些審計公司或者審計工作室常常會特別去看的部分整理出來,大家在完成自己的專案之後也可以針對這些細項去做研究!

Reference

Appendix: Toolkit

Appendix: Web3 Development Debugger

這是我給帶的學生們的「問問題前的詳閱須知」:

  1. 所有事情開始之前都先看一下, 宣告的 networkID 跟現在自己的 Metamask 的網路、合約佈署的一不一樣
  2. Infura 或其他節點商的 Project Key 要貼對
  3. ABI 要貼對,import 路徑要寫對,看一下 inspect 裡的 error 是什麼,跑不動或按鈕沒反應要善用 console.log(),不是先找人幫 de...
  4. 合約可以先在 Remix 試試看正常的樣子是怎麼樣,如果在 Remix 都跑不動,在 Truffle, Hardhat 怎麼可能跑得動...
  5. 要記得宣告 web3.js,或者是 ethers.js 都行,但一定要很清楚自己導入的 API 在幹嘛,不然未來某一天會後悔的
  6. 函式名子要寫對,參數型態跟數量都要傳對,不是每個人的合約 Mint Function 都叫做 mint()
  7. 物件宣告要寫對,web3.eth.Contract.methods 還是什麼的好好查完官方文件再寫,名子可以自己令但 member function 的關係是不變的
  8. 不會寫 React 也沒關係,但要問問題前先去學,完全不會的話我怎麼解釋 React 相關的 Bug 呢...
  9. npm install 有問題的時候,可以設環境變數(.env)、升降版本、刪掉該 module 重載(請慎重考慮)、改用 yarn,問問題之前先查一下 stackoverflow
  10. 升降版本的時候要注意 node.js, npm 的版本
  11. 注意 react-script, babel, webpack, browserify 的版本組合
  12. Opensea Testnet 支援的是 Rinkeby,但 Rinkeby 停掉之後會是什麼我不知道(2022/9/25 更新:是 Goerli)
  13. 用主網的 Etherscan 不會找到你的交易,因為你部屬在測試網上...
  14. Metamask 中你的錢包有一個地址,合約佈署之後會有合約地址,交易會有一個交易 HASH 跟 TO 代表目標地址,佈署是一種交易
  15. utils 裡面要傳「"ether"」,傳「ether」只有幫你 de 的助教看得懂而已,React 會說你沒宣告過不是因為它的英文比助教爛...
  16. 要搞懂各種進制、型態,ascii、256bit、HEX、BigNumber,不是全部都是 decimal 的
  17. 記得要 await,還那麼年輕這麼快就 render 了真的不行
  18. http, https, crypto… 那些 module 問題可以透過在降 react-scripts 的 config 中 webpack-config.js 加上 resolve fallback 相關 browerify 來解決。之後如果出現 babel 問題要在 .env 加上 SKIP(看報錯)
  19. NFT 的 BaseURI 最後要記得加 "/"
  20. URI 是位置、URL 是路徑,是 baseURL = ""setBaseURI()

最後歡迎大家拍打餵食大學生0x2b83c71A59b926137D3E1f37EF20394d0495d72d


上一篇
Day 25 - EVM(Ethereum Virtual Machine) & Memory Pool
下一篇
Day 27 - Contrac Security: Auditing
系列文
Smart Contract Development Breakdown30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言