XSS dibagi jadi tiga tipe: Stored, Reflected, dan DOM-based. Bedanya cuma di dua pertanyaan: teks yang gak bisa dipercaya itu datang dari mana, dan siapa yang menaruhnya ke halaman. Bayangin tukang ledeng yang datang ke rumah dengan pipa bocor. Pertanyaan pertamanya selalu: airnya masuk dari mana, dan keluar di mana? Jawaban itu yang menentukan bagian mana yang diperbaiki. XSS juga begitu.
🧪 Tebakan cepat: di DOM-based XSS, apakah escape di template server bisa menolong? Jawabannya ada di bagian "DOM-based XSS".
Source dan sink
- Source adalah tempat data dari user masuk: komentar yang disimpan di database, parameter URL seperti
?q=, atau bagian URL yang dibaca JavaScript. - Sink adalah tempat data itu dijadikan HTML: template server yang mencetak mentah, atau
innerHTMLdi JavaScript. - XSS terjadi kalau data dari source sampai ke sink tanpa escape. Perbaikannya selalu di sink: data ditaruh sebagai teks.
Lab memakai tiga contoh yang sama dengan materi ini, masing-masing dengan teks yang gak bisa dipercaya sebagai datanya.
Stored XSS
Data disimpan dulu, lalu ditampilkan ke setiap orang yang membuka halamannya. Contohnya komentar di database yang dicetak oleh Blade:
{{-- rentan: komentar dari database dicetak mentah --}}
<p>{!! $comment->body !!}</p>
{{-- perbaikan: di-escape saat ditampilkan --}}
<p>{{ $comment->body }}</p>
- Source: database, kolom
bodydi tabel komentar. Sink: template server. - Yang kena: setiap pengunjung halaman itu, karena datanya ikut tersaji terus. Itu yang membuat Stored XSS paling luas dampaknya.
- Perbaikan: escape saat ditampilkan. Dengan
{{ }}, komentar<script>alert(1)</script>tampil sebagai tulisan.
Reflected XSS
Data datang dari request dan langsung dipantulkan ke response, tanpa disimpan. Contohnya halaman pencarian:
// rentan: kata kunci dari URL disalin mentah ke HTML
res.send("<p>Hasil pencarian untuk: " + req.query.q + "</p>");
// perbaikan: di-escape dulu
res.send("<p>Hasil pencarian untuk: " + sanitizeHTML(req.query.q) + "</p>");
- Source: parameter URL
q. Sink: response server. - Yang kena: cuma orang yang membuka URL dengan
qitu, karena gak ada yang disimpan. - Perbaikan: escape di server, atau pakai template engine yang escape otomatis.
sanitizeHTMLdi sini fungsi escape dari materi XSS Prevention.
DOM-based XSS
Di sini server gak ikut menyusun HTML-nya. JavaScript di browser membaca data dari URL, lalu menaruhnya ke halaman:
const pesan = new URLSearchParams(location.search).get("pesan");
// rentan: dibaca sebagai HTML
document.getElementById("pesan").innerHTML = pesan;
// perbaikan: selalu jadi teks
document.getElementById("pesan").textContent = pesan;
- Source:
location.search, dibaca di browser. Sink:innerHTML. - Yang kena: orang yang membuka URL itu.
- Escape di template server gak menolong, karena HTML-nya dibuat JavaScript di browser, bukan oleh server. Kalau source-nya
location.hash, server bahkan gak menerima teksnya sama sekali: bagian setelah#gak dikirim browser. Itu jawaban tebakannya. Perbaikannya harus di JavaScript:textContent.
Perbandingan
| Tipe | Source | Sink | Yang kena | Perbaikan |
|---|---|---|---|---|
| Stored | database | template server | setiap pengunjung | escape saat ditampilkan |
| Reflected | parameter request | response server | yang membuka URL itu | escape di server |
| DOM-based | URL, dibaca JavaScript | innerHTML |
yang membuka URL itu | textContent di JavaScript |
⚠️ Jebakan yang sering ketemu
- Mengira data dari URL pasti Reflected. Kalau yang menaruhnya ke HTML adalah JavaScript di browser, itu DOM-based, dan perbaikannya di JavaScript.
- Escape cuma di satu halaman. Data yang disimpan bisa tampil di banyak tempat: halaman publik, panel admin, notifikasi. Setiap sink harus escape.
- Mengandalkan filter saat data masuk. Data lama atau data dari jalur lain bisa lolos. Escape saat ditampilkan tetap wajib.
- Lupa sink lain. Selain
innerHTML, adaouterHTML,insertAdjacentHTMLdandocument.write. Untuk teks, pakaitextContent.
TL;DR: Tiga tipe XSS dibedakan dari source dan sink-nya. Stored XSS: data tersimpan di database dan tercetak mentah, mengenai setiap pengunjung; perbaiki dengan escape saat ditampilkan ({{ }} di Blade). Reflected XSS: data dari URL atau form langsung dipantulkan server, mengenai yang membuka URL itu; perbaiki dengan escape di server. DOM-based XSS: JavaScript di browser menaruh data dari URL lewat innerHTML, tanpa server ikut menyusun HTML-nya; perbaiki di JavaScript dengan textContent.