Formulir mengumpulkan teks dari pengunjung, dan teks itu hampir selalu ditampilkan kembali — pada daftar komentar, ulasan produk, atau profil. Di titik itu muncul pertanyaan keamanan yang menentukan: apakah isian dirender sebagai teks, atau sebagai markup yang diurai browser? Bila markup dari luar ikut diurai, halaman kita memproses perintah yang bukan berasal dari kita. Chapter ini membedah mekanisme tersebut beserta prinsip penangkalnya: perlakukan input pengguna sebagai teks dan lakukan escape saat dirender.

Ketika Markup Dibaca Kembali #

Browser tidak membedakan markup yang kita tulis dengan markup yang terselip di dalam isian form; keduanya sama-sama diurai menjadi pohon DOM. Misalkan Budi mengirim komentar berikut pada formulir di situs kita:

html Isian komentar yang dikirim Budi
<img src="foto-palsu.png" onerror="ambil(document.cookie)">

Output yang Dihasilkan:

text
Browser mencoba memuat foto-palsu.png. Permintaan gagal, lalu event
onerror berjalan dan memanggil fungsi ambil() beserta cookie halaman.
Komentar Budi tidak tampil sebagai teks sama sekali.

Perhatikan bahwa isian di atas bahkan tidak memakai elemen script. Atribut onerror adalah event handler HTML yang berjalan saat gambar gagal dimuat — cukup untuk menjalankan JavaScript. Varian lain menyelipkan <script> langsung ke dalam isian; hasilnya sama: skrip asing berjalan di dalam konteks halaman kita.

Perlakukan Input sebagai Teks #

Prinsipnya tegas: data dari pengunjung adalah teks biasa, dan markup hanya boleh berasal dari kode yang kita tulis. Agar teks itu tampil apa adanya, karakter khususnya perlu di-escape — diganti bentuk entitasnya — sebelum digabung ke dokumen. Escape dilakukan saat render, pada mesin template server, bukan saat data disimpan. Potongan pseudo-code berikut membandingkan kedua versinya:

text Pseudo-code server: versi rentan dan versi aman
Versi rentan: render('<p class="komentar">' + komentar + '</p>')

Versi aman  : render('<p class="komentar">' + escapeHTML(komentar) + '</p>')

Output yang Dihasilkan:

text
Versi rentan : browser mengurai markup dalam isian; event onerror berjalan
Versi aman   : isian tampil apa adanya sebagai teks; tag tidak diurai

Mari kita telaah kedua jalurnya:

  1. Baris 1 (versi rentan): string komentar digabung langsung ke dokumen, sehingga browser menguraikan isinya sebagai markup.
  2. Baris 3 (versi aman): fungsi escapeHTML() mengganti karakter khusus — < menjadi &lt; dan seterusnya — sebelum penggabungan.
  3. Beda hasilnya: markup dalam isian berhenti menjadi markup; yang tersisa hanyalah teks yang aman ditampilkan.

Mesin template modern menyediakan fungsi escape bawaan; tugas kita adalah memastikan jalur render benar-benar memakainya.

Jangan Percaya Validasi Klien #

Validasi browser yang kita bahas pada section Form Validation menyaring salah ketik dan isian terlewat, bukan isian yang disusun dengan sengaja. Siapa pun dapat menghapus atribut required lewat DevTools atau mengirim permintaan langsung tanpa melewati halaman — pembahasannya ada pada chapter Batasan Validasi HTML. Karena itu server wajib memeriksa ulang setiap data yang masuk dan meng-escape saat render. Validasi klien adalah lapisan kenyamanan; escape saat render adalah lapisan keamanan.

Contoh: Komentar yang Ditampilkan dengan Aman #

<!doctype html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Komentar yang Ditampilkan dengan Aman</title>
</head>
<body>
  <h1>Komentar Pengunjung</h1>
  <article>
    <h2>Siti</h2>
    <p>Materinya runtut, terima kasih untuk latihannya!</p>
  </article>
  <article>
    <h2>Budi</h2>
    <p>&lt;img src="foto-palsu.png" onerror="ambil(document.cookie)"&gt;</p>
  </article>
  <p>Komentar Budi tersimpan dalam bentuk yang di-escape, sehingga browser menampilkan tag-nya apa adanya dan tidak menguraikan elemen <code>img</code> sama sekali.</p>
</body>
</html>

Output yang Dihasilkan:

text
Dua kartu komentar tampil. Pada kartu kedua, tag img dan atribut
onerror tampil sebagai teks biasa; tidak ada elemen yang dibuat.

Mari kita telaah bagian pentingnya:

  1. <article> pertama: komentar wajar dari Siti lewat jalur render yang sama; isinya tidak mengandung karakter khusus.
  2. <article> kedua: komentar Budi tersimpan dalam bentuk ter-escape — &lt; menggantikan setiap < — sehingga browser menampilkan tag-nya sebagai teks.
  3. Paragraf penjelas: menegaskan kepada pembaca halaman bahwa teks tersebut memang sengaja ditampilkan demikian.

Pola yang kita cegah pada chapter ini memiliki nama resmi dan tiga variasi jalur: Cross-Site Scripting. Chapter berikutnya memperkenalkannya secara utuh, lengkap dengan dampak, jenis, dan mitigasi yang diakui industri.

Referensi resmi untuk pendalaman:

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

komentar-aman.html

Unduh berkas
html
<!doctype html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Komentar yang Ditampilkan dengan Aman</title>
</head>
<body>
  <h1>Komentar Pengunjung</h1>
  <article>
    <h2>Siti</h2>
    <p>Materinya runtut, terima kasih untuk latihannya!</p>
  </article>
  <article>
    <h2>Budi</h2>
    <p>&lt;img src="foto-palsu.png" onerror="ambil(document.cookie)"&gt;</p>
  </article>
  <p>Komentar Budi tersimpan dalam bentuk yang di-escape, sehingga browser menampilkan tag-nya apa adanya dan tidak menguraikan elemen <code>img</code> sama sekali.</p>
</body>
</html>