ngodingdulu
SecurityTutorialPemula

XSS dan CSRF dengan Contoh Nyata

Dua serangan yang memanfaatkan browser penggunamu sendiri. Kami mencobanya di Chrome sungguhan, supaya kamu tahu persis mana yang aman dan mana yang tidak.

Oleh Afa Hernandes6 menit baca

Daftar isi

SQL injection menyerang database-mu. Dua serangan di artikel ini menyerang lewat jalur lain: browser penggunamu sendiri.

  • XSS (Cross-Site Scripting): penyerang menyisipkan script ke halamanmu, lalu script itu berjalan di browser setiap orang yang membukanya.
  • CSRF (Cross-Site Request Forgery): situs lain membuat browser penggunamu mengirim permintaan ke aplikasimu, misalnya mengganti email atau mentransfer saldo, tanpa disadari pengguna.

Kedua serangan ini termasuk dalam OWASP Top 10. Semua contoh di artikel ini sudah kami jalankan di Chrome, termasuk simulasi dua situs untuk CSRF, jadi hasil yang ditulis di sini adalah hasil sungguhan, bukan teori.

XSS: ketika teks pengguna berubah jadi kode

Contoh celahnya

Bayangkan fitur komentar. Kode yang menampilkan komentar sering ditulis seperti ini:

komentar.js
const komentar = ambilKomentarDariServer(); // teks yang diketik pengunjung
// ❌ RENTAN: teks diperlakukan sebagai HTML
kotak.innerHTML = komentar;

Selama isinya teks biasa, semuanya tampak normal. Sekarang seseorang menulis komentar ini:

<img src=x onerror="alert('Akunmu sudah dibobol')">

Browser memperlakukannya sebagai HTML sungguhan. Gambar x gagal dimuat, lalu kode di onerror dijalankan, di browser setiap orang yang membuka halaman itu. Pada serangan nyata, isinya bukan alert, tapi kode yang mengirim data halaman ke server penyerang, mengubah isi halaman menjadi form login palsu, atau bertindak atas nama pengguna.

Cara mencegahnya: tampilkan sebagai teks

komentar.js
// ✅ AMAN: isi selalu tampil sebagai teks biasa
kotak.textContent = komentar;

Kami menguji keduanya di Chrome dengan isi komentar yang sama:

Cara menampilkan Kode onerror dijalankan? Yang terlihat pengguna
innerHTML Ya Gambar rusak, dan script-nya berjalan
textContent Tidak Teks <img src=x onerror=...> apa adanya

Ini juga alasan aplikasi di tutorial aplikasi pertama dengan AI sengaja meminta textContent di spesifikasinya.

Aturan pencegahan XSS

  1. Tampilkan input pengguna sebagai teks. Di JavaScript murni pakai textContent, bukan innerHTML.
  2. Manfaatkan framework. React, Vue, Svelte, Astro, dan template engine server seperti Blade (Laravel) atau Jinja (Python) otomatis meng-escape teks. Celahnya muncul justru saat fitur itu dimatikan, misalnya dangerouslySetInnerHTML (React), v-html (Vue), {!! !!} (Blade), atau |safe (Jinja). Setiap kali melihatnya, pastikan isinya tidak berasal dari pengguna.
  3. Kalau memang perlu HTML dari pengguna (misalnya editor teks kaya), bersihkan dulu dengan library sanitizer yang matang, misalnya DOMPurify. Jangan menulis penyaring sendiri dengan regex.
  4. Pasang Content Security Policy (CSP) sebagai lapisan tambahan. Header ini memberi tahu browser script dari mana saja yang boleh dijalankan, sehingga script sisipan lebih sulit berjalan walaupun ada celah.
  5. Tandai cookie session dengan HttpOnly, supaya cookie itu tidak bisa dibaca JavaScript, termasuk JavaScript sisipan penyerang.

CSRF: browser penggunamu dipakai mengirim permintaan

Cara kerjanya

Saat pengguna login ke aplikasimu, browser menyimpan cookie session. Setiap permintaan ke aplikasimu membawa cookie itu, dan dari cookie itulah aplikasimu tahu siapa penggunanya.

Masalahnya: dulu browser juga mengirim cookie itu walaupun permintaannya dipicu dari situs lain. Jadi situs penyerang cukup memasang form tersembunyi seperti ini:

situs-penyerang.html
<form method="POST" action="https://tokoku.id/akun/ganti-email">
<input type="hidden" name="email" value="penyerang@contoh.com">
</form>
<script>document.forms[0].submit();</script>

Pengguna yang sedang login ke tokoku.id cukup membuka halaman penyerang. Browsernya mengirim form itu ke tokoku.id lengkap dengan cookie session, dan aplikasi mengira permintaan itu dibuat oleh pengguna sendiri. Email akun berganti, dan pengambilalihan akun tinggal selangkah lagi.

Atribut SameSite mengatur apakah cookie ikut terkirim pada permintaan dari situs lain. Atur cookie session seperti ini:

Set-Cookie: sesi=abc123; HttpOnly; Secure; SameSite=Lax; Path=/
Atribut Fungsi
HttpOnly Cookie tidak bisa dibaca JavaScript (membatasi dampak XSS)
Secure Cookie hanya dikirim lewat HTTPS
SameSite=Lax Cookie tidak dikirim pada POST yang dipicu situs lain, tapi tetap dikirim saat pengguna membuka link biasa ke situsmu
SameSite=Strict Cookie tidak dikirim dari situs lain sama sekali, termasuk saat pengguna mengklik link dari situs lain. Paling ketat, tapi pengguna akan tampak “logout” saat datang dari link luar

