以太坊“後偷渡時代”盜幣之“拾荒攻擊”

作者:Sissel@知道創宇404區塊鏈安全研究團隊 發佈時間:2018/08/20

0x00 前 言

2018年08月01日,知道創宇404區塊鏈安全研究團隊發佈《金錢難寐,大盜獨行——以太坊 JSON-RPC 接口多種盜幣手法大揭祕》,針對 偷渡漏洞後偷渡時代的盜幣方式 進行了介紹,披露了 後偷渡時代 的三種盜幣方式:離線攻擊、重放攻擊和爆破攻擊。

在進一步的研究中,我們又發現了針對這些攻擊方式的補充:拾荒攻擊。攻擊者或求助於礦工,或本身擁有一定算力以獲得將交易打包進區塊的權利。在偷渡漏洞中,攻擊者在被攻擊節點構造gasPrice爲 0 的交易,等待用戶解鎖賬戶簽名廣播。攻擊者同時設置一個惡意節點,用於接收這筆交易。攻擊者將符合條件的交易打包,就可以實現 0 手續費完成轉賬。通過這種攻擊,攻擊者可以獲取到餘額不足以支付轉賬手續費或勉強足夠支付手續費節點上的所有以太幣,並在一定程度上可以防止其他攻擊者的競爭,可謂是 薅羊毛 的典範。

除此之外,在薅夠以太幣殘羹之後,攻擊者又盯上了這些以太幣已被盜光,但賬戶中殘留的代幣。直到現在,針對許多智能合約發行的代幣,一些被攻擊賬戶中的token,仍在小額地被攻擊者以拾荒攻擊盜走。

本文將從一筆零手續費交易談起,模擬復現盜幣的實際流程,對拾荒攻擊成功的關鍵點進行分析。

0x01 從一筆零手續費交易談起

在區塊鏈系統中,每一筆交易都應該附帶一部分gas以及相應的gasPrice作爲手續費,當該交易被打包進區塊,這筆手續費將用來獎勵完成打包的礦工。

