一文了解Opyn 合約被黑詳細分析 - 冷萃財經

一文了解Opyn 合約被黑詳細分析

一文了解Opyn 合約被黑詳細分析
文章摘要:2020 年 8 月 5 日,Opyn 合約遭遇黑客攻擊。慢霧安全團隊在收到情報後對本次攻擊事件進行了全面的分析,下面為大家就這次攻擊事件展開具體的技術分析。

背景

2020 年 8 月 5 日,Opyn 合約遭遇黑客攻擊。慢霧安全團隊在收到情報後對本次攻擊事件進行了全面的分析,下面為大家就這次攻擊事件展開具體的技術分析。

攻擊細節

邏輯分析

看其中一筆攻擊交易:

https://etherscan.io/tx/0xa858463f30a08c6f3410ed456e59277fbe62ff14225754d2bb0b4f6a75fdc8ad

一文了解Opyn 合約被黑詳細分析

通過查看內聯交易可以看到攻擊者僅使用 272ETH 最終得到 467ETH

使用 OKO 合約瀏覽器對具體的攻擊細節進行分析

https://oko.palkeo.com/0xa858463f30a08c6f3410ed456e59277fbe62ff14225754d2bb0b4f6a75fdc8ad/

一文了解Opyn 合約被黑詳細分析

關鍵點在於 oToken 合約的 exercise 函數,從上圖中可以看出在 exercise 函數中通過調用兩次 transfer 將 USDC 發送給攻擊者合約,接下來我們切入 exercise 函數進行具體的分析

function exercise(        uint256 oTokensToExercise,        address payable[] memory vaultsToExerciseFrom) public payable {        for (uint256 i = 0; i < vaultsToExerciseFrom.length; i++) {            address payable vaultOwner = vaultsToExerciseFrom[i];            require(                hasVault(vaultOwner),                "Cannot exercise from a vault that doesn't exist"            );            Vault storage vault = vaults[vaultOwner];            if (oTokensToExercise == 0) {                return;            } else if (vault.oTokensIssued >= oTokensToExercise) {                _exercise(oTokensToExercise, vaultOwner);                return;            } else {                oTokensToExercise = oTokensToExercise.sub(vault.oTokensIssued);                _exercise(vault.oTokensIssued, vaultOwner);            }        }        require(            oTokensToExercise == 0,            "Specified vaults have insufficient collateral"        );    }

