Menu

  • Home
  • Trending
  • Recommended
  • Latest

分類

  • #2021 新年特輯
  • 100天區塊鏈挑戰
  • 2019 台灣區塊鏈產業指南
  • 2020 台灣區塊鏈產業年鑑
  • 2022 台灣年度最影響力人物榜
  • 2023 台灣年度影響力人物榜
  • 2023 新春特輯
  • 2024 TON Hacker House
  • 2024新春特輯
  • ABS 2018 專題報導
  • AI
  • CBDC是什麼?為何全球都在積極研究、有什麼優點與隱憂
  • Cefi
  • dao
  • dApps
  • defi
  • DePIN 如何開啟去中心化物理網路革命?
  • Entertainment
  • EOS
  • EOS insights
  • Gaming
  • Howto & Style
  • ICO
  • JiaJia
  • Layer 2
  • Libra
  • Movie
  • Music
  • News
  • nft
  • Plurality 多元宇宙
  • Starknet 空投落地,後續帶來什麼影響?
  • TON的崛起之路》背靠 Telegram 如何實現 Web3 大規模應用?
  • Uncategorized
  • Web3.0
  • 中國
  • 亞太
  • 交易所
  • 人物專訪
  • 以太坊
  • 以太坊
  • 以太坊 Dencun 坎昆升級將臨,你需要知道的所有事
  • 以太坊再質押協議為何成為最熱門賽道?
  • 供應鏈
  • 保險
  • 債券
  • 元宇宙
  • 全台最大詐騙案!Ace交易所涉垃圾幣詐騙
  • 全球加密貨幣監管最新動態統整
  • 其他國家
  • 其他幣別
  • 分散式帳本技術
  • 創投
  • 加密貨幣「詐騙手法」整理,學習如何保護你的資產
  • 加密貨幣市場
  • 區塊鏈平台
  • 區塊鏈新手全攻略,你需要知道的加密貨幣基礎
  • 區塊鏈新手教學
  • 區塊鏈活動
  • 區鍵禧
  • 即時新聞
  • 台灣
  • 哈希派
  • 國際組織報告
  • 多元宇宙Plurality有多重要?未來20年人類躍進關鍵
  • 央行
  • 娛樂平台
  • 安全
  • 專欄作者
  • 巴拉區塊事
  • 市場分析
  • 幣安與美國司法部達成 43 億美元和解,CZ認罪
  • 影片測試的分類
  • 快訊
  • 技術
  • 抓住空投爆擊!值得埋伏的項目、互動教學總整理
  • 投資分析
  • 挖礦
  • 推薦閱讀
  • 搶先看
  • 支付
  • 政府報告
  • 數位產權
  • 數據報告
  • 概念
  • 歐洲
  • 比特幣
  • 比特幣現貨ETF上市,真帶來了增量資金?
  • 比特幣第四次減半將臨,行情怎麼走?
  • 比特幣銘文大爆發,Oridinal 如何改變 BTC 生態?
  • 比特彭
  • 汪彪
  • 法規
  • 測試網
  • 灰度
  • 物聯網
  • 犯罪
  • 獨立觀點
  • 瑞波
  • 環境永續
  • 社交
  • 私人機構報告
  • 稅務
  • 穩定幣
  • 管制
  • 美國
  • 肺炎
  • 能源
  • 訴訟
  • 評級報告
  • 財金哥 & 區塊妹
  • 身份驗證
  • 遊戲
  • 鄧庶杭
  • 金融市場
  • 銀行
  • 錢包

Subscriptions

  • 零壹財經 01 binary
    01 Binary
  • 0xdt
  • 0xJigglypuff
  • aaaaYYYY
  • ABCDELabs

Recent News

  • 圖解多元宇宙》V神、Glen力推的Plurality是什麼?為何協作技術是人類社會進步關鍵
  • 精選文章搶先看!動區登入Access質押訂閱服務,解鎖寶貴資訊快人一步
  • ABS獨家專訪》Gitcoin共同創辦人Scott:台灣是現實與Web3治理的重要交匯點
