XSS Prevention & Sanitization — Security

Mencegah XSS Prinsip utama pencegahan XSS: jangan pernah mempercayai input pengguna dan selalu encode/escape output sebelum ditampilkan. 1. Output Encoding…

Mencegah XSS intinya satu: data dari user diubah jadi teks tepat di tempat dia ditaruh. Bayangin koran yang mengutip ucapan narasumber. Kutipannya diberi tanda petik, jadi pembaca tahu itu kata-kata orang lain, bukan pernyataan redaksi. Escape adalah tanda petik untuk browser: < ditulis &lt;, jadi browser menampilkan tanda <, tapi gak membuka tag.

🧪 Tebakan cepat: sebuah fungsi escape mengganti < dulu, baru &. Apa hasilnya untuk a & b < c? Jawabannya ada di bagian "Escape HTML: lima karakter".

Escape HTML: lima karakter

Lima karakter punya arti khusus di HTML. Escape menggantinya dengan entity, kode yang dibaca browser sebagai karakter biasa:

Karakter Jadi
& &amp;
< &lt;
> &gt;
" &quot;
' &#39;

Di latihan, fungsinya bernama sanitizeHTML:

function sanitizeHTML(input) {
  return input
    .replace(/&/g, "&amp;") // harus pertama
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

console.log(sanitizeHTML("<script>alert(1)</script>")); // &lt;script&gt;alert(1)&lt;/script&gt;
console.log(sanitizeHTML('Halo "Budi"')); // Halo &quot;Budi&quot;
console.log(sanitizeHTML("a & b < c")); // a &amp; b &lt; c

Tergantung tempatnya

Penangkal yang benar tergantung di mana data ditaruh. Lab memakai satu template untuk tiap tempat:

Tempat Template Penangkal a & b < c jadi
Isi elemen <p>Komentar: ...</p> sanitizeHTML a &amp; b &lt; c
Nilai atribut <input value="..."> sanitizeHTML, atribut selalu bertanda kutip a &amp; b &lt; c
URL <a href="/cari?q=..."> encodeURIComponent a%20%26%20b%20%3C%20c

Biarkan template yang escape

Template engine modern sudah escape otomatis. Yang perlu kamu kenali justru jalan pintas yang mematikannya.

// PHP biasa: escape sendiri
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
{{-- Blade --}}
<p>{{ $comment->body }}</p>   {{-- aman: di-escape --}}
<p>{!! $comment->body !!}</p> {{-- bahaya: dicetak mentah --}}
// React: {name} di-escape otomatis
<h1>Halo, {name}</h1>
// dangerouslySetInnerHTML melewati escape: jangan untuk teks user
<div dangerouslySetInnerHTML={{ __html: userInput }} />

Kalau memang butuh HTML: sanitizer

Kadang komentar boleh berisi teks tebal dan miring. Escape akan menampilkan <b> sebagai tulisan, jadi yang dipakai sanitizer allowlist: daftar tag yang boleh, sisanya dibuang.

import DOMPurify from "dompurify";

const komentar = "<b>Budi</b> <img src=x onerror=alert(1)>";
const bersih = DOMPurify.sanitize(komentar, { ALLOWED_TAGS: ["b", "i"], ALLOWED_ATTR: [] });
console.log(JSON.stringify(bersih)); // "<b>Budi</b> "
box.innerHTML = bersih;

💡 CSP: jaring pengaman terakhir

Content Security Policy (CSP) adalah header yang memberi tahu browser script mana yang boleh jalan:

Content-Security-Policy: script-src 'self' 'nonce-abc123'

CSP itu jaring pengaman, bukan pengganti escape. Detailnya ada di materi Content Security Policy.

⚠️ Jebakan yang sering ketemu

TL;DR: Ubah data user jadi teks tepat di tempat dia ditaruh. Di HTML, escape lima karakter: & jadi &amp; (pertama), < jadi &lt;, > jadi &gt;, " jadi &quot;, ' jadi &#39;. Di atribut, selalu pakai tanda kutip; di URL, pakai encodeURIComponent. PHP: htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8'). Blade {{ }} escape otomatis, {!! !!} mentah. Kalau HTML-nya memang boleh, pakai sanitizer allowlist seperti DOMPurify. CSP jadi jaring pengaman terakhir, bukan pengganti escape.

Yang akan kamu pelajari