Skip to main content

Reentrancy Attack

Reentrancy attack adalah serangan pada smart contract yang terjadi ketika sebuah kontrak memanggil kontrak eksternal sebelum memperbarui state internalnya. Hal ini memungkinkan kontrak eksternal tersebut untuk memanggil ulang (re-enter) fungsi awal secara berulang dalam satu transaksi, sehingga logika kontrak rusak, misalnya saldo belum berkurang tetapi dana sudah terkirim.

Ilustrasi Sederhana

Misalkan kita memiliki fungsi withdraw() seperti berikut:

function withdraw(uint _amount) public {
require(balances[msg.sender] >= _amount);

// Kirim ETH ke pengguna
(bool success, ) = msg.sender.call{value: _amount}("");
require(success, "Transfer failed");

// Update saldo setelah kirim
balances[msg.sender] -= _amount;
}

Masalahnya:

  • Saldo baru dikurangi setelah ETH dikirim.
  • Jika msg.sender adalah kontrak berbahaya dengan fallback seperti ini:
fallback() external payable {
victim.withdraw(1 ether);
}

Maka saat call mengirim ETH, fallback akan dipanggil, lalu kontrak berbahaya bisa memanggil ulang withdraw() sebelum saldo diperbarui, sehingga bisa menarik dana berkali-kali.

Contoh Nyata

The DAO Hack (2016)

  • Kerugian: ± $60 juta ETH
  • Mekanisme: Penyerang memanfaatkan reentrancy untuk memanggil withdraw() berkali-kali.
  • Dampak: Hard fork Ethereum → lahirnya Ethereum Classic.

Cara Mencegah

1. Checks-Effects-Interactions Pattern

Urutkan kode menjadi: Cek kondisi → Update state → Interaksi eksternal

function withdraw(uint _amount) public {
require(balances[msg.sender] >= _amount);

// Update saldo dulu
balances[msg.sender] -= _amount;

// Baru kirim ETH
(bool success, ) = msg.sender.call{value: _amount}("");
require(success, "Transfer failed");
}

2. Gunakan ReentrancyGuard

Memakai library OpenZeppelin dan modifier nonReentrant untuk mencegah panggilan berulang pada fungsi yang sama.

3. Minimalkan Interaksi Eksternal

  • Hindari call atau transfer otomatis jika tidak perlu.
  • Gunakan pola withdrawal pattern ketimbang pembayaran langsung.