可以看到 exercise 函數允許傳入多個 vaultsToExerciseFrom,然後通過 for 循環調用_exercise 函數對各個 vaultsToExerciseFrom 進行處理,現在我們切入 _exercise 函數進行具體的分析

    function _exercise(        uint256 oTokensToExercise,        address payable vaultToExerciseFrom) internal {        // 1. before exercise window: revert        require(            isExerciseWindow(),            "Can't exercise outside of the exercise window"        );
    require(hasVault(vaultToExerciseFrom), "Vault does not exist");
    Vault storage vault = vaults[vaultToExerciseFrom]; require(oTokensToExercise > 0, "Can't exercise 0 oTokens"); // Check correct amount of oTokens passed in) require( oTokensToExercise <= vault.oTokensIssued, "Can't exercise more oTokens than the owner has" ); // Ensure person calling has enough oTokens require( balanceOf(msg.sender) >= oTokensToExercise, "Not enough oTokens" );
    // 1. Check sufficient underlying // 1.1 update underlying balances uint256 amtUnderlyingToPay = underlyingRequiredToExercise( oTokensToExercise ); vault.underlying = vault.underlying.add(amtUnderlyingToPay);
    // 2. Calculate Collateral to pay // 2.1 Payout enough collateral to get (strikePrice * oTokens) amount of collateral uint256 amtCollateralToPay = calculateCollateralToPay( oTokensToExercise, Number(1, 0) );
    // 2.2 Take a small fee on every exercise uint256 amtFee = calculateCollateralToPay( oTokensToExercise, transactionFee ); totalFee = totalFee.add(amtFee);
    uint256 totalCollateralToPay = amtCollateralToPay.add(amtFee); require( totalCollateralToPay <= vault.collateral, "Vault underwater, can't exercise" );
    // 3. Update collateral + oToken balances vault.collateral = vault.collateral.sub(totalCollateralToPay); vault.oTokensIssued = vault.oTokensIssued.sub(oTokensToExercise);
    // 4. Transfer in underlying, burn oTokens + pay out collateral // 4.1 Transfer in underlying if (isETH(underlying)) { require(msg.value == amtUnderlyingToPay, "Incorrect msg.value"); } else { require( underlying.transferFrom( msg.sender, address(this), amtUnderlyingToPay ), "Could not transfer in tokens" ); } // 4.2 burn oTokens _burn(msg.sender, oTokensToExercise);
    // 4.3 Pay out collateral transferCollateral(msg.sender, amtCollateralToPay);
    emit Exercise( amtUnderlyingToPay, amtCollateralToPay, msg.sender, vaultToExerciseFrom );
    }

    1、在代碼第 6 行首先檢查了現在是否在保險期限內,這自然是肯定的

    2、在代碼第 11 行則對 vaultToExerciseFrom 是否創建了 vault 進行檢查,注意這裡只是檢查了是否有創建 vault

    3、在代碼第 14、16、21 行對傳入的 oTokensToExercise 值進行了檢查,在上圖 OKO 瀏覽器中我們可以看到攻擊者傳入了 0x1443fd000,這顯然是可以通過檢查的

    4、接下來在代碼第 28 行計算需要消耗的 ETH 數量

    5、在代碼第 35、41 行計算需要支付的數量與手續費

    6、接下來在代碼第 59 行對 underlying 是否是 ETH 地址進行判斷,而 underlying 在上面代碼第 31 行進行了賦值,由於 isETH 為 true, 因此將會進入 if 邏輯而不會走 else 邏輯,在 if 邏輯中 amtUnderlyingToPay 與 msg.value 都是用戶可控的

    7、隨後對 oTokensToExercise 進行了燃燒,並調用 transferCollateral 函數將 USDC 轉給exercise 函數的調用者

    以上關鍵的地方在於步驟 2 與步驟 6,因此我們只需要確保傳入的 vaultToExerciseFrom 都創建了 vault,且使 amtUnderlyingToPay 與 msg.value 相等即可,而這些相關參數都是我們可以控制的,所以攻擊思路就顯而易見了。

    思路驗證

    讓我們通過攻擊者的操作來驗證此過程是否如我們所想:

    1、首先在保險期限內是肯定的

    一文了解Opyn 合約被黑詳細分析

    2、攻擊者傳入的 vaultToExerciseFrom 分別為:

    0xe7870231992ab4b1a01814fa0a599115fe94203f0x076c95c6cd2eb823acc6347fdf5b3dd9b83511e4

    一文了解Opyn 合約被黑詳細分析

    經驗證,這兩個地址都創建了 vault

    3、攻擊者調用 exercise 傳入 oTokensToExercise 為 0x1443fd000 (5440000000),msg.value 為 272ETH,vaultsToExerciseFrom 分別為以上兩個地址

    一文了解Opyn 合約被黑詳細分析

    4、此時由於此前攻擊者創建的 oToken 為 0xa21fe800 (2720000000),及 vault.oTokensIssued 為 2720000000 小於 5440000000,所以將走 exercise 函數中的 else 邏輯,此時 oTokensToExercise 為 0xa21fe800 (2720000000),則以上代碼第 60 行 msg.value == amtUnderlyingToPay 是肯定成立的

    一文了解Opyn 合約被黑詳細分析

    5、由於 vaultsToExerciseFrom 傳入兩個地址,所以 for 循環將執行兩次 _exercise 函數,因此將 transfer 兩次把 USDC 轉給攻擊者合約

    一文了解Opyn 合約被黑詳細分析

    完整的攻擊流程如下

    1、攻擊者使用合約先調用 Opyn 合約的 createERC20CollateralOption 函數創建 oToken

    2、攻擊合約調用 exercise 函數,傳入已創建 vault 的地址

    3、通過 exercise 函數中 for 循環邏輯執行調用兩次 _exercise 函數

    4、exercise 函數調用 transferCollateral 函數將 USDC 轉給函數調用者(由於 for 循環調用兩次 _exercise 函數,transferCollateral 函數也將執行兩次)

    5、攻擊合約調用 removeUnderlying 函數將此前傳入的 ETH 轉出

    6、最終攻擊者拿回了此前投入的 ETH 以及額外的 USDC

    攻擊合約地址

    0xe7870231992Ab4b1A01814FA0A599115FE94203f

    Opyn 合約地址

    0x951D51bAeFb72319d9FBE941E1615938d89ABfe2

    攻擊交易(其一)

    0xa858463f30a08c6f3410ed456e59277fbe62ff14225754d2bb0b4f6a75fdc8ad

    修復建議

    此次攻擊主要是利用了 _exercise 函數中對 vaultToExerciseFrom 是否創建 vault 的檢查缺陷。此檢查未校驗 vaultToExerciseFrom 是否是調用者自己,而只是簡單的檢查是否創建了 vault,導致攻擊者可以任意傳入已創建 vault 的地址來通過檢查。

    建議如下:

    1、在處理用戶可控的參數時應做好許可權判斷,限制 vaultToExerciseFrom 需為調用者本人。

    2、項目方可以在項目初期或未完成多次嚴謹安全審計之前添加合約暫停功能與可升級模型,避免在發生黑天鵝事件時無法有效的保證剩餘資金安全。

    冷萃財經原創,作者:Awing,轉載請註明出處:https://www.lccjd.top/2020/08/05/%e4%b8%80%e6%96%87%e4%ba%86%e8%a7%a3opyn-%e5%90%88%e7%ba%a6%e8%a2%ab%e9%bb%91%e8%af%a6%e7%bb%86%e5%88%86%e6%9e%90/?variant=zh-tw

    0

    掃一掃,分享到微信

    猜你喜歡

    文章評論

    電子郵件地址不會被公開。 必填項已用*標註

    後發表評論

      上一篇

      議程曝光!40位重量級嘉賓,IPFS官方連線,這屆POW'ER 2020 IPFS峰會看點全在這裡了

      下一篇

      今日推薦 | Uniswap大遷徙:從交易到「新資產發行」平台

      微信公眾號

      微信公眾號