慢霧:DeFi平臺Lendf.Me被黑細節分析及防禦建議

買賣虛擬貨幣
來源:慢霧安全團隊


前言


據慢霧區情報,以太坊 defi 平臺 lendf.me 遭受重入漏洞攻擊。慢霧安全團隊在收到情報後隨即對此次攻擊事件展開分析,並快速定位了問題所在。 

據慢霧科技反洗錢(aml)系統初步統計分析,lendf.me 被攻擊累計的損失約 24,696,616 美元,具體盜取的幣種及數額為:

weth: 55159.02134,
wbtc: 9.01152,
chai: 77930.93433,
hbtc: 320.27714,
husd: 432162.90569,
busd: 480787.88767,
pax: 587014.60367,
tusd: 459794.38763,
usdc: 698916.40348,
usdt: 7180525.08156,
usdx: 510868.16067,
imbtc: 291.3471

之後攻擊者不斷透過 1inch.exchange、paraswap、tokenlon 等 dex 平臺將盜取的幣兌換成 eth 及其他代幣。 

以下是詳細分析過程。

攻擊細節


本次對 lendf.me 實施攻擊的攻擊者地址為 0xa9bf70a420d364e923c74448d9d817d3f2a77822,攻擊者透過部署合約 0x538359785a8d5ab1a741a0ba94f26a800759d91d 對 lendf.me 進行攻擊。 

透過在 etherscan 上檢視攻擊者的其中一筆交易:https://etherscan.io/tx/0xae7d664bdfcc54220df4f18d339005c6faf6e62c9ca79c56387bc0389274363b



我們發現,攻擊者首先是存入了 0.00021593 枚 imbtc,但是卻從 lendf.me 中成功提現了 0.00043188 枚 imbtc,提現的數量幾乎是存入數量的翻倍。那麼攻擊者是如何從短短的一筆交易中拿到翻倍的餘額的呢?這需要我們深入分析交易中的每一個動作,看看究竟發生了什麼。 

透過把該筆交易放到 bloxy.info 上檢視,我們能知道完整的交易流程



透過分析交易流程,我們不難發現攻擊者對 lendf.me 進行了兩次 supply() 函式的呼叫,但是這兩次呼叫都是獨立的,並不是在前一筆 supply() 函式中再次呼叫 supply() 函式。 

緊接著,在第二次 supply() 函式的呼叫過程中,攻擊者在他自己的合約中對 lendf.me 的 withdraw() 函式發起呼叫,最終提現



在這裡,我們不難分析出,攻擊者的 withdraw() 呼叫是發生在 transferfrom 函式中,也就是在 lendf.me 透過 transferfrom 呼叫使用者的 tokenstosend() 鉤子函式的時候呼叫的。很明顯,攻擊者透過 supply() 函式重入了 lendf.me 合約,造成了重入攻擊,那麼具體的攻擊細節是怎樣的呢?我們接下來跟進 lendf.me 的合約程式碼。

程式碼分析


lendf.me 的 supply() 函式在進行了一系列的處理後,會呼叫一個 dotransferin 函式,用於把使用者提供的幣存進合約,然後接下來會對 market 變數的一些資訊進行賦值。回顧剛才說的攻擊流程,攻擊者是在第二次 supply() 函式中透過重入的方式呼叫了 withdraw() 函式提現,也就是說在第二次的 supply() 函式中,1590 行後的操作在 withdraw() 之前並不會執行,在 withdraw() 執行完之後,1590 行後的程式碼才會繼續執行。這裡的操作導致了攻擊者可提現餘額變多。 

我們深入分析下 supply() 函式



根據上圖,可以看到,在 supply() 函式的末尾,會對 market 和使用者的餘額進行更新,在這之前,使用者的餘額會在函式的開頭預先獲取好並儲存在 localresults.usersupplycurrent,如下:



透過賦值給 localresults 變數的方式,使用者的轉入資訊會先暫時儲存在這個變數內,然後此時攻擊者執行 withdraw() 函式,我們看下 withdraw() 函式的程式碼:



這裡有兩個關鍵的地方: 

1、在函式的開頭,合約首先獲取了 storage 的 market 及 supplybalance 變數。
2、在 withdraw() 函式的末尾,存在同樣的邏輯對 market 使用者的餘額資訊 (supplybalance) 進行了更新,更新值為扣除使用者的提現金額後的餘額。 

按正常的提現邏輯而言,在 withdraw() 單獨執行的時候,使用者的餘額會被扣除並正常更新,但是由於攻擊者將 withdraw() 嵌入在 supply() 中,在 withdraw() 函式更新了使用者餘額 (supplybalance) 後,接下來在 supply() 函式要執行的程式碼,也就是 1590 行之後,使用者的餘額會再被更新一次,而用於更新的值會是先前 supply() 函式開頭的儲存在localresults 中的使用者原先的存款加上攻擊者第一次呼叫 supply() 函式存款的值。
在這樣的操作下,使用者的餘額雖然在提現後雖然已經扣除了,但是接下來的 supply() 函式的邏輯會再次將使用者未扣除提現金額時的值覆蓋回去,導致攻擊者雖然執行了提現操作,但是餘額不但沒有扣除,反而導致餘額增加了。透過這樣的方式,攻擊者能以指數級別的數量提現,直至把 lendf.me 提空。

防禦建議


針對本次攻擊事件慢霧安全團隊建議: 

  1. 在關鍵的業務操作方法中加入鎖機制,如:openzeppelin 的 reentrancyguard
  2. 開發合約的時候採用先更改本合約的變數,再進行外部呼叫的編寫風格
  3. 專案上線前請優秀的第三方安全團隊進行全面的安全審計,儘可能的發現潛在的安全問題
  4. 多個合約進行對接的時候也需要對多方合約進行程式碼安全和業務安全的把關,全面考慮各種業務場景相結合下的安全問題
  5. 合約儘可能的設定暫停開關,在出現“黑天鵝”事件的時候能夠及時發現並止損
  6. 安全是動態的,各個專案方也需要及時捕獲可能與自身專案相關的威脅情報,及時排查潛在的安全風險

附:
openzeppelin reentrancyguard: https://github.com/openzeppelin/openzeppelin-contracts/blob/master/contracts/utils/reentrancyguard.sol

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;