動區動趨-最具影響力的區塊鏈新聞媒體
  • Home
    • Home Layout 1
    • Home Layout 2
    • Home Layout 3
  • Browse
    • News
    • Movie
    • Music
    • Technology
    • Howto & Style
    • Entertainment
    • Gaming
  • Features
    • Youtube Video
    • Vimeo Video
    • Dailymotion Video
    • Self-hosted Video
    • User Profile
    • Playlists
    • User-created Playlist
    • Favorite Playlist (Private)
    • Watch Later Playlist (Private)
    • All JNews Features
No Result
View All Result
  • Login
  • Register
UPLOAD
動區動趨-最具影響力的區塊鏈新聞媒體
No Result
View All Result
Home defi

DeFi安全漏洞|解析:DeFi Saver用戶的「31萬枚DAI」是如何被盜的?

慢霧科技 Slow Mist by 慢霧科技 Slow Mist
2020-10-13
in defi
473 20
0
DeFi安全漏洞|解析:DeFi Saver用戶的「31萬枚DAI」是如何被盜的?
677
SHARES
3.1k
VIEWS
Share on FacebookShare on Twitter

2020 年 10 月 8 號,去中心化錢包 imToken 發布推文表示,用戶報告稱 31 萬枚 DAI 被盜,這與 DeFi Saver Exchange 衝突有關。DeFi Saver 對此回應稱,被盜資金仍舊安全,正在接觸受害用戶。截至目前,資金已全部歸還受害用戶。筆者在收到情報後,針對這次發布 31 萬枚 DAI 被盜事件展開具體的分析。本文由專欄作者 慢霧科技 撰稿,不代表動區立場。
(相關報導:資安月報|DeFi 詐騙、跑路頻發!9 月安全事件共 33 起,危害程度評級 HIGH)

本文目錄

  • RelatedPosts
  • Uniswap回擊SEC證券指控:UNI 代幣是「文件格式」而非投資契約
  • 回顧dYdX如何逐步走上永續合約DEX龍頭:離開以太坊到發dYdX Chain,證明Rollup並非唯一解
  • MakerDAO新穩定幣PureDai是什麼?不保證與美元掛鉤、引入浮動價格、擁有新治理代幣..
  • 背景
  • 攻擊過程分析
  • 分析思路驗證
  • 完整的攻擊流程如下
  • 最後思考
    • Defi是洗錢天堂?加密犯罪若出圈到 DeFi,監管風險不容小覷
    • 為什麼投資者不用擔心,KuCoin 遭駭近 2 億美元會讓以太坊崩跌?
    • Bitfinex駭客比特幣再轉移 7.5 億!4 年來累積轉出近 400 億贓款,恐成拋售危機

 

RelatedPosts

Uniswap回擊SEC證券指控:UNI 代幣是「文件格式」而非投資契約

回顧dYdX如何逐步走上永續合約DEX龍頭:離開以太坊到發dYdX Chain,證明Rollup並非唯一解

MakerDAO新穩定幣PureDai是什麼?不保證與美元掛鉤、引入浮動價格、擁有新治理代幣..

背景

2020 年 10 月8號,去中心化錢包 imToken 發布推文表示,用戶報告稱 31 萬枚 DAI 被盜,這與 DeFi Saver Exchange 衝突有關。

DeFi Saver 對此回應稱,被盜資金仍舊安全,正在接觸受害用戶。

⚠️ We received a user report of 310,000 DAI got drained which related to DeFi Saver Exchange vulnerability. Someone is front running the white hack. We suggest @DefiSaver suicide the contract to save people's money, immediately!https://t.co/OR9gA3hngi https://t.co/uJuLvJbSih

— imToken Wallet – Bitcoin, Ethereum & more (@imTokenOfficial) October 8, 2020

截至目前,資金已全部歸還受害用戶。

早在今年 6 月份 DEFI Saver 就表示該團隊發現 DEFI Save 應用系列中自有交易平台的一個漏洞,此次 31 萬枚 DAI 被盜也與此前的 SaverExchange 合約漏洞有關。筆者在收到情報後,針對這次發布 31 萬枚 DAI 被盜事件展開具體的分析。

