โ† Semua pembelajaran / Laravel Nol โ†’ Enterprise
Fase 4 ยท Autentikasi & Keamanan

CSRF & kerentanan web umum

Laravel menutup sebagian besar kerentanan klasik secara default. Yang berbahaya justru itu: perlindungan yang tidak terlihat gampang dimatikan oleh seseorang yang sedang buru-buru menghilangkan error 419.

Sumber asli laravel.com Resmi Rangkuman ~6 menit baca

Intisari

  • CSRF: penyerang membuat browser korban mengirim permintaan ke situsmu memakai sesinya. Tokenlah penawarnya.
  • @csrf di setiap form; rute web dilindungi otomatis. Laravel 13 memperkuatnya lewat PreventRequestForgery dengan verifikasi origin.
  • 419 Page Expired berarti token hilang atau sesi kedaluwarsa โ€” jangan diperbaiki dengan mengecualikan rutenya.
  • XSS: {{ }} aman, {!! !!} tidak. SQL injection: query builder aman, string yang disambung tidak.
  • Rute API tidak butuh CSRF karena tidak memakai cookie โ€” asalkan ia benar-benar tidak memakai cookie.

Apa yang sebenarnya dicegah

Kamu sedang login di portal.test. Kamu membuka tab lain berisi situs jahat, yang diam-diam memuat:

<form method="POST" action="https://portal.test/artikel/12/hapus">
<script>document.forms[0].submit()</script>

Browser dengan patuh mengirim cookie sesimu, dan artikel terhapus. Token CSRF menutupnya: situs jahat tidak bisa membaca nilai token milik portal.test, jadi permintaannya ditolak sebelum menyentuh controller.

<form method="POST" action="{{ route('artikel.hapus', $artikel) }}">
    @csrf
    @method('DELETE')
    <button>Hapus</button>
</form>
{{-- Untuk JavaScript --}}
<meta name="csrf-token" content="{{ csrf_token() }}">

Laravel 13 memformalkan perlindungan ini sebagai PreventRequestForgery, yang menambahkan verifikasi berbasis origin di samping token. Artinya permintaan yang datang dari asal yang tidak dikenal ditolak walaupun entah bagaimana membawa token yang benar โ€” pertahanan berlapis untuk kelas serangan yang sama.

419 dan cara yang salah memperbaikinya

SebabPerbaikan yang benar
Form tanpa @csrfTambahkan
Sesi kedaluwarsa saat halaman lama dibukaNaikkan SESSION_LIFETIME, atau tangani 419 di frontend
Permintaan AJAX tanpa header tokenKirim X-CSRF-TOKEN
Session driver file di banyak kontainerPindahkan ke Redis
SESSION_DOMAIN salahSamakan dengan domain yang benar-benar dipakai

Yang tidak boleh dilakukan: mengecualikan rute dari perlindungan CSRF supaya errornya hilang. Pengecualian hanya sah untuk endpoint yang memang dipanggil mesin dan punya autentikasi lain โ€” webhook dari penyedia pembayaran, misalnya. Dan webhook seperti itu wajib memverifikasi tanda tangan pengirimnya.

// bootstrap/app.php โ€” hanya untuk webhook yang tanda tangannya diverifikasi
->withMiddleware(function (Middleware $middleware) {
    $middleware->validateCsrfTokens(except: [
        'webhook/pembayaran',
    ]);
})

Peta kerentanan lain

KerentananSudah dilindungi?Cara melubanginya tanpa sadar
SQL injectionYa โ€” semua nilai di-bindwhereRaw("nama = '$input'")
XSSYa โ€” {{ }} meng-escape{!! $input !!}, atau v-html di frontend
Mass assignmentYa โ€” $fillable$guarded = []
CSRFYa โ€” rute webMenambah rute ke daftar pengecualian
Objek milik orang lainTidakLupa policy, atau binding bersarang tanpa scopeBindings()
Kebocoran lewat errorTidakAPP_DEBUG=true di produksi
Redirect terbukaTidakredirect($request->input('next'))
Serangan tebak passwordTidak, kecuali kamu memasangnyaRute login tanpa throttle

Dua yang paling sering terlewat

// Redirect terbuka โ€” penyerang mengirim korban ke situsnya lewat domainmu
return redirect($request->input('next'));                     // BAHAYA

return redirect()->to(
    Str::startsWith($request->input('next', '/'), '/')         // hanya path internal
        ? $request->input('next')
        : '/'
);
// Webhook: verifikasi tanda tangan, dan bandingkan dengan cara yang tahan timing attack
$tandaTangan = hash_hmac('sha256', $request->getContent(), config('services.bayar.secret'));

abort_unless(
    hash_equals($tandaTangan, $request->header('X-Signature', '')),
    403,
);

hash_equals(), bukan ===. Perbandingan string biasa berhenti di karakter pertama yang berbeda, sehingga waktu eksekusinya membocorkan seberapa banyak awalan yang sudah benar. hash_equals() selalu membandingkan seluruh panjangnya.

Header keamanan

// Middleware global
$response->headers->add([
    'X-Content-Type-Options' => 'nosniff',
    'X-Frame-Options'        => 'SAMEORIGIN',
    'Referrer-Policy'        => 'strict-origin-when-cross-origin',
    'Strict-Transport-Security' => 'max-age=31536000; includeSubDomains',
]);

Di Fase 9, header seperti ini lebih tepat dipasang di CloudFront lewat response headers policy โ€” supaya juga berlaku untuk berkas statis yang tidak pernah menyentuh PHP.

Latihan: buat form penghapusan tanpa @csrf, kirim, dan pastikan kamu menerima 419. Lalu buat endpoint webhook yang memverifikasi HMAC dengan hash_equals(), dan uji dengan tanda tangan yang benar dan yang salah.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.