「虛擬印鈔機」Popsicle Finance 雙花攻擊分析
BlockSec
2021-08-04 13:10
本文约3499字,阅读全文需要约14分钟
​北京時間2021 年8 月4 日早上6 點(區塊12955063),Popsicle Finance 項目下的多個機槍池被攻擊,損失金額超過兩千萬美元,是迄今為止DeFi 領域發生的損失數額最大的單筆攻擊之一。

北京時間2021 年8 月4 日早上6 點(區塊12955063),Popsicle Finance 項目下的多個機槍池被攻擊,損失金額超過兩千萬美元,是迄今為止DeFi 領域發生的損失數額最大的單筆攻擊之一。通過分析攻擊交易及項目代碼我們發現,此次攻擊是一個利用項目的記賬漏洞進行多次提取的攻擊(Double-Claiming Rewards)。下面我們通過代碼和攻擊流程分析此次攻擊。

代碼分析

Popsicle Finance 是一個涉及多個鏈的機槍池(Yield Optimization Platform)。

用戶首先調用deposit 函數向機槍池存入一定的流動性,並獲得Popsicle LP Token (以下簡稱PLP Token)作為存款的份額證明。 Popsicle Finance 會將用戶提供的流動性存入Uniswap 等底層池子並獲得收益。

用戶還可以調用withdraw 函數,根據用戶持有的PLP Token 所代表的流動性份額,從機槍池取回流動性。 Popsicle Finance 會將PLP Token 對應的流動性從Uniswap 等底層池子中取回給用戶。

最後,用戶在機槍池中存的流動性會隨著時間產生一定的收益(Yield),會累計在合約的用戶狀態中。用戶可以調用collectFees 函數取回部分存款獎勵。

本次攻擊的核心函數正是collectFees 函數。下面我們逐步分析其代碼。首先獲得存儲在userInfo 中的用戶狀態。其中用戶狀態中的token0Rewards 和token1Rewards 是由於用戶存款而累積的獎勵。

接下來計算該合約中,對應機槍池的Token 對的Balance。如果在合約中有足夠的Balance,就按金額將Reward 支付給用戶;否則會調用pool.burnExactLiquidity 從底層pool 取回流動性返回給用戶。

最後,會將記錄在userInfo 中的Rewards 狀態進行更新。看到這裡,機槍池的代碼實現還是比較符合邏輯的。但是在函數開頭我們發現了updateVault modifier,這個函數會在collectFees 的函數體之前運行,漏洞也許在updateVault 相關的函數中。

以上是updateVault 相關函數的實現。過程如下:

  • 首先調用_earnFees 向底層pool 獲取積累的Fee;

  • 隨後調用_tokenPerShare 更新token0PerShareStored 和token1PerShareStored 參數,這兩個參數代表了池子中每個share 代表的token0 和token1 的數量,即機槍池的每個份額計代表的Token 對數量;

  • 最後調用fee0Earned 和fee1Earned 更新對應到這個用戶的存款Rewards (即user.token0Rewards 和user.token1Rewards)。

以上是fee0Earned 和fee1Earned 函數的實現,兩個函數實現相同,都實現了這樣一個公式(以_fee0Earned 為例):

user.token0Rewards += PLP.balanceOf(account) * (fee0PerShare - user.token0PerSharePaid) / 1e18

也就是說,該函數會在原有的user.token0Rewards 基礎上,根據用戶擁有的PLP Token 數量計算應給用戶發放的Fee 的份額。

但我們注意到這個函數是增量的,也就是說即使用戶並沒有持有PLP Token (PLP.balanceOf(account) 為0),該函數仍會返回保存在user.token0Rewards 中記賬的存款獎勵。

因此對於整個合約,我們發現兩個重要的邏輯缺陷:

  • 用戶的存款獎勵是記錄在user.token0Rewards 和user.token1Rewards 中的,並不與任何PLP Token 或其他東西有任何形式的綁定。

  • 用於取回存款收益的collectFees 函數僅僅依賴於記賬的user.token0Rewards 和user.token1Rewards 狀態,即使用戶並未持有PLP Token,仍可以取出對應的存款獎勵。