攻擊過程分析

查看這筆攻擊交易:

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

其中可以看到被盜用戶 0xc0 直接轉出 31 萬枚 DAI 到攻擊合約 0x5b。

我們可以使用 OKO 瀏覽器查看具體的交易細節:

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

(以下不需閱讀代碼分析的讀者,可直接跳到總結整理的部分。)

從中可以插入攻擊者通過調用 swapTokenToToken 函數插入_exchangeAddress,_src,_dest 為 DAI 合約地址,選擇_exchangeType 為 4,並自定的 _callData。

進行具體的分析:(以下為詳細程式碼)

function swapTokenToToken(address _src, address _dest, uint _amount, uint _minPrice, uint _exchangeType, address _exchangeAddress, bytes memory _callData, uint _0xPrice) public payable {
    // use this to avoid stack too deep error
    address[3] memory orderAddresses = [_exchangeAddress, _src, _dest];

    if (orderAddresses[1] == KYBER_ETH_ADDRESS) {
        require(msg.value >= _amount, "msg.value smaller than amount");
    } else {
        require(ERC20(orderAddresses[1]).transferFrom(msg.sender, address(this), _amount), "Not able to withdraw wanted amount");
    }

    uint fee = takeFee(_amount, orderAddresses[1]);
    _amount = sub(_amount, fee);
    // [tokensReturned, tokensLeft]
    uint[2] memory tokens;
    address wrapper;
    uint price;
    bool success;

    // at the beggining tokensLeft equals _amount
    tokens[1] = _amount;

    if (_exchangeType == 4) {
        if (orderAddresses[1] != KYBER_ETH_ADDRESS) {
            ERC20(orderAddresses[1]).approve(address(ERC20_PROXY_0X), _amount);
        }

        (success, tokens[0], ) = takeOrder(orderAddresses, _callData, address(this).balance, _amount);
        // either it reverts or order doesn't exist anymore, we reverts as it was explicitely asked for this exchange
        require(success && tokens[0] > 0, "0x transaction failed");
        wrapper = address(_exchangeAddress);
    }

    if (tokens[0] == 0) {
        (wrapper, price) = getBestPrice(_amount, orderAddresses[1], orderAddresses[2], _exchangeType);

        require(price > _minPrice || _0xPrice > _minPrice, "Slippage hit");

        // handle 0x exchange, if equal price, try 0x to use less gas
        if (_0xPrice >= price) {
            if (orderAddresses[1] != KYBER_ETH_ADDRESS) {
                ERC20(orderAddresses[1]).approve(address(ERC20_PROXY_0X), _amount);
            }
            (success, tokens[0], tokens[1]) = takeOrder(orderAddresses, _callData, address(this).balance, _amount);
            // either it reverts or order doesn't exist anymore
            if (success && tokens[0] > 0) {
                wrapper = address(_exchangeAddress);
                emit Swap(orderAddresses[1], orderAddresses[2], _amount, tokens[0], wrapper);
            }
        }

        if (tokens[1] > 0) {
            // in case 0x swapped just some amount of tokens and returned everything else
            if (tokens[1] != _amount) {
                (wrapper, price) = getBestPrice(tokens[1], orderAddresses[1], orderAddresses[2], _exchangeType);
            }

            // in case 0x failed, price on other exchanges still needs to be higher than minPrice
            require(price > _minPrice, "Slippage hit onchain price");
            if (orderAddresses[1] == KYBER_ETH_ADDRESS) {
                (tokens[0],) = ExchangeInterface(wrapper).swapEtherToToken.value(tokens[1])(tokens[1], orderAddresses[2], uint(-1));
            } else {
                ERC20(orderAddresses[1]).transfer(wrapper, tokens[1]);

                if (orderAddresses[2] == KYBER_ETH_ADDRESS) {
                    tokens[0] = ExchangeInterface(wrapper).swapTokenToEther(orderAddresses[1], tokens[1], uint(-1));
                } else {
                    tokens[0] = ExchangeInterface(wrapper).swapTokenToToken(orderAddresses[1], orderAddresses[2], tokens[1]);
                }
            }

            emit Swap(orderAddresses[1], orderAddresses[2], _amount, tokens[0], wrapper);
        }
    }

    // return whatever is left in contract
    if (address(this).balance > 0) {
        msg.sender.transfer(address(this).balance);
    }

    // return if there is any tokens left
    if (orderAddresses[2] != KYBER_ETH_ADDRESS) {
        if (ERC20(orderAddresses[2]).balanceOf(address(this)) > 0) {
            ERC20(orderAddresses[2]).transfer(msg.sender, ERC20(orderAddresses[2]).balanceOf(address(this)));
        }
    }

    if (orderAddresses[1] != KYBER_ETH_ADDRESS) {
        if (ERC20(orderAddresses[1]).balanceOf(address(this)) > 0) {
            ERC20(orderAddresses[1]).transfer(msg.sender, ERC20(orderAddresses[1]).balanceOf(address(this)));
        }
    }
}

