背景
8 月22 號,Balancer 官方發佈公告表示收到影響多個V2 Boost 池的嚴重漏洞報告,只有1.4% 的TVL 受影響,多個池子已暫停,並通知用戶盡快提取流動性LP。[1] [2]
8 月27 號,慢霧MistEye 系統發現疑似Balancer 漏洞被利用的攻擊交易。[3]
由於池子無法暫停,部分資金仍受到攻擊的影響,Balancer 官方再次提醒用戶將受影響池子中的LP 取回。[4] 隨後,Balancer 官方於Medium 發布了8 月披露的漏洞細節 [5],慢霧安全團隊進行複盤,詳情如下:
引入
Balancer 官方在其揭露中簡單指出此次的問題在於,線性池的向下舍入以及可組合池的虛擬供應量導致bptSupply 為0。首先讓我們來簡單了解一下與這次漏洞相關的Balancer 協議中的內容。
Balancer V2 Vault
Balancer V2 [6] 協議是一種基於以太坊的去中心化自動做市商(AMM)協議,它代表了可編程流動性的靈活構建塊。其核心組件是Vault 合約,該合約維護所有池子的記錄,並管理代幣的記帳和轉移,甚至包括原生ETH 的包裝和解包。也就是說,Vault 的實作是將代幣記帳和管理與池子邏輯分開。
Vault 中有四個接口,分別是joinPool, exitPool, swap 和batchSwap(加入、退出和交換是分開的調用,不存在單次調用時的組合)。其中一個突出的特點是batchSwap,它能實現多個池子之間多次原子交換,將一個池子交換的輸出與另一個池子的輸入連接起來(GiveIn 和GiveOut)。該系統還引入了閃電交換 [7],類似於一個內部的閃電貸。
Linear Pools 線性池
Balancer 為了提高LP 的資本效率及warp 和unwarp 高額開銷的問題,在V2 中推出線性池作為解決方案,由此引入了BPT (ERC20 Balancer Pool Token) 代幣。
線性池 [8] 包含main token(底層資產),warpped token(包裝代幣)和BPT 代幣,透過已知匯率交換資產及其包裝的、具有收益的對應物。包裝代幣的比例越高,收益率和資金池的資本效率就越高。在warp 的過程中,通常都會透過縮放因子來確保不同代幣以相同的精度進行計算。
Composable Pools 可組合池
所有的Balancer 池都是可組合池,池子包含其他代幣,池子本身也有自己的代幣。其中BPT 幣指的是ERC20 平衡池代幣,是所有池的基礎。用戶可以在其他池內使用BPT 代幣自由組合進行兌換。兌換總是涉及一個池和兩個代幣:GiveIn 和GiveOut。 In 代表送入成分代幣並接收BPT,而Out 意味著送入BPT 並接收成分代幣。如果BPT 本身就是成分代幣,它就可以像其他代幣一樣交換。這樣的實作構成了外部池中的基礎資產和代幣之間的一個簡單batchSwap 路徑,用戶可以用BPT 交換到線性池的底層資產,這也是Balancer Boosted Pool [9] 的基礎。
透過以上的組合,Balancer 的可組合池就形成了。一個bb-a-USD 可組合穩定池由三個線性池組成,同時向外部協定(Aave)發送閒置流動性。例如,bb-a-DAI 是一個包含DAI 和waDAI(包裝的aDAI)的線性池。當使用者需要進行batchSwap 時(如要將USDT 換成DAI),交換路徑舉例如下:
1. 在USDT 線性池中,將USDT 兌換bb-a-USDT(進入USDT 線性池);
2. 在bb-a-USD 中,bb-a-USDT 兌換bb-a-DAI(線性BPT 之間的交換);
3. 在DAI 線性池中,bb-a-DAI 兌換為DAI(退出DAI 線性池)。
簡單了解以上前置知識後,我們進入漏洞分析環節。
分析
在8 月27 號時,慢霧安全團隊收到MistEye 系統識別,一筆疑似Balancer 漏洞的在野利用發生。交易 [3] 如下:
攻擊者先向AAVE 透過閃電貸借出300,000 枚USDC。接著調用Vault 的batchSwap 操作,透過可組合穩定池bb-a-USD 池進行BPT 代幣的兌換計算,最終將94,508 枚USDC 兌換為59,964 枚bb-a-USDC,68,201 枚bb-a-DAI 和74,280枚bb-a-USDT。最後將獲得的BPT 代幣透過Vault 合約的exitPool 退出池子換取底層資產,償還閃電貸,並獲利約108,843.7 美元離場。
由此可見,這次攻擊的關鍵在batchSwap 裡,而batchSwap 中具體發生了什麼事?我們深入了解一下。
攻擊者在整個batchSwap 過程中,先在bb-a-USDC 池中兌換出USDC ,接著進行BPT 代幣間的兌換,將bb-a-USDC 兌換為bb-a-DAI,bb-a-USDT 和USDC。最後再將底層的main 代幣USDC 兌換為bb-a-USDT。也就是說,bb-a-USDC 作為關鍵的BPT 代幣充當GiveOut 和GiveIn 的成分代幣。
攻擊者在第一步驟以固定的縮放因子在bb-a-USDC 線性池中以BPT 代幣兌換出USDC main代幣,其增加的數量記錄在池子中的bptBalance 中。但在第二次onSwap 的兌換後,我們發現,同樣的兌換過程,兌換出USDC 的amountOut 值卻是0。這是為什麼呢?
深入onSwap 函數,我們發現在這個過程中會先做一次精度處理nominal 化併計算出對應代幣的縮放因子。而接下來呼叫_downscaleDown 函數時,amountOut 存在向下捨去的情況。如果amountOut 和scalingFactors[indexOut] 之間的值相差很大,計算出的_downscaleDown 值就為零。
也就是說當我們使用BPT 代幣來兌換main 代幣時,如果amountOut 過小,則回傳值會向下捨去為零,而這個值就是小於由scalingFactors 所計算的1e12。但amountIn 進來的bb-a-USDC 數量仍然會加入到bptBalance 虛擬數量當中,而此操作會增加bb-a-USDC 池子中的餘額,可以將其看作為單邊添加bb-a-USDC 流動性。
接著利用可組合穩定池的特性,透過BPT 代幣之間的相互轉換,首先將bb-a-USDC 兌換為其他BPT 代幣。跟進這個兌換過程,可組合穩定池的以下呼叫路徑bb-a-DAI onSwap -> _swapGivenIn -> _onSwapGivenIn 先將bb-a-USDC 依序換成bb-a-DAI 和bb-a-USDT。與在線性池中不同的是,可組合穩定池在進行onSwap 操作之前需要進行匯率的快取更新。從程式碼中我們可以看到,在組合池中,onSwap 會先判斷是否需要更新快取的token 兌換率。
經過先前的兌換,bb-a-USDC 的數量發生了改變,並透過_toNominal 名義化後的真實總量為totalBalance 994,010,000,000,虛擬供應的BPT 代幣為20,000,000,000 。可以計算出,更新後的匯率幾乎是先前線性池原始快取兌換率1,100,443,876,587,504,549 的45 倍,即49,700,500,000,000,000,000。
隨後,在線性池中將bb-a-USDC 兌換為USDC。然而這次的兌換同第二次的兌換一樣,再一次造成amountOut 向下舍入為0 的情況,兌換路徑和之前相同。
而接下來的這次兌換則是反向將USDC 兌換成bb-a-USDC,兌換路徑為onSwap -> onSwapGivenIn -> _swapGivenMainIn。在這個過程中,我們發現,在計算需要兌換的amountOut 的時候,其中對於虛擬供應量的計算,是基於兌換後的BPT 代幣totalsupply 與池中剩餘量之間的差值,該差值為0 。
這是因為bptSupply 為0,在計算BPT Out 時直接透過呼叫_toNominal 函數,而此路徑的呼叫使得USDC 兌bb-a-USDC 的兌換比例接近1:1。
總結
batchSwap 透過多個池子之間多次原子交換,將一個池子交換的輸出與另一個池子的輸入連接(tokenIn 和tokenOut),將USDC 兌換為BPT 代幣。在這個batchSwap 中並不會發生實際代幣轉移,而是透過記錄轉入和轉出的數量來確認最後的兌換數量。又因為線性池是透過底層資產代幣進行兌換的,兌換方式是透過一個虛擬供應量且是固定的演算法計算出Rate 。因此,batchSwap 中存在兩個安全漏洞:
一是線性池的向下舍入問題,攻擊者透過捨去為池子單邊添加main 代幣提高緩存代幣的比率,從而操縱相應可組合池中的代幣兌換率;
二是由於可組合池的虛擬供應量特性,虛擬供應量透過BPT 代幣減去池子中的餘額來計算,在兌換的時候如果GiveIn 是BPT 代幣,那麼之後的供應量就會扣掉這部分,攻擊者只需要將BPT 作為GiveIn 來進行兌換,並將其供應量先操縱為0 ,之後進行反向swap,即BPT 再作為GiveOut 一方,這時候由於供應量是0,演算法會按照接近1: 1 的比例低於線性池的兌換比例來進行實際兌換,使得GiveOut 的BPT 代幣數量間接被操控。
我們可以發現,漏洞一為兌換增加了兌換率,而反向兌換時漏洞二再反向降低兌換率,攻擊者利用了雙重buff 獲利離場。
參考連結:
[1]https://twitter.com/Balancer/status/1694014645378724280
[2]https://forum.balancer.fi/t/vulnerability-found-in-some-pools/5102?u=endymionjkb
[3]https://etherscan.io/tx/0x7020e0ccafff2c86db3df5a2af0cccb4e931fe948f69bf20ea517b0cc99c1f15
[4]https://twitter.com/Balancer/status/1695777503699435751
[5]https://medium.com/balancer-protocol/rate-manipulation-in-balancer-boosted-pools-technical-postmortem-53db4b642492
[6]https://docs.balancer.fi/concepts/overview/basics.html
[7]https://docs.balancer.fi/reference/swaps/flash-swaps.html#flash-swaps
[8]https://docs.balancer.fi/concepts/pools/linear.html
[9]https://docs.balancer.fi/concepts/pools/boosted.html