├── README.md ├── b0g0_ctf ├── BuggyNFTVault.sol └── README.MD ├── deth_ctf ├── Factory&Vault.sol └── README.md └── nmirchev8_ctf ├── ExtraSafeExternalCall.sol └── README.md /README.md: -------------------------------------------------------------------------------- 1 | # CTF Challenge Guidelines 2 | 3 | ## Submission Guidelines 4 | - **Submitting a Report**: Use the Issues tab in this repository to submit your findings. 5 | 6 | - **Create a new issue for your submission.** 7 | The title of your issue should include the challenge folder name (e.g., "nmirchev8_ctf - Reentrancy Bug"). 8 | In the issue description, clearly explain the following: 9 | - **Submission Deadline:** Issues will be considered up to 16.08.2024 16:00 UTC. Make sure to submit your findings before the deadline to be eligible for the reward. 10 | - - **Description of the Bug:** What is the vulnerability in the contract? 11 | - - **Impact:** What are the potential consequences of this bug? How can it be exploited? 12 | - - **Solution (Optional):** Suggest how the contract could be fixed or improved to prevent the bug. 13 | First-Come, First-Served: The first correct submission for each challenge will be considered the winner. Ensure your report is detailed and accurate! 14 | 15 | ## Prizes: 16 | - For each challenge, the total pool is $200 USDC. 17 | - Rewards are distributed to the first person who finds it 18 | -------------------------------------------------------------------------------- /b0g0_ctf/BuggyNFTVault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | import "@openzeppelin/contracts/utils/Counters.sol"; 6 | 7 | contract BuggyNFTVault is ERC721 { 8 | using Counters for Counters.Counter; 9 | Counters.Counter private _tokenIds; 10 | uint256 depositRequired; 11 | 12 | uint256 public constant MIN_DEPOSIT = 0.1 ether; 13 | 14 | /// @notice Stores the amount of ETH deposited by each user. 15 | mapping(address => uint256) public deposits; 16 | 17 | /** 18 | * @dev Constructor that sets the required deposit amount to mint an NFT. 19 | */ 20 | constructor(uint256 _depositAmount) ERC721("CtfNFT", "CNFT") { 21 | require(_depositAmount >= MIN_DEPOSIT, "Min deposit"); 22 | depositRequired = _depositAmount; 23 | } 24 | 25 | /** 26 | * @notice Deposits ETH and mints an NFT in return. 27 | */ 28 | function deposit() external payable { 29 | require(msg.value == depositRequired, "Incorrect ETH amount"); 30 | 31 | _tokenIds.increment(); 32 | uint256 newTokenId = _tokenIds.current(); 33 | 34 | deposits[msg.sender] += msg.value; 35 | 36 | _mint(msg.sender, newTokenId); 37 | } 38 | 39 | /** 40 | * @notice Withdraws ETH by burning the NFT. 41 | * @param tokenId The ID of the NFT to burn. 42 | */ 43 | function withdraw(uint256 tokenId) external { 44 | require(ownerOf(tokenId) == msg.sender, "Only the owner can withdraw"); 45 | 46 | // Burn the NFT to complete the withdrawal process. 47 | _burn(tokenId); 48 | deposits[msg.sender] -= depositRequired; 49 | 50 | (bool success, ) = msg.sender.call{value: depositRequired}(" "); 51 | require(success, "Transfer failed"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /b0g0_ctf/README.MD: -------------------------------------------------------------------------------- 1 | ## Info 2 | A simple contract that accepts ETH deposits and mints ERC721 NFTs in return. Deposit can be redeemed by burning the NFT 3 | 4 | ## Assumptions 5 | Assume depositors are the ones responsible for taking care that they can handle NFT's. 6 | 7 | # Results 8 | 9 | ## NOTE 10 | 11 | Due to a technical mistake on my side, the CTF was not published in its final version for the first 15 min of this challenge. Since this introduced additional vulnerabilities that were reported before it was fixed, I decided it's only fair to also reward the first submitters of those issues and pay respect to the efforts of all participants. The pot for this particular CTF will also be x3-ed to 600$ as another incentive to everyone that managed to uncover a substantial bug in this challenge. 12 | 13 | Below are the auditors who managed to be the first to submit a valid non-informational bug, regardless if it was in the [unfinilized](https://github.com/Egis-Security/CTF_Challenge/blob/e5fa79951e1b20c90d68d1cf071ccca81b51ea38/b0g0_ctf/BuggyNFTVault.sol) or the [finilized](https://github.com/Egis-Security/CTF_Challenge/blob/26b1cc25ac00177207ec25ab8ccc0a47ee8a26de/b0g0_ctf/BuggyNFTVault.sol) version of the challenge. 14 | 15 | 16 | 17 | | Name | Reward | Issues | 18 | |----------|----------|----------| 19 | | sammy-tm | $150 | #1(partial) , #22(partial) | 20 | | dimi6oni | $75 | #2(partial)| 21 | | highskore | $75 | #23(partial) | 22 | | Viktor-Andreev4 | $150 | #27 | 23 | | v-kirilov | $150 | #30 | 24 | 25 | * Partial reports were taken into consideration in an attempt to best distribute rewards among perticipant. 26 | -------------------------------------------------------------------------------- /deth_ctf/Factory&Vault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Factory { 5 | error AlreadyDeployed(); 6 | 7 | address public lastDeployed; 8 | 9 | function deployVault() external { 10 | address vaultAddress = computeAddress(); 11 | 12 | if (vaultAddress.codehash != bytes32(0)) { 13 | revert AlreadyDeployed(); 14 | } 15 | 16 | bytes32 salt = bytes32(uint256(uint160(msg.sender))); 17 | vaultAddress = address(new Vault{salt: salt}(msg.sender)); 18 | } 19 | 20 | function computeAddress() public view returns (address) { 21 | bytes32 salt = bytes32(uint256(uint160(msg.sender))); 22 | return address(uint160(uint256(keccak256(abi.encodePacked( 23 | bytes1(0xff), 24 | address(this), 25 | salt, 26 | keccak256(type(Vault).creationCode) 27 | ))))); 28 | } 29 | } 30 | 31 | contract Vault { 32 | bool public locked = true; 33 | address public owner; 34 | 35 | mapping(address => uint256) public balances; 36 | 37 | event Deposited(address user, uint256 amount); 38 | event Withdrawn(address user, uint256 amount); 39 | event Unlocked(address owner); 40 | 41 | modifier onlyOwner() { 42 | require(msg.sender == owner, "Not the owner"); 43 | _; 44 | } 45 | 46 | constructor(address _owner) { 47 | owner = _owner; 48 | } 49 | 50 | function unlock() external onlyOwner { 51 | locked = false; 52 | emit Unlocked(msg.sender); 53 | } 54 | 55 | function deposit() external payable { 56 | require(!locked, "Vault is locked"); 57 | balances[msg.sender] += msg.value; 58 | emit Deposited(msg.sender, msg.value); 59 | } 60 | 61 | function withdraw(uint256 amount) external { 62 | require(!locked, "Vault is locked"); 63 | require(balances[msg.sender] >= amount, "Insufficient balance"); 64 | 65 | balances[msg.sender] -= amount; 66 | (bool success, ) = payable(msg.sender).call{value: amount}(""); 67 | require(success); 68 | emit Withdrawn(msg.sender, amount); 69 | } 70 | } -------------------------------------------------------------------------------- /deth_ctf/README.md: -------------------------------------------------------------------------------- 1 | ## Info 2 | Factory that deploys Vaults which simply store native tokens inside. 3 | 4 | ## Results 5 | 6 | | Name | Reward | Issues | 7 | |----------|----------|----------| 8 | | Kaiziron | $100 | #6 | 9 | | amaron14 | #100 | #9 | 10 | | DevPelz | $50 | #4 | 11 | -------------------------------------------------------------------------------- /nmirchev8_ctf/ExtraSafeExternalCall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity >=0.8.2 <0.9.0; 3 | 4 | contract ExtraSafeExternalCall{ 5 | bytes4 internal constant NO_CONTRACT_SIG = 0x0c3b563c; 6 | bytes4 internal constant NO_GAS_FOR_CALL_EXACT_CHECK_SIG = 0xafa32a2c; 7 | bytes4 internal constant NOT_ENOUGH_GAS_FOR_CALL_SIG = 0x37c3be29; 8 | 9 | function _callWithExactGasSafeReturnData( 10 | bytes memory payload, 11 | address target, 12 | uint256 gasLimit 13 | ) internal returns (bool success, bytes memory retData, uint256 gasUsed) { 14 | // allocate retData memory ahead of time 15 | uint16 maxReturnDataBytes = 2 * 32; 16 | retData = new bytes(maxReturnDataBytes); 17 | uint16 gasForCallExactCheck = 5_000; 18 | assembly { 19 | // solidity calls check that a contract actually exists at the destination, so we do the same 20 | // Note we do this check prior to measuring gas so gasForCallExactCheck (our "cushion") 21 | // doesn't need to account for it. 22 | if iszero(extcodesize(target)) { 23 | mstore(0x0, NO_CONTRACT_SIG) 24 | revert(0x0, 0x4) 25 | } 26 | 27 | let g := gas() 28 | // Compute g -= gasForCallExactCheck and check for underflow 29 | // The gas actually passed to the callee is _min(gasAmount, 63//64*gas available). 30 | // We want to ensure that we revert if gasAmount > 63//64*gas available 31 | // as we do not want to provide them with less, however that check itself costs 32 | // gas. gasForCallExactCheck ensures we have at least enough gas to be able 33 | // to revert if gasAmount > 63//64*gas available. 34 | if lt(g, gasForCallExactCheck) { 35 | mstore(0x0, NO_GAS_FOR_CALL_EXACT_CHECK_SIG) 36 | revert(0x0, 0x4) 37 | } 38 | g := sub(g, gasForCallExactCheck) 39 | // if g - g//64 <= gasAmount, revert. We subtract g//64 because of EIP-150 40 | if iszero(gt(sub(g, div(g, 64)), gasLimit)) { 41 | mstore(0x0, NOT_ENOUGH_GAS_FOR_CALL_SIG) 42 | revert(0x0, 0x4) 43 | } 44 | 45 | // We save the gas before the call so we can calculate how much gas the call used 46 | let gasBeforeCall := gas() 47 | // call and return whether we succeeded. ignore return data 48 | // call(gas,addr,value,argsOffset,argsLength,retOffset,retLength) 49 | success := call(gasLimit, target, 0, add(payload, 0x20), mload(payload), 0x0, 0x0) 50 | gasUsed := sub(gasBeforeCall, gas()) 51 | 52 | // Store the length of the copied bytes 53 | mstore(retData, maxReturnDataBytes) 54 | // copy the bytes from retData[0:maxReturnDataBytes] 55 | returndatacopy(add(retData, 0x20), 0x0, maxReturnDataBytes) 56 | } 57 | return (success, retData, gasUsed); 58 | } 59 | 60 | 61 | /// @dev A function to call untrusted target contract and to be safe from return gas bombs and gas griefing attacks 62 | function callContractSafe(bytes memory payload, address target, uint256 gasLimit) external returns (bool success, bytes memory returnData) { 63 | (success, returnData, ) =_callWithExactGasSafeReturnData(payload, target, gasLimit); 64 | } 65 | } -------------------------------------------------------------------------------- /nmirchev8_ctf/README.md: -------------------------------------------------------------------------------- 1 | ## Prelim Result 2 | 3 | | Name | Reward | Issues | 4 | |----------|----------|----------| 5 | | Chidubemkingsley | $100 | #29 | 6 | | highskore | $100 | #34 | 7 | 8 | 9 | ## Summary 10 | **`ExtraSafeExternalCall.sol` is a Solidity smart contract that:** 11 | 12 | - Is meant to be inherited by other contracts. 13 | - Provides a safe way to interact with potentially untrusted external contracts. 14 | - Implements mechanisms to mitigate specific attack vectors associated with external calls. 15 | ## Key Points 16 | **Purpose:** 17 | 18 | - The contract provides a method to call external contracts safely, mitigating certain risks. 19 | - It ensures the call does not revert and instead returns the status and any relevant data. 20 | 21 | **Security Measures:** 22 | 23 | - Gas Limit: Limits the amount of gas passed to the external call to prevent gas griefing attacks where the called contract could use excessive gas. 24 | - Data Limit: Limits the size of the returned data to protect against gas bomb attacks where the called contract returns an excessively large amount of data. 25 | - Check if the target contract exists. 26 | **Functionality:** 27 | 28 | - The `callContractSafe` function performs the external call. 29 | - It returns a boolean indicating success. 30 | - If the call fails, it returns the reason for the revert. 31 | - If the external call succeeds, the data from the is returned. 32 | --------------------------------------------------------------------------------