1. 在代碼第 5 行可以看到先對 orderAddresses [1] 是否為  KYBER_ETH_ADDRESS 地址已確定,由 orderAddresses [1] 為 DAI 合約地址,因此將直接調用從函數將數量為 _amount 的 DAI 轉入本協議。

2. 接下來在代碼第 11,12 行,通過 takeFee 函數計算費用,最終計算結果都為 0 時,不做這裡展開。

3. 由於攻擊者攻擊的 _exchangeType 為 4,因此將走代碼第 22 行 if(_exchangeType == 4)的邏輯。在代碼中我們可以裁剪在此邏輯中調用了 order 函數,並進行了攻擊者自定的 _callData,注意這將是本次攻擊的關鍵點,隨後切入 takeOrder 函數:

function takeOrder(address[3] memory _addresses, bytes memory _data, uint _value, uint _amount) private returns(bool, uint, uint) {
        bool success;

        (success, ) = _addresses[0].call.value(_value)(_data);

        uint tokensLeft = _amount;
        uint tokensReturned = 0;
        if (success){
            // check how many tokens left from _src
            if (_addresses[1] == KYBER_ETH_ADDRESS) {
                tokensLeft = address(this).balance;
            } else {
                tokensLeft = ERC20(_addresses[1]).balanceOf(address(this));
            }

            // check how many tokens are returned
            if (_addresses[2] == KYBER_ETH_ADDRESS) {
                TokenInterface(WETH_ADDRESS).withdraw(TokenInterface(WETH_ADDRESS).balanceOf(address(this)));
                tokensReturned = address(this).balance;
            } else {
                tokensReturned = ERC20(_addresses[2]).balanceOf(address(this));
            }
        }

        return (success, tokensReturned, tokensLeft);
    }

4. 在 takeOrder 函數中的第4行,我們可以直觀的修剪此邏輯可對目標 _addresses [0] 的函數進行調用,此時 _addresses [0] 為_exchangeAddress 即 DAI 合約地址,而具體的調用即攻擊者自定值的 _callData,因此如果持有 DAI 用戶在 DAI 合約中對 SaverExchange 合約進行過授權,則可以通過調用的 _callData 調用DAI 合約的 transfer 從函數將用戶的 DAI 直接轉出,具體都可以在_callData 中進行構造。

5. 接下來由於返回的令牌 [0] 為 1,所以將走 swapTokenToToken 函數代碼塊中第 76 行以下的邏輯,如果判斷的邏輯,可以看到都是使用,毫無疑問可以走通。

分析思路驗證

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

1. 通過鏈上記錄可以看到,被盜的用戶歷史上有對 SaverExchange合約進行 DAI 的授權,交易哈希如下:

0xdcf73848022ec1f730d9fdb90f4e8563f0dff48d9191aab19fc51241708eacf0

2. 通過鏈上數據可以發現預期的 _callData 為:

23b872dd //SlowMist// transferFrom 函数签名
000000000000000000000000c001cd7a
370524209626e28eca6abe6cfc09b0e5
0000000000000000000000005bb456cd
09d85156e182d2c7797eb49a43840187
00000000000000000000000000000000
00000000000041a522386d9b95c00000 //SlowMist// 310000e18