我們假想一個攻擊流程:

  • 攻擊者向機槍池中存入一定的流動性,獲得一部分PLP Token。

  • 攻擊者調用collectFees(0, 0),後者會更新攻擊者的存款獎勵,即狀態變量user.token0Rewards 的值,但並沒有真正取回存款獎勵。

  • 攻擊者將PLP Token 轉給自己控制的其他合約,再調用collectFees(0, 0) 更新狀態變量user.token0Rewards。也就是說通過不斷地流轉PLP Token 並調用collectFees(0, 0),攻擊者復制了這些PLP Token 對應的存款獎勵。

  • 最後,攻擊者從以上各個地址調用collectFees 函數,取回真正的獎勵。此時雖然這些賬戶中並沒有PLP Token,但由於記賬在user.token0Rewards 沒有更新,攻擊者因此得以取出多份獎勵。

用現實生活中的例子來描述這個攻擊,相當於我向銀行存錢,銀行給了我一張存款憑證,但這張憑證沒有防偽措施也沒有和我綁定,我把憑證複印了幾份發給不同的人,他們每個人都憑藉這個憑證向銀行取回了利息。

攻擊流程分析

通過以上的代碼分析,我們發現了Popsicle Finance 在機槍池實現上的漏洞。下面我們對攻擊交易進行深入分析,看攻擊者是怎樣利用這個漏洞的。

攻擊者的總體流程如下:

  • 攻擊者創建了三個交易合約。其中一個用於發起攻擊交易,另外兩個用於接收PLP Token 並調用Popsicle Finance 機槍池的collectFees 函數取回存款獎勵。

  • 通過閃電貸從AAVE 借出大量流動性。攻擊者選擇了Popsicle Finance 項目下的多個機槍池,向AAVE 借出了對應這些機槍池的六種流動性。

  • 進行Deposit-Withdraw-CollectFees循環。攻擊者一共進行了8 次循環,分別攻擊了Popsicle Finance 項目下的多個機槍池,取出了大量流動性。

  • 向AAVE 歸還閃電貸,並將獲利通過Tornado Cash 洗錢。

本次攻擊交易主要由數個Deposit-Withdraw-CollectFees 循環構成,每一個循環的示意圖如上圖所示。根據我們的分析,邏輯如下:

  • 攻擊者首先將閃電貸借來的流動性存入機槍池中,獲得一定量的PLP Token。

  • 攻擊者將PLP Token 轉給攻擊合約2。

  • 攻擊合約2 調用機槍池的collectFees(0, 0) 函數,設置合約2 對應的user.token0Rewards 和user.token1Rewards 狀態。

  • 攻擊合約2 將PLP Token 轉給攻擊合約3。

  • 和攻擊合約2 的操作類似,攻擊合約3 調用機槍池的collectFees(0, 0) 函數,設置合約2 對應的user.token0Rewards 和user.token1Rewards 狀態。

  • 攻擊合約2 將PLP Token 轉回攻擊合約,後者調用機槍池的withdraw 函數Burn 掉PLP Token,取回流動性。

  • 攻擊合約2 和攻擊合約3 調用collectFees 函數,用虛假的tokenRewards 狀態取回了存款獎勵。

[虛擬印鈔機] Popsicle Finance 雙花攻擊分析

[虛擬印鈔機] Popsicle Finance 雙花攻擊分析

利潤分析

本次攻擊一共獲利:2.56k WETH,96.2 WBTC,160k DAI,5.39m USDC,4.98m USDT,10.5k UNI,獲利共計超過20,000,000 美元。

在此次攻擊之後攻擊者通過首先通過Uniswap 和WETH 將攻擊獲得的其他token 全部換成ETH,然後通過多次使用Tornado.Cash 將ETH 洗白。

BlockSec 團隊以核心安全技術驅動,長期關注DeFi 安全、數字貨幣反洗錢和基於隱私計算的數字資產存管,為DApp 項目方提供合約安全和數字資產安全服務。團隊發表20 多篇頂級安全學術論文(CCS, USENIX Security, S&P),合夥人獲得AMiner 全球最具影響力的安全和隱私學者稱號(2011-2020 排名全球第六). 研究成果獲得中央電視台、新華社和海外媒體的報導。獨立發現數十個DeFi 安全漏洞和威脅,獲得2019 年美國美國國立衛生研究院隱私計算比賽(SGX 賽道) 全球第一名。團隊以技術驅動,秉持開放共贏理念,與社區夥伴攜手共建安全DeFi 生態。

掃描二維碼,關注更多精彩

https://www.blocksecteam.com/

contact@blocksecteam.com

BlockSec
作者文库