Kami membuktikannya dengan dua server lokal: “aplikasi” di 127.0.0.1 dan “penyerang” di localhost, yang bagi browser dihitung sebagai dua situs berbeda. Cookie session diset dengan SameSite=Lax, lalu form POST dikirim dari dua tempat:

Asal form POST Cookie session ikut terkirim?
Situs penyerang (http://localhost:3102) Tidak
Situs sendiri (http://127.0.0.1:3101) Ya

Serangan CSRF di atas gagal, karena aplikasi menerima permintaan tanpa session.

Pertahanan kedua: token CSRF dan pemeriksaan Origin

SameSite sangat membantu, tapi jangan jadikan satu-satunya pertahanan. Ada browser lama, ada konfigurasi cookie yang keliru, dan ada celah dari subdomain. Tambahkan minimal salah satu dari dua lapisan berikut:

Token CSRF. Server menyisipkan nilai acak ke setiap form, lalu menolak permintaan yang tidak membawa token yang cocok. Situs penyerang tidak bisa membaca token itu, jadi tidak bisa memalsukannya. Hampir semua framework sudah menyediakannya: @csrf di Laravel, {% csrf_token %} di Django, dan sejenisnya. Pastikan fitur itu aktif, jangan dimatikan.

Periksa header Origin. Browser menyertakan header Origin pada permintaan POST. Dalam uji di atas, permintaan dari situs penyerang membawa Origin: http://localhost:3102. Tolak permintaan yang mengubah data kalau asalnya bukan situsmu sendiri:

cek-origin.js
const ASAL_SAH = 'https://tokoku.id';
function asalDiizinkan(req) {
// hanya terima permintaan yang mengubah data dari situs sendiri
return req.headers.origin === ASAL_SAH;
}

Aturan pencegahan CSRF

  1. Jangan pernah mengubah data lewat GET. Link seperti /hapus?id=5 bisa dipicu dari mana saja, bahkan dari tag <img>. Pakai POST, PUT, atau DELETE untuk setiap perubahan.
  2. Cookie session: HttpOnly; Secure; SameSite=Lax (atau Strict).
  3. Aktifkan perlindungan CSRF bawaan framework-mu, atau periksa header Origin.
  4. Minta konfirmasi ulang untuk aksi sensitif seperti mengganti email, password, atau melakukan pembayaran, misalnya dengan memasukkan password lagi.

Kalau kodenya ditulis AI

Saat meninjau kode buatan AI, cari:

  • innerHTML, dangerouslySetInnerHTML, v-html, {!! !!}, atau |safe yang menerima data dari pengguna,
  • cookie session yang dibuat tanpa HttpOnly, Secure, dan SameSite,
  • endpoint yang mengubah data lewat GET,
  • perlindungan CSRF framework yang dimatikan “supaya form-nya jalan”.

Poin terakhir sering terjadi. Saat form gagal karena token CSRF, solusi tercepat yang kadang disarankan adalah mematikan pemeriksaannya. Solusi yang benar adalah menyertakan token di form tersebut.

Pertanyaan yang sering diajukan

Apa bedanya XSS dan CSRF?

XSS menyisipkan kode ke halamanmu, sehingga penyerang bisa melakukan hampir apa saja di halaman itu. CSRF tidak menyisipkan kode, tapi meminjam login pengguna untuk mengirim satu permintaan dari situs lain. Kalau aplikasimu punya celah XSS, perlindungan CSRF ikut tidak berarti, karena script sisipan berjalan dari dalam situsmu sendiri.

Kalau token disimpan di JavaScript dan dikirim lewat header Authorization, browser tidak mengirimnya otomatis, jadi CSRF klasik tidak berlaku. Tapi token yang disimpan di localStorage bisa dicuri lewat XSS. Tidak ada pilihan yang bebas risiko. Yang penting, kamu tahu risiko pilihanmu.

Apakah HTTPS mencegah XSS dan CSRF?

Tidak. HTTPS melindungi data saat dikirim, bukan isi halaman atau asal permintaan. Tetap wajib dipakai (dan atribut Secure pada cookie membutuhkannya), tapi XSS dan CSRF butuh pertahanannya sendiri.

Kesimpulan

XSS terjadi saat teks dari pengguna diperlakukan sebagai kode. Tampilkan sebagai teks, dan biarkan framework meng-escape-nya. CSRF terjadi saat situs lain meminjam login penggunamu. Pakai cookie SameSite, token CSRF atau pemeriksaan Origin, dan jangan pernah mengubah data lewat GET.

Dengan ini Level 1 jalur Security selesai. Kalau aplikasimu dibangun dengan bantuan AI, rangkum semuanya dengan checklist keamanan aplikasi buatan AI.

Bagikan:WhatsAppTelegramXFacebook
Ilustrasi input email berisi tanda kutip OR 1=1 yang membuat query rentan membocorkan semua pengguna termasuk admin, dibandingkan query aman dengan placeholder yang mengembalikan nol baris
SecurityTutorial

SQL Injection: Cara Kerja dan Pencegahannya

Cukup satu tanda kutip di kolom login untuk membuka seluruh isi tabel. Lihat sendiri cara kerjanya, lalu tutup celahnya dengan satu kebiasaan sederhana.

9 Maret 20266 mnt

Ilustrasi password kopi-susu-123 dan salt acak masuk ke mesin hashing scrypt atau argon2id, menghasilkan hash yang disimpan di database dan tidak bisa dibalik
SecurityTutorial

Menyimpan Password dengan Benar: Hashing

Kalau database-mu bocor, password pengguna tidak boleh ikut terbaca. Begini cara menyimpannya supaya tetap aman, bahkan dalam skenario terburuk.

23 Februari 20266 mnt

Enter bukaEsc tutupHalaman pencarian