3. 通過鏈上調用過程可研磨攻擊者直接調用 DAI 合約的 transferFrom 函數將被盜用戶的 31 萬枚 DAI 轉走:

完整的攻擊流程如下

1. 攻擊者調用 swapTokenToToken 函數為 _exchangeAddress 為 DAI 合約地址,選擇 _exchangeType 為 4,將攻擊有效載荷置於_callData 中。

2. 此時將走 _exchangeType == 4 的邏輯,這將調用 takeOrder 函數並寫入 _callData。

3. takeOrder 函數將對預期的 _callData 進行具體調用,因此如果持有 DAI 用戶在 DAI 合約中對 SaverExchange 合約進行過授權,則可以通過的 _callData 調用 DAI 合約的 transfer 來自函數將用戶的 DAI直接轉出,具體都可以在 _callData 中進行構造。

4. 通過構造的 _callData 與相對用戶對 SaverExchange 合約進行過 DAI 的授權,SaverExchange 合約可以通過調用 DAI 合約的 transfer從函數將用戶帳戶中的 DAI 直接轉出至攻擊者指定的地址。

最後思考

此突破的關鍵在於攻擊者可以通過 takeOrder 函數對目標合約_addresses [0] 的任意函數進行任意調用,而引入 takeOrder 函數的參數都是用戶可控的,並且未對參數進行任何檢查或限制。

因此,為避免出現此類問題,建議項目方使用白名單策略對用戶調用的 _callData 等參數進行檢查,或者結合項目方具體的業務場景尋找更好的調用方式,而不是不做任何限制的進行隨意調用。

此漏洞不僅只影響到通過 DAI 合約對 SaverExchange 合約授權過的用戶,如果用戶歷史對 SaverExchange 合約有進行過其他令牌的授權,則都會存在帳戶的令牌被任意轉出風險。

建議此前有對 SaverExchange 合約進行過授權的用戶盡快取消授權(推薦使用https://approve.sh/網站自查授權情況),避免帳戶資產被惡意轉出。

📍相關報導📍

Defi是洗錢天堂?加密犯罪若出圈到 DeFi,監管風險不容小覷

為什麼投資者不用擔心,KuCoin 遭駭近 2 億美元會讓以太坊崩跌?

Bitfinex駭客比特幣再轉移 7.5 億!4 年來累積轉出近 400 億贓款,恐成拋售危機


讓動區 Telegram 新聞頻道再次強大!!立即加入獲得第一手區塊鏈、加密貨幣新聞報導。

LINE 與 Messenger 不定期為大家服務

加入好友

加入好友

Tags: DaiimToken竊盜

Recommended videos

3:18

‘Stranger Things 3: The Game’ arrives on iOS and Android

2.5k Views
2024-06-22
    3:36

    Dua Lipa – Lost In Your Light feat. Miguel (Official Video)

    2.5k Views
    2024-06-23
      1:07

      Switch fishing using Joy-Con as futuristic fishing rod

      2.5k Views
      2024-07-16
        2:01

        Gundala Director Joko Anwar Lets ‘Imaginations Go Wild’

        2.5k Views
        2024-06-24
          Show More
          Copyright (c) 2019 by Jegtheme.
          • About
          • Buy JNews
          • Request A Demo
          • Contact
          No Result
          View All Result
          • Account
          • BlockTempo Beginner – 動區新手村
          • Change Password
          • Forgot Password?
          • Home 1
          • Home 2
          • Home 3
          • Jin-homepage
          • Latest
          • Login
          • Profile
          • Register
          • Reset Password
          • Trending
          • Users
          • Users List Item
          • 不只加密貨幣,談談那些你不知道的區塊鏈應用|動區新手村
          • 所有文章
          • 關於 BlockTempo

          © 2025 JNews - Premium WordPress news & magazine theme by Jegtheme.

          Welcome Back!

          Login to your account below

          Forgotten Password? Sign Up

          Create New Account!

          Fill the forms below to register

          All fields are required. Log In

          Retrieve your password

          Please enter your username or email address to reset your password.

          Log In

          Add New Playlist