keamanancsrfformMenengah4 mnt baca

Mencegah CSRF dengan Token

Lindungi form dari request palsu lintas situs memakai token CSRF.

Analogi: Stempel Rahasia

Bayangkan bank memproses surat perintah transfer hanya dari tulisan tanganmu. Penyerang tidak bisa memalsukan tulisan tanganmu, tapi bagaimana kalau ia menipumu menandatangani surat kosong? CSRF (Cross-Site Request Forgery) seperti itu: penyerang membuat browser korban yang sedang login mengirim request ke websitemu tanpa sadar, misalnya via gambar tersembunyi <img src="https://bankmu.com/transfer?ke=penyerang">.

Browser korban otomatis menyertakan cookie session, jadi server mengira request itu sah dari user. Solusinya: token CSRF, stempel rahasia yang hanya diketahui server dan form aslimu.

php
<?php
// Contoh 1: membuat dan memvalidasi token CSRF
session_start();

// Saat menampilkan form: buat token acak, simpan di session
if (empty($_SESSION["csrf"])) {
    $_SESSION["csrf"] = bin2hex(random_bytes(32));
}
$token = $_SESSION["csrf"];
?>
<form method="post" action="transfer.php">
    <input type="hidden" name="csrf" value="<?= htmlspecialchars($token) ?>">
    <input type="text" name="jumlah" placeholder="Jumlah transfer">
    <button>Kirim</button>
</form>
<?php
// --- transfer.php: validasi SEBELUM proses ---
session_start();
$kirim = $_POST["csrf"] ?? "";
if (!hash_equals($_SESSION["csrf"] ?? "", $kirim)) {
    http_response_code(403);
    die("Token CSRF tidak valid.");
}
// ... proses transfer ...

Alurnya: token acak disimpan di session DAN disisipkan sebagai hidden input. Saat submit, server membandingkan keduanya. Situs penyerang tidak tahu isi token (tidak bisa membaca halamanmu karena same-origin policy), jadi request palsunya selalu gagal validasi.

php
<?php
// Contoh 2: helper sekali pakai (lebih aman)
// Token sekali pakai: setelah dipakai, hanguskan agar tidak bisa replay
function csrf_token(): string {
    session_start();
    if (empty($_SESSION["csrf_tokens"])) {
        $_SESSION["csrf_tokens"] = [];
    }
    $t = bin2hex(random_bytes(32));
    $_SESSION["csrf_tokens"][$t] = time();
    // bersihkan token lebih dari 1 jam
    $_SESSION["csrf_tokens"] = array_filter(
        $_SESSION["csrf_tokens"],
        fn($w) => time() - $w < 3600
    );
    return $t;
}

function csrf_cek(string $t): bool {
    session_start();
    if (isset($_SESSION["csrf_tokens"][$t])) {
        unset($_SESSION["csrf_tokens"][$t]);   // hanguskan setelah dipakai
        return true;
    }
    return false;
}

hash_equals() wajib untuk membandingkan token: perbandingan === biasa rentan timing attack (penyerang menebak token dari perbedaan waktu respons). Detail kecil, dampak besar.

Jebakan yang Sering Terjadi

Token hanya di form GET. CSRF menyerang aksi yang mengubah data (transfer, hapus, ubah password). Aksi seperti itu wajib POST + token. Form GET pencarian tidak butuh token karena tidak mengubah apa pun.

Mengira SameSite cookie cukup. Cookie SameSite=Lax memang memblokir banyak CSRF, tapi tidak semua (misalnya request GET top-level). Token CSRF tetap lapisan wajib; SameSite adalah bonus, bukan pengganti.

CSRF di Luar Form Klasik

CSRF tidak hanya menyerang <form>. Endpoint API yang dipanggil JavaScript fetch juga rentan jika mengandalkan cookie session:

php
<?php
// API hapus akun via fetch: tetap butuh token!
// JavaScript mengirim token dari meta tag:
?>
<meta name="csrf" content="<?= htmlspecialchars($_SESSION["csrf"]) ?>">
<script>
fetch("api-hapus.php", {
    method: "POST",
    headers: {"X-CSRF": document.querySelector('meta[name=csrf]').content}
});
</script>
<?php
// api-hapus.php
session_start();
$token = $_SERVER["HTTP_X_CSRF"] ?? "";
if (!hash_equals($_SESSION["csrf"] ?? "", $token)) {
    http_response_code(403);
    exit(json_encode(["error" => "CSRF"]));
}

Pola header X-CSRF adalah standar untuk API/AJAX. Token disimpan di meta tag (bukan cookie), dibaca JavaScript, dikirim sebagai header. Situs jahat tidak bisa membaca meta tag-mu (same-origin policy), jadi tetap aman.

Catatan teknis: Framework modern (Laravel) menyuntikkan token CSRF otomatis via @csrf di Blade dan memvalidasi di middleware. Memahami mekanisme manualnya membuatmu paham kenapa error "419 Page Expired" muncul: token kedaluwarsa atau session hilang.

Tantangan

Amankan form ganti password

Tambahkan proteksi CSRF ke form ganti password: buat token di session saat menampilkan form, sisipkan sebagai hidden input, dan validasi dengan hash_equals di file proses sebelum mengganti password.