《金錢難寐,大盜獨行——以太坊 JSON-RPC 接口多種盜幣手法大揭祕》中,我們提到了一個利用以太坊JSON-RPC接口的攻擊者賬號0x957cD4Ff9b3894FC78b5134A8DC72b032fFbC464(https://etherscan.io/address/0x957cD4Ff9b3894FC78b5134A8DC72b032fFbC464)。該攻擊者在公網中掃描開放的RPC端口,構造高手續費的交易請求,一旦用戶解鎖賬戶,便會將用戶餘額轉至攻擊者的賬戶或攻擊者創建的合約賬戶。

在分析該賬戶交易信息的時候,我們發現了一筆不符合常識的交易,先從這筆交易開始談起。

交易地址:0xb1050b324f02e9a0112e0ec052b57013c16156301fa7c894ebf2f80ac351ac22(https://https//etherscan.io/tx/0xb1050b324f02e9a0112e0ec052b57013c16156301fa7c894ebf2f80ac351ac22

Function: transfer(address _to, uint256 _value)MethodID: 0xa9059cbb[0]: 000000000000000000000000957cd4ff9b3894fc78b5134a8dc72b032ffbc464[1]: 000000000000000000000000000000000000000000000000000000000abe7d00

從0x00a329c0648769a73afac7f9381e08fb43dbea72(https://etherscan.io/address/0x00a329c0648769a73afac7f9381e08fb43dbea72)向合約MinereumToken(攻擊者的合約)的交易,雖然用戶餘額很少,但這筆交易使用了該賬戶所有餘額作爲value與合約交互,這筆交易使用了正常數量的gas,但它的gasPrice被設定爲0。

前文提到,攻擊者會使用較高的手續費來保證自己的交易成功,礦工會按照本節點的txpool中各交易的gasPrice倒序排列,優先將高gasPrice交易打包進之後的區塊。在這個世界上每時每刻都在發生着無數筆交易,在最近七日,成交一筆交易的最低gasPrice是3Gwei。這筆零手續費交易究竟是如何發生,又是如何打包進區塊的呢。

0x02 思路分析

在區塊鏈系統中,任何人都可以加入區塊鏈網絡,成爲其中一個節點,參與記賬、挖礦等操作。保證區塊鏈的可信性和去中心化的核心便是共識機制

共識機制

在以太坊中,礦工將上一區塊的哈希值、txpool中手續費較高的交易、時間戳等數據打包,不斷計算nonce來挖礦,最先得出符合條件的nonce值的礦工將擁有記賬權,得到手續費和挖礦獎勵。礦工將廣播得到的區塊,其他節點會校驗這一區塊,若無錯誤,則認爲新的區塊產生,區塊鏈高度增加。這就是各節點生成新區塊保持共識的過程。

將0 gasPrice交易完成需要確認兩個問題

  • 礦工是否會接受這個交易,並將其打包
  • 其餘節點接收到含此交易的區塊,是否會達成共識

下面我們來對0 gasPrice交易相關的操作進行測試。瞭解零手續費的交易如何產生,如何被txpool接受,打包了零手續費交易的區塊能否被認可,確認上述問題的答案。

0x03 零手續費交易測試

a. 單節點測試

首先,我們來確認此交易是否可以進入節點的txpool中,啓用一個測試鏈。默認rpc端口是8545,使用python的web3包發起一筆0 gasPrice轉賬。

geth --networkid 233 --nodiscover --verbosity 6 --ipcdisable --datadir data0 --rpc --rpcaddr 0.0.0.0 console

節點一發起轉賬的腳本,轉帳前要解鎖賬戶

from web3 import Web3, HTTPProviderweb3 = Web3(HTTPProvider("http://localhost:8545/"))print(web3.eth.accounts)# 轉帳前要解鎖賬戶web3.eth.sendTransaction({ "from":web3.eth.accounts[0], "to":web3.eth.accounts[1], "value": 10, "gas":21000, "gasPrice":0, })

交互結果

> txpool.content { pending: {}, queued: {} } > eth.getBalance(eth.accounts[0]) 800000000 > personal.unlockAccount(eth.accounts[0],'sissel') true > INFO [08-14|11:20:14.972] Submitted transaction fullhash=0x72e81751d2517807cabad24102d3cc2f0f4f2e8b92f1f106f1ee0bf6be734fe4 recipient=0x92636b228148e2824cB8d472Ef2F4e76f2F5059C > txpool.content { pending: { 0x092fda221a114FA702e2f59C217C92cfEB63f5AC: { 3: { blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000", blockNumber: null, from: "0x092fda221a114fa702e2f59c217c92cfeb63f5ac", gas: "0x5208", gasPrice: "0x0", hash: "0x72e81751d2517807cabad24102d3cc2f0f4f2e8b92f1f106f1ee0bf6be734fe4", input: "0x", nonce: "0x3", r: "0x1eca20e3f371ed387b35ca7d3220789399a3f64c449a825e0fa7423b96ce235c", s: "0x35a58e5cb5027c7903c1f1cc061ae846fb5150186ebbabb2b0766e4cbfc4aee6", to: "0x92636b228148e2824cb8d472ef2f4e76f2f5059c", transactionIndex: "0x0", v: "0x42", value: "0xa" } } }, queued: {} } > miner.start(1) INFO [08-14|11:20:35.715] Updated mining threads threads=1 INFO [08-14|11:20:35.716] Transaction pool price threshold updated price=18000000000 null INFO [08-14|11:20:35.717] Starting mining operation > INFO [08-14|11:20:35.719] Commit new mining work number=115 txs=1 uncles=0 elapsed=223µs > mINFO [08-14|11:20:36.883] Successfully sealed new block number=115 hash=ce2f34…210039 INFO [08-14|11:20:36.885] ? block reached canonical chain number=110 hash=2b9417…850c25 INFO [08-14|11:20:36.886] ? mined potential block number=115 hash=ce2f34…210039 INFO [08-14|11:20:36.885] Commit new mining work number=116 txs=0 uncles=0 elapsed=202µs > miner.stop() true > eth.getBalance(eth.accounts[0]) 799999990

節點一發起的零手續費交易成功,並且挖礦後成功將該交易打包進區塊中。

b. 多節點共識測試

現在加入另一個節點

geth --datadir "./" --networkid 233 --rpc --rpcaddr "localhost" --port 30304 --rpcport "8546" --rpcapi "db,eth,net,web3" --verbosity 6 --nodiscover console 使用這些方法添加節點 > admin.nodeInfo > admin.addPeer() > admin.peers

節點一仍使用剛纔的腳本發起零手續費交易,節點一的txpool中成功添加,但節點二因爲gasPrice非法拒絕了此交易。

TRACE[08-15|10:09:24.682] Discarding invalid transaction hash=3902af…49da03 err="transaction underpriced" > txpool.content []

在geth的配置中發現了與此相關的參數

--txpool.pricelimit value Minimum gas price limit to enforce for acceptance into the pool (default: 1)

將其啓動時改爲0,但節點二的txpool中仍未出現這筆交易。

閱讀源碼知,此參數確實是控制txpool增加的交易的最低gasPrice,但不能小於1。

if conf.PriceLimit < 1 { log.Warn("Sanitizing invalid txpool price limit", "provided", conf.PriceLimit, "updated", DefaultTxPoolConfig.PriceLimit) conf.PriceLimit = DefaultTxPoolConfig.PriceLimit }

令節點一(txpool中含0 gasPrice)開始挖礦,將該交易打包進區塊後,發現節點二認可了此區塊,達成共識,兩節點高度均增長了。

得到結論:

  • 零手續費交易,通常情況下只有發起者的txpool可以接收,其餘節點無法通過同步此交易。如若需要,必須進行修改geth源碼等操作。
  • 雖然這筆交易無法進入其他節點的txpool,但對於含此交易的區塊,可以達成共識。

我們將進行簡要的源代碼分析,支持我們的結論。

0x04 源碼分析

(以下的代碼分析基於https://github.com/ethereum/go-ethereum的當前最新提交:commit 6d1e292eefa70b5cb76cd03ff61fc6c4550d7c36)

以太坊目前最流行的節點程序(Geth/Parity)都提供了RPC API,用於對接礦池、錢包等其他第三方程序。首先確認一下節點在打包txs時,代碼的實現。

i. 交易池

代碼路徑:./go-ethereum/core/tx_pool.go

// TxPool contains all currently known transactions. Transactions// enter the pool when they are received from the network or submitted// locally. They exit the pool when they are included in the blockchain.type TxPool struct { config TxPoolConfig chainconfig *params.ChainConfig chain blockChain gasPrice *big.Int //最低的GasPrice限制 /* 其他參數 */}

生成一個tx實例時,發現有對gasPrice的最低要求,具體在這個函數中會拒絕接收此交易。

// validateTx checks whether a transaction is valid according to the consensus// rules and adheres to some heuristic limits of the local node (price and size).func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { // 在這裏是gasPrice的校驗 if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 { return ErrUnderpriced } /* ... */ return nil}

ii. 移除低於閾值的交易

代碼路徑:./go-ethereum/core/tx_list.go 並且在處理txs中,會將低於閾值的交易刪除,但本地的交易不會刪除。

// Cap finds all the transactions below the given price threshold, drops them// from the priced list and returs them for further removal from the entire pool.func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transactions { drop := make(types.Transactions, 0, 128) // Remote underpriced transactions to drop save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep for len(*l.items) > 0 { // Discard stale transactions if found during cleanup tx := heap.Pop(l.items).(*types.Transaction) if _, ok := (*l.all)[tx.Hash()]; !ok { // 如果發現一個已經刪除的,那麼更新states計數器 l.stales-- continue } // Stop the discards if we've reached the threshold if tx.GasPrice().Cmp(threshold) >= 0 { // 如果價格不小於閾值, 那麼退出 save = append(save, tx) break } // Non stale transaction found, discard unless local if local.containsTx(tx) { //本地的交易不會刪除 save = append(save, tx) } else { drop = append(drop, tx) } } for _, tx := range save { heap.Push(l.items, tx) } return drop}

以上部分爲區塊鏈網絡內一節點,嘗試接收或加入 0 gasPrice 的交易時,會有部分過濾或規則限制。但通過修改源碼,我們依然可以做到將 0 gasPrice 的交易合法加入到區塊中,並進行之後的nonce計算。下面繼續源碼分析,考察通過此方式得到的區塊,是否可以被其他節點接受,達成共識。

iii. 共識校驗

代碼路徑:./go-ethereum/consensus/consensus.go 這是geth中,提供的共識算法engine接口

type Engine interface { // 簽名 Author(header *types.Header) (common.Address, error) /* 驗證了header、seal,處理難度等函數 ... */ // 預處理區塊頭信息,修改難度等 Prepare(chain ChainReader, header *types.Header) error // 區塊獎勵等,挖掘出區塊後的事情 Finalize(chain ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) // 計算nonce,若收到更高的鏈,則退出 Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) // 計算難度值 CalcDifficulty(chain ChainReader, time uint64, parent *types.Header) *big.Int // APIs returns the RPC APIs this consensus engine provides. APIs(chain ChainReader) []rpc.API // Close terminates any background threads maintained by the consensus engine. Close() error}

查看VerifySeal(),發現校驗瞭如下內容:

  • 不同模式下的一些特殊處理
  • 難度是否合法
  • nonce值是否合法
  • gas值是否合法

可以看到,其他節點針對共識,檢查了簽名、nonce等內容,對於其中零手續費的交易沒有檢驗。換句話說,零手續費的交易雖然不能激勵礦工,但它依然是合法的。

0x05 利用流程

攻擊者首先以偷渡漏洞利用的方式,構造零手續費,正常的transfer交易。待用戶解鎖賬戶後,廣播交易。具體流程見下圖:

0x06 小結

由此我們可以得出,0 gasPrice這樣的特殊交易,有如下結論:

  • 通常情況下,0 gasPrice可通過節點自身發起加入至txpool中。
  • 以 geth 爲例,修改geth部分源碼重新編譯運行,該節點方可接受其他節點發出的特殊交易(目標賬戶發起的0 gasPrice交易)。此爲攻擊者需要做的事情。
  • 0 gasPrice的交易可以打包進區塊,並且符合共識要求。

因爲json-rpc接口的攻擊方式中,攻擊者可以通過偷渡漏洞簽名 0 gasPrice交易並廣播。通過收集此類0 gasPrice交易並添加至部分礦工的txpool中,當該礦工挖出一個新的區塊,這類交易也將會被打包。即攻擊者可能與部分礦工聯手,或攻擊者本身就有一定的運算能力,讓礦工不再遵循誠實挖礦維護區塊鏈系統的原則。

0x07 利用價值及防禦方案

因爲零手續費交易的出現,諸多低收益的攻擊都將擁有意義。

提高收益

攻擊者可以通過此種方式,結合其他的攻擊手法,將被攻擊賬戶中的餘額全部轉出,達到了收益最大化。

羊毛薅盡

依照《金錢難寐,大盜獨行——以太坊 JSON-RPC 接口多種盜幣手法大揭祕》中提到的攻擊方式,對於賬戶餘額較少,甚至不足以支付轉賬手續費的情況,可通過上文提到的薅羊毛式攻擊方案,將賬戶中的殘羹收入囊中。由於此交易gasPrice爲0,可在一區塊中同時打包多個此類型交易,例如此合約下的多組交易:0x1a95b271b0535d15fa49932daba31ba612b52946(https://etherscan.io/address/0x1a95b271b0535d15fa49932daba31ba612b52946),此區塊中的幾筆交易:4788940(https://etherscan.io/txs?block=4788940&p=6

偷渡代幣

在被盜賬戶已無以太幣的情況下,攻擊者發現這些賬戶還存有部分智能合約發行的代幣。沒有以太幣便不能支付gas進行轉賬,零手續費交易可以完美解決這個問題。直到現在,有諸多無以太幣的被攻擊賬戶,仍在被此方式轉賬代幣。

防禦方案

由於0 gasPrice交易只是擴展其他攻擊方案的手法,還應將防禦着眼在之前json-rpc接口利用。

  • 對於有被偷渡漏洞攻擊的痕跡或可能曾經被偷渡漏洞攻擊過的節點,建議將節點上相關賬戶的資產轉移到新的賬戶後廢棄可能被攻擊過的賬戶。
  • 建議用戶不要使用弱口令作爲賬戶密碼,如果已經使用了弱口令,可以根據1.2節末尾的內容解出私鑰內容,再次通過 geth account import 命令導入私鑰並設置強密碼。
  • 如節點不需要簽名轉賬等操作,建議節點上不要存在私鑰文件。如果需要使用轉賬操作,務必使用 personal_sendTransaction 接口,而非 personal_unlockAccount 接口。

0x08 影響規模

我們從上面說到的0 gasPrice的交易入手。調查發現,近期依然有許多交易,以0 gasPrice成交。多數0手續費交易都出自礦池:0xb75d1e62b10e4ba91315c4aa3facc536f8a922f5(https://etherscan.io/address/0xb75d1e62b10e4ba91315c4aa3facc536f8a922f5)和0x52e44f279f4203dcf680395379e5f9990a69f13c(https://etherscan.io/address/0x52e44f279f4203dcf680395379e5f9990a69f13c),例如區塊 6161214(https://etherscan.io/block/6161214)、6160889(https://etherscan.io/block/6160889)等。

我們注意到,這些0 gasPrice交易,僅有早期的少部分交易,會攜帶較少的以太幣,這符合我們對其薅羊毛特性的預計。經統計,從2017年6月起,陸續有748個賬戶總計24.2eth被零手續費轉賬。

在其中也找到了《金錢難寐,大盜獨行——以太坊 JSON-RPC 接口多種盜幣手法大揭祕》中提到的重放攻擊,造成的賬戶損失:0x682bd7426ab7c7b4b5beed331d5f82e1cf2cecc83c317ccee6b4c4f1ae34d909(https://etherscan.io/tx/0x682bd7426ab7c7b4b5beed331d5f82e1cf2cecc83c317ccee6b4c4f1ae34d909

被盜走0.05eth

在這些0 gasPrice中,更多的是對合約發行的TOKEN,進行的轉賬請求,將用戶賬戶中的token轉移至合約擁有者賬戶(https://etherscan.io/address/0xe386e3372e3d316ae063af50c38704ec6fba5149)中,例如:

該賬戶的tx記錄。

攻擊者擁有多個礦池的算力,將衆多被攻擊賬戶擁有的多種token,轉移到相應的賬戶中,雖然單筆交易金額較小,但可進行此種攻擊方式的賬戶較多,合約較多,且不需要手續費。積少成多,直到現在,攻擊者仍在對這些代幣進行着拾荒攻擊。

0x09 結 語

區塊鏈系統基於去中心化能達成交易的共識,一個前提就是,絕大多數的礦工,都會通過誠實挖礦來維持整個比特幣系統。當礦工不再誠實,區塊鏈的可信性和去中心化將會大打折扣。當黑客聯合礦工,或黑客本身擁有了算力成爲礦工,都會在現有攻擊手法的基礎上,提供更多的擴展攻擊方案。

0 gasPrice交易的出現,違背了區塊鏈設計初衷,即應對礦工支付手續費作爲激勵。 區塊鏈技術與虛擬貨幣的火熱,賦予了鏈上貨幣們巨大的經濟價值,每個人都想在區塊鏈浪潮中分得一杯羹。黑客們更是如此,他們作爲盜幣者,絞盡腦汁的想着各個角度攻擊區塊鏈與合約。當黑客棲身於礦工,他們不但能挖出區塊,也能挖出漏洞。

參 考 鏈 接

  1. json-rpc接口盜幣手法:金錢難寐,大盜獨行——以太坊 JSON-RPC 接口多種盜幣手法大揭祕
  2. https://www.reddit.com/r/ethereum/comments/7lx1do/a_christmas_mystery_sweepers_and_zero_gas_price/
  3. how-to-create-your-own-private-ethereum-blockchain-dad6af82fc9f:https://medium.com/mercuryprotocol/how-to-create-your-own-private-ethereum-blockchain-dad6af82fc9f
  4. 零手續費交易:https://etherscan.io/tx/0xb1050b324f02e9a0112e0ec052b57013c16156301fa7c894ebf2f80ac351ac22
  5. 慢霧命名的“以太坊黑色情人節”,細節:以太坊生態缺陷導致的一起億級代幣盜竊大案: https://mp.weixin.qq.com/s/Kk2lsoQ1679Gda56Ec-zJg
  6. 揭祕以太坊中潛伏多年的“偷渡”漏洞,全球黑客正在瘋狂偷幣: https://paper.seebug.org/547/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章