Stored XSS dan Rich Text Editor: Cara Membuat Sanitizer HTML yang Aman

Stored XSS terjadi ketika payload disimpan—misalnya di artikel atau komentar—lalu dieksekusi pada browser pembaca. Dampaknya dapat mencakup pengambilalihan sesi, tindakan sebagai user, dan perubahan tampilan.
Escaping bukan sanitization#
Jika content harus tampil persis sebagai text, gunakan output encoding. Jika editor memang mengizinkan <strong>, link, tabel, dan gambar, encoding seluruh HTML akan merusak fungsi. Saat itulah dibutuhkan sanitizer allowlist.
Model allowlist#
$allowedTags = ['p','br','strong','em','ul','ol','li','a','pre','code'];
$allowedAttrs = [
'a' => ['href','title','target','rel'],
'code' => ['class'],
];
$allowedSchemes = ['http','https','mailto'];
Buang script, style, iframe, event handler seperti onerror, dan atribut yang tidak dikenali. URL harus diparse dan scheme diperiksa setelah normalisasi.
Payload uji#
<img src=x onerror=alert(1)>
<a href="javascript:alert(1)">klik</a>
<svg onload=alert(1)></svg>
<p style="background:url(javascript:alert(1))">x</p>
Hasil aman: event handler dan scheme berbahaya hilang; tag tidak diizinkan dibuang atau diubah menjadi text. Jangan menampilkan payload mentah pada admin preview tanpa sanitizer.
Jebakan parser#
Regex tidak memahami HTML malformed, entity encoding, namespace SVG, dan perbedaan parser browser. Gunakan parser HTML yang terawat. OWASP XSS Prevention merekomendasikan HTML sanitization ketika user perlu menulis HTML, serta mengingatkan bahwa memodifikasi HTML setelah sanitization dapat membatalkan perlindungan.
Sanitize saat simpan atau render?#
Sanitize saat render memberi keuntungan: aturan terbaru melindungi content lama. Anda dapat juga sanitize saat simpan untuk feedback cepat, tetapi simpan raw content hanya jika aksesnya sangat dibatasi dan ada kebutuhan audit. Jangan pernah merender raw content.
Defense in depth#
- Sanitization pada server.
- Output encoding untuk semua field text.
- CSP nonce/hash tanpa
unsafe-inlinejika memungkinkan. - HttpOnly session cookie.
- Preview melalui pipeline yang sama dengan halaman publik.
- Regression corpus berisi payload XSS.
- Patch library sanitizer.
Test yang berguna#
Input: <a href="javascript:alert(1)" target="_blank">x</a>
Output: <a target="_blank" rel="noopener noreferrer">x</a>
Assert: tidak ada javascript:, on*, style, iframe, svg, object
Sanitizer yang baik bukan yang menerima sebanyak mungkin HTML, tetapi yang mempertahankan kebutuhan editor dengan permukaan serangan sekecil mungkin.
URL validation yang sering terlupakan#
Allowlist atribut href belum cukup. Trim control character, parse scheme secara case-insensitive, dan izinkan relative URL atau scheme spesifik. Untuk link target="_blank", tambahkan rel="noopener noreferrer". Tentukan apakah data: image memang dibutuhkan; jika tidak, tolak.
Mutation setelah sanitization#
Contoh berbahaya: HTML sudah dibersihkan, lalu plugin syntax highlighter membangun markup menggunakan innerHTML dari attribute yang tidak dipercaya. Audit seluruh alur setelah sanitizer. Gunakan DOM API dan safe sink seperti textContent ketika membuat text.
Regression corpus#
- Tag dan attribute yang diizinkan tetap berfungsi.
- Mixed-case
JaVaScRiPt:ditolak. - Entity dan malformed markup tidak lolos.
- SVG/MathML tidak masuk bila tidak dibutuhkan.
- Nested forbidden element tidak menyisakan executable child.
- Link eksternal memperoleh rel yang aman.
Respons insiden stored XSS#
Nonaktifkan rendering raw content, identifikasi seluruh record terdampak, rotate session bila ada risiko pencurian, perbaiki sanitizer, re-sanitize content lama, dan tambahkan payload tersebut ke test suite. Menghapus satu posting saja tidak memperbaiki akar masalah.
💬 Comments (0)
No comments yet. Be the first to share your thoughts!