Belajar Solidity - Security: Reentrancy & Common Vulnerabilities
Episode 15 of 23

Belajar Solidity - Security: Reentrancy & Common Vulnerabilities

Mengenali dan menangkis kerentanan klasik kontrak pintar: reentrancy dengan pola checks-effects-interactions dan nonReentrant, overflow bawaan 0.8.x, serangan flash loans, manipulasi oracle, front-running, serta pitfall tx.origin dan selfdestruct

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah di episode 14 kalian menguasai proxy dan governance, pada episode ini kita memasuki topik yang membedakan developer Solidity biasa dari yang profesional: keamanan.

Mengapa episode ini penting? Karena sejarah menunjukkan bahwa bug keamanan kecil bisa berarti kerugian miliaran dolar. Serangan reentrancy pada 2016 memicu hard fork Ethereum (The DAO). Puluhan protokol sejak itu jatuh karena pola yang sama atau variasinya. Di dunia kontrak pintar, keamanan bukan pelengkap — ia adalah syarat untuk beroperasi.

Reentrancy: Serangan Klasik

Reentrancy terjadi saat kontrak mengirim ETH/token keluar sebelum memperbarui state-nya, sehingga pemanggil bisa masuk lagi ke fungsi yang sama sebelum panggilan pertama selesai.

src/VaultVulnerable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.36;
 
contract VulnerableVault {
    mapping(address => uint256) public balances;
 
    function withdraw() external {
        uint256 amount = balances[msg.sender];
        (bool ok, ) = msg.sender.call{value: amount}("");
        require(ok, "gagal kirim");
        balances[msg.sender] = 0; // BUG: state diubah SETELAH kirim dana
    }
}

Kontrak penyerang dengan receive() kosong (fallback) memanggil kembali withdraw() — dan karena balances belum di-set 0, saldo masih penuh. Ini bisa diulang sampai vault terkuras.

Solusi 1: Checks-Effects-Interactions (CEI)

Aturan emas: ubah state dulu, baru berinteraksi dengan kontrak lain.

solidity
function withdraw() external {
    // Checks
    uint256 amount = balances[msg.sender];
    require(amount > 0, "nol");
 
    // Effects: update state SEBELUM interaksi
    balances[msg.sender] = 0;
 
    // Interactions
    (bool ok, ) = msg.sender.call{value: amount}("");
    require(ok, "gagal kirim");
}

Sekarang panggilan ulang masuk ke withdraw() yang melihat saldo sudah 0 — serangan gagal. CEI adalah pertahanan pertama dan paling penting untuk fungsi yang mentransfer aset.

Solusi 2: Modifier nonReentrant

Untuk jaring pengaman ekstra, gunakan reentrancy guard — lock sederhana yang menolak panggilan masuk saat fungsi sedang berjalan:

solidity
bool private _locked;
 
modifier nonReentrant() {
    require(!_locked, "reentrancy terdeteksi");
    _locked = true;
    _;
    _locked = false;
}

Versi production tersedia di OpenZeppelin (ReentrancyGuard). Kebiasaan baik: pasang nonReentrant di semua fungsi yang mentransfer aset, bahkan jika sudah menerapkan CEI — pertahanan berlapis.

Warning

Ingat pelajaran besar dari The DAO: reentrancy tidak hanya lewat call — bisa lewat token yang memiliki hook (ERC-777) atau transferFrom yang memanggil kontrak lain. Selalu kombinasikan CEI + nonReentrant, dan audit semua jalur interaksi eksternal.

Overflow: Dicek Otomatis di 0.8.x

Sejak Solidity 0.8.0, overflow/underflow otomatis revert:

solidity
uint8 x = 255;
x + 1; // revert: overflow

Untuk kasus tertentu yang membutuhkan wrap-around (misal hash computation), gunakan unchecked — tapi sadarilah kalian menonaktifkan proteksi:

solidity
unchecked {
    counter += 1; // dibungkus, tanpa cek
}

Pada kontrak lama (pra-0.8), overflow adalah kerentanan besar — inilah mengapa upgrade ke 0.8.x sangat disarankan (kita bahas versi di episode 16).

Flash Loans, Oracle Manipulation, dan Front-running

Flash Loan

Flash loan meminjamkan dana tanpa kolateral dengan syarat dikembalikan dalam satu transaksi. Ini alat sah, tetapi juga bahan serangan: attacker meminjam miliaran untuk memanipulasi harga sementara, lalu memanfaatkan protokol yang bergantung pada harga itu. Mitigasi terkuat adalah harga yang tidak mudah dimanipulasi (oracle terdesentralisasi dari episode 13).

Oracle Manipulation

Jika protokol memakai harga dari pool yang dangkal (misal cadangan kecil di satu AMM), attacker bisa memompa harga, menabrak protokol, lalu menjual kembali. Solusi: pakai Chainlink Price Feeds yang mengagregasi banyak sumber dan tahan manipulasi.

Front-running

Validator/mempool memungkinkan orang lain melihat transaksi sebelum masuk block. Attacker bisa mendahului transaksi swap yang besar (sandwich attack) atau memburu transaksi yang menguntungkan. Mitigasi:

  • amountOutMin — batasi slippage (kita buat di episode 12).
  • Private transaction services (Flashbots) untuk eksekusi sensitif.
  • Desain yang meminimalkan MEV.

Pitfall Klasik: tx.origin dan selfdestruct

tx.origin

solidity
require(tx.origin == owner); // RENTAN: phishing

tx.origin adalah alamat yang memulai rantai transaksi, bukan pemanggil langsung. Sebuah kontrak phising bisa memanggil kontrak kalian, dan tx.origin tetap mengembalikan wallet pengguna — melewati cek! Selalu gunakan msg.sender:

solidity
require(msg.sender == owner); // aman

selfdestruct

selfdestruct menghapus kontrak dan mengirim ETH-nya — tetapi fungsi yang memakai selfdestruct untuk transfer tidak bisa dipercaya sebagai "guaranteed payment": penyerang bisa memanggil selfdestruct terhadap alamat lain dan memaksakan ETH masuk tanpa melewati receive() kontrak tujuan. Jangan pernah menganggap saldo ETH sebagai "aman" tanpa logika.

Audit Mindset

Keamanan bukan sekadar daftar teknik — ini pola pikir:

  1. Threat model dulu — siapa penyerangnya, apa aset yang berharga, jalur apa yang bisa disalahgunakan?
  2. Tulis test negatif — setiap fungsi harus punya test "harus revert jika ...".
  3. Anggap semua input tak terpercaya — validasi di setiap batas fungsi.
  4. Gunakan tool otomasi — Slither, Mythril, Echidna (kita bahas di episode 18).
  5. Sewakan auditor — kode bernilai jutaan layak diaudit manusia.

Important

Aturan praktis yang menghemat segalanya: CEI untuk semua transfer aset, msg.sender bukan tx.origin, oracle yang tidak termanipulasi, dan test negatif untuk setiap jalur revert. Terapkan empat ini, dan kalian sudah menghindari mayoritas kerentanan klasik.

Penutup

Inti yang harus dibawa pulang:

  • Reentrancy: terapkan CEI (update state sebelum interaksi) + nonReentrant.
  • Overflow otomatis diamankan sejak 0.8.x; unchecked menonaktifkannya dengan sadar.
  • Flash loans, manipulasi oracle, dan front-running menyerang lewat data harga & ordering — mitigasi dengan oracle agregat dan amountOutMin.
  • tx.origin tidak pernah dipakai untuk authorization; selfdestruct tidak bisa menjamin transfer.
  • Keamanan dimulai dari threat model, bukan sekadar daftar CVE.

Di episode 16 selanjutnya kita akan membahas Solidity 0.8.36 & fitur terbaru — rilis stabil terbaru, fitur SSA stack-to-memory spilling, penghapusan backend EOF eksperimental, builtin erc7201, support Fusaka dan default EVM osaka, serta persiapan menuju 0.9.0 yang breaking. Sampai jumpa di episode 16!

Belajar Solidity - Security: Reentrancy & Common Vulnerabilities | Belajar Solidity