Mengenal XSS: Jenis, Cara Kerja, Simulasi dengan PHP Native, dan Cara Mencegahnya
Mengenal XSS: Jenis, Cara Kerja, Simulasi dengan PHP Native, dan Cara Mencegahnya
Ringkasan
Cross-Site Scripting atau XSS adalah salah satu vulnerability yang sangat sering dibahas dalam keamanan aplikasi web. Sederhananya, XSS terjadi ketika aplikasi mengambil data yang bisa dikendalikan attacker, kemudian browser korban memperlakukan data tersebut sebagai kode yang bisa dieksekusi, bukan sekadar data biasa.
Kelihatannya sederhana. Kita tinggal "escape HTML", selesai.
Sayangnya, kenyataannya tidak sesederhana itu.
XSS memiliki beberapa bentuk, terutama Reflected XSS, Stored XSS, dan DOM-based XSS. Masing-masing memiliki alur serangan yang berbeda. Selain itu, cara mencegah XSS juga bergantung pada context tempat data digunakan. Data yang dimasukkan ke HTML body tidak selalu bisa diproteksi dengan cara yang sama ketika data tersebut dimasukkan ke JavaScript, CSS, atau URL.
Pada artikel ini kita akan membahas XSS dari dasar sampai praktik. Kita akan membuat simulasi sederhana menggunakan PHP native, melihat bagaimana vulnerability terjadi, bagaimana memperbaikinya, kemudian membandingkannya dengan Laravel dan JavaScript modern.
Semua simulasi dilakukan secara lokal menggunakan payload demonstrasi yang hanya menampilkan alert().
1. Apa Itu XSS?
XSS adalah singkatan dari Cross-Site Scripting.
Secara sederhana:
XSS terjadi ketika attacker dapat memasukkan data yang akhirnya dieksekusi oleh browser sebagai bagian dari website yang dipercaya.
MDN menjelaskan XSS sebagai kondisi ketika attacker berhasil membuat website menjalankan kode berbahaya seolah-olah kode tersebut berasal dari website itu sendiri. Karena kode dijalankan dalam origin website target, dampaknya dapat mencakup manipulasi halaman, akses terhadap data yang tersedia di halaman atau local storage, dan pembuatan request menggunakan kredensial korban.
Misalnya kita memiliki website:
https://example.com
dan website tersebut memiliki form:
Komentar:
[________________________]
[ Submit ]
Developer mungkin membuat kode:
<?php
$comment = $_POST['comment'];
echo "<p>$comment</p>";
Sekilas terlihat normal.
Tetapi masalahnya adalah:
$_POST['comment']
berasal dari browser.
Artinya, user dapat mengontrol nilainya.
Misalnya user memasukkan:
<script>alert('XSS')</script>
Kemudian server menghasilkan:
<p>
<script>alert('XSS')</script>
</p>
Browser membaca <script> sebagai HTML yang berisi JavaScript.
Akibatnya:
Browser
↓
menerima HTML
↓
menemukan <script>
↓
menjalankan JavaScript
Muncullah:
XSS!
Masalah sebenarnya bukan alert()-nya.
alert() hanya digunakan sebagai demonstrasi bahwa JavaScript berhasil dieksekusi.
Dalam vulnerability nyata, JavaScript yang disisipkan dapat melakukan tindakan menggunakan hak akses korban terhadap aplikasi. Karena itu XSS dapat memiliki dampak serius, tergantung konteks dan hak akses korban.
2. Kenapa Disebut "Cross-Site Scripting"?
Nama XSS memang agak membingungkan.
Huruf pertama adalah Cross, tetapi sebenarnya vulnerability ini tidak selalu berarti script berjalan dari website lain.
Istilah tersebut berasal dari sejarah serangan web, tetapi sekarang digunakan secara lebih luas untuk injection ke dalam context halaman web. OWASP juga menjelaskan bahwa istilah XSS berkembang dari serangan awal yang berfokus pada data lintas situs menjadi istilah untuk berbagai bentuk injection ke dalam halaman web.
Karena itu lebih mudah memahaminya seperti ini:
Attacker-controlled data
↓
Website
↓
HTML / DOM
↓
Browser
↓
JavaScript dieksekusi
3. Hal yang Menyebabkan XSS
Secara umum XSS membutuhkan dua kondisi:
1. Aplikasi menerima input yang dapat dikendalikan attacker.
2. Input tersebut kemudian dimasukkan ke halaman tanpa perlindungan yang tepat.
MDN merangkum XSS dengan pola yang sama: aplikasi menerima input yang dapat dibuat attacker dan memasukkan input tersebut ke halaman tanpa memastikan bahwa input tersebut tidak menjadi executable code.
Contohnya:
$name = $_GET['name'];
echo "<h1>Hello $name</h1>";
Developer mungkin berpikir:
"Saya hanya menampilkan nama."
Browser tidak melihat niat developer.
Browser hanya melihat:
<h1>Hello Jendra</h1>
atau:
<h1>Hello <script>...</script></h1>
Jadi yang penting adalah hasil akhir HTML yang diterima browser.
4. Data Tidak Sama dengan Code
Inilah konsep paling penting untuk memahami XSS.
Misalnya kita ingin menampilkan:
Jendra
Maka HTML yang kita inginkan adalah:
<p>Jendra</p>
Tetapi kalau user memasukkan:
<script>alert('XSS')</script>
kita seharusnya ingin browser menampilkannya sebagai teks:
<script>alert('XSS')</script>
Bukan menjalankannya.
Jadi tujuan utama XSS prevention adalah:
INPUT
↓
DATA
↓
OUTPUT ENCODE
↓
BROWSER MELIHAT DATA
bukan:
INPUT
↓
HTML
↓
BROWSER MELIHAT CODE
5. Tiga Jenis XSS yang Paling Umum
Secara umum, kita akan bertemu tiga kategori utama:
1. Reflected XSS
2. Stored XSS
3. DOM-based XSS
OWASP membedakan Reflected dan Stored sebagai server-side injection, sedangkan DOM-based XSS terjadi pada sisi client/browser melalui manipulasi DOM yang tidak aman.
Mari kita bahas satu per satu.
6. Reflected XSS
Reflected XSS terjadi ketika payload datang melalui request dan kemudian langsung direfleksikan ke response tanpa penyandian yang tepat.
Contoh sederhana:
GET /search?q=hello
Server mungkin membuat:
<h1>Hasil pencarian untuk: hello</h1>
Sekarang bayangkan:
GET /search?q=<script>alert('XSS')</script>
Server melakukan:
echo $_GET['q'];
dan menghasilkan:
<h1>
Hasil pencarian untuk:
<script>alert('XSS')</script>
</h1>
Browser mengeksekusi JavaScript tersebut.
Itulah Reflected XSS.
7. Contoh Reflected XSS dengan PHP Native
Buat file:
search.php
Isi:
<?php
$query = $_GET['q'] ?? '';
echo "<h1>Hasil pencarian</h1>";
echo "<p>Kamu mencari: $query</p>";
Kemudian jalankan PHP local server:
php -S localhost:8000
Buka:
http://localhost:8000/search.php?q=Laravel
Hasilnya:
Hasil pencarian
Kamu mencari: Laravel
Sekarang coba URL dengan payload demonstrasi:
http://localhost:8000/search.php?q=%3Cscript%3Ealert('XSS')%3C%2Fscript%3E
Browser akan menampilkan:
XSS
Vulnerability terjadi karena:
echo "<p>Kamu mencari: $query</p>";
langsung memasukkan input ke HTML.
8. Memperbaiki Reflected XSS
Gunakan:
htmlspecialchars()
Contoh:
<?php
$query = $_GET['q'] ?? '';
$safeQuery = htmlspecialchars(
$query,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
echo "<h1>Hasil pencarian</h1>";
echo "<p>Kamu mencari: $safeQuery</p>";
Sekarang payload:
<script>alert('XSS')</script>
akan ditampilkan sebagai teks.
Browser kurang lebih akan menerima:
<p>
<script>alert('XSS')</script>
</p>
Browser melihat:
<script>alert('XSS')</script>
sebagai text content, bukan element <script>.
PHP mendokumentasikan htmlspecialchars() sebagai fungsi untuk mengubah karakter-karakter khusus HTML menjadi HTML entities. Fungsi tersebut adalah salah satu mekanisme penting untuk menyiapkan string sebelum dimasukkan ke context HTML.
9. Cara Kerja htmlspecialchars()
Misalnya:
$input = "<script>alert('XSS')</script>";
Kemudian:
echo htmlspecialchars(
$input,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
Hasilnya secara konsep menjadi:
<script>alert('XSS')</script>
Browser akhirnya menampilkan:
<script>alert('XSS')</script>
bukan menjalankan JavaScript tersebut.
Karakter yang memiliki arti khusus dalam HTML diubah menjadi entity. PHP mendokumentasikan bahwa htmlspecialchars() mengonversi special HTML characters menjadi HTML entities.
10. Stored XSS
Sekarang kita masuk ke jenis yang biasanya lebih mudah menyebabkan dampak berulang.
Stored XSS.
Alurnya:
Attacker
↓
Input
↓
Database
↓
Disimpan
↓
User lain membuka halaman
↓
Payload dibaca
↓
Browser menjalankan payload
Misalnya website mempunyai fitur komentar.
Database:
comments
id | user | comment
1 | Andi | Halo
2 | Budi | Bagus
Attacker memasukkan:
<script>alert('XSS')</script>
Developer menyimpan begitu saja:
$comment = $_POST['comment'];
$stmt = $pdo->prepare(
"INSERT INTO comments (comment) VALUES (?)"
);
$stmt->execute([$comment]);
Database sekarang memiliki:
id | comment
1 | Halo
2 | <script>alert('XSS')</script>
Lalu halaman komentar:
foreach ($comments as $comment) {
echo "<p>{$comment['comment']}</p>";
}
Saat user lain membuka halaman tersebut:
Database
↓
Stored payload
↓
HTML response
↓
Browser
↓
JavaScript executed
Itulah Stored XSS.
11. Simulasi Stored XSS dengan PHP + SQLite
Agar mudah dipraktikkan, kita bisa menggunakan SQLite.
Buat:
database.php
<?php
$db = new PDO('sqlite:database.sqlite');
$db->setAttribute(
PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION
);
$db->exec(
'CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
body TEXT NOT NULL
)'
);
Kemudian:
index.php
<?php
require 'database.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$comment = $_POST['comment'] ?? '';
$stmt = $db->prepare(
'INSERT INTO comments (body) VALUES (?)'
);
$stmt->execute([$comment]);
}
$comments = $db
->query('SELECT * FROM comments ORDER BY id DESC')
->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html>
<head>
<title>Comments</title>
</head>
<body>
<h1>Comments</h1>
<form method="POST">
<textarea
name="comment"
rows="5"
cols="50"
></textarea>
<br>
<button type="submit">
Submit
</button>
</form>
<hr>
<?php foreach ($comments as $comment): ?>
<div>
<?= $comment['body'] ?>
</div>
<?php endforeach; ?>
</body>
</html>
Perhatikan bagian:
<?= $comment['body'] ?>
Ini vulnerability.
Kita menyimpan data ke database menggunakan prepared statement, tetapi output-nya tidak di-escape.
12. Menguji Stored XSS
Masukkan komentar:
<script>alert('Stored XSS')</script>
Submit.
Kemudian refresh halaman.
Payload tersebut akan dieksekusi setiap kali komentar ditampilkan dengan cara yang rentan.
Untuk simulasi ini, kita hanya menggunakan:
alert()
agar efeknya terlihat.
13. Memperbaiki Stored XSS
Perbaikannya:
<?= htmlspecialchars(
$comment['body'],
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
) ?>
Sekarang komentar:
<script>alert('Stored XSS')</script>
ditampilkan sebagai teks.
Ini adalah contoh penting bahwa:
SQL Injection prevention dan XSS prevention adalah dua hal berbeda.
Kita sudah menggunakan:
PDO prepare()
untuk SQL Injection.
Tetapi kita tetap memerlukan:
htmlspecialchars()
untuk output HTML.
Security control harus sesuai dengan vulnerability yang ingin dicegah.
14. "Tapi Bukankah Validasi Input Sudah Cukup?"
Tidak.
Misalnya kita membuat:
if (strlen($comment) > 500) {
die('Comment too long');
}
Ini bukan XSS protection.
Begitu juga:
if (preg_match('/script/', $comment)) {
die('Invalid');
}
juga bukan pertahanan yang dapat diandalkan.
Kenapa?
Karena XSS bukan hanya soal kata:
<script>
Serangan dapat muncul melalui berbagai HTML/DOM context.
OWASP secara eksplisit menekankan bahwa input validation atau sanitization dapat membantu, tetapi pertahanan utama tetap harus berupa context-sensitive output encoding, sementara HTML sanitization diperlukan ketika aplikasi memang harus menerima HTML.
Jadi jangan membuat security seperti:
if input contains "<script>"
reject
lalu merasa masalah selesai.
15. DOM-Based XSS
Jenis ketiga adalah DOM-based XSS.
Ini sedikit berbeda.
Pada Reflected dan Stored XSS, server biasanya memasukkan data ke response.
Pada DOM XSS, data berbahaya dapat mengalir dari sumber di browser menuju DOM sink yang tidak aman tanpa harus terlebih dahulu dipantulkan oleh server dalam HTML response. OWASP menjelaskan DOM-based XSS sebagai client-side injection issue yang terjadi ketika data yang dikendalikan attacker digunakan oleh JavaScript untuk memperbarui DOM melalui API yang tidak aman.
Contoh klasik:
const name = location.hash.substring(1);
document.getElementById('output').innerHTML = name;
Kita memiliki URL:
http://localhost:8000/index.html#Jendra
JavaScript membaca:
Jendra
kemudian:
innerHTML = name;
Masalah muncul ketika hash berisi HTML.
16. Simulasi DOM XSS
Buat:
dom.html
<!DOCTYPE html>
<html>
<head>
<title>DOM XSS Demo</title>
</head>
<body>
<h1>Search</h1>
<div id="output"></div>
<script>
const value = location.hash.substring(1);
document.getElementById('output').innerHTML = value;
</script>
</body>
</html>
Jalankan:
php -S localhost:8000
Kemudian:
http://localhost:8000/dom.html#Hello
Tidak ada masalah.
Sekarang gunakan payload demonstrasi:
http://localhost:8000/dom.html#%3Cimg%20src=x%20onerror=alert('XSS')%3E
JavaScript mengambil nilai dari:
location.hash
lalu memasukkannya ke:
innerHTML
Browser kemudian memprosesnya sebagai HTML.
17. Kenapa innerHTML Berbahaya?
innerHTML bukan otomatis berbahaya.
Masalahnya adalah:
element.innerHTML = untrustedData;
Artinya kita mengatakan kepada browser:
"Interpretasikan data ini sebagai HTML."
Kalau data berasal dari user atau sumber yang dapat dimanipulasi attacker, ini menjadi dangerous sink.
MDN dan OWASP sama-sama menekankan pentingnya menghindari penggunaan API DOM yang menafsirkan data tidak tepercaya sebagai HTML ketika sebenarnya kita hanya ingin menampilkan teks.
18. Memperbaiki DOM XSS
Gunakan:
textContent
bukan:
innerHTML
Contoh:
const value = location.hash.substring(1);
document.getElementById('output').textContent = value;
Sekarang:
<img src=x onerror=alert('XSS')>
ditampilkan sebagai teks.
Browser tidak memprosesnya sebagai HTML element.
Ini adalah contoh bagus dari prinsip:
Gunakan API yang sesuai dengan jenis data yang ingin kamu tampilkan.
Kalau ingin menampilkan text:
textContent
Kalau memang membutuhkan HTML dari user:
sanitize
dengan sanitizer yang sesuai dan trusted.
19. Source dan Sink
Ketika mempelajari DOM XSS, dua istilah sangat penting:
Source
Sink
Source
Source adalah tempat data masuk.
Contoh:
location.href
location.search
location.hash
document.cookie
localStorage
Tidak semuanya otomatis berbahaya, tetapi data yang berasal dari sumber yang dapat dikendalikan attacker harus dianggap tidak tepercaya sampai dibuktikan sebaliknya.
Sink
Sink adalah API/tempat data digunakan.
Contohnya:
element.innerHTML = value;
atau operasi lain yang menyebabkan browser memperlakukan data sebagai executable markup/code.
Model sederhananya:
SOURCE
↓
untrusted data
↓
SINK
↓
browser interprets as HTML/code
↓
XSS
20. XSS Tidak Hanya script Tag
Ini salah satu hal yang perlu dipahami oleh developer pemula.
Kesalahan:
"Saya sudah memblok
<script>, berarti aman."
Belum tentu.
HTML memiliki banyak context.
Misalnya:
<div>VALUE</div>
berbeda dengan:
<input value="VALUE">
berbeda dengan:
<script>
const x = "VALUE";
</script>
berbeda dengan:
<a href="VALUE">
berbeda lagi dengan:
<style>
...
</style>
OWASP menekankan bahwa output encoding harus context-specific. HTML body, HTML attribute, JavaScript, CSS, dan URL memiliki kebutuhan encoding yang berbeda.
21. HTML Context
Misalnya:
$name = $_GET['name'];
echo "<p>$name</p>";
Gunakan HTML escaping:
echo '<p>' .
htmlspecialchars(
$name,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
) .
'</p>';
Ini adalah salah satu context yang paling umum.
22. HTML Attribute Context
Misalnya:
<input
type="text"
value="<?= $name ?>"
>
Jangan melakukan:
value="<?= $name ?>"
langsung.
Gunakan:
value="<?= htmlspecialchars(
$name,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
) ?>"
Karena quote juga mempunyai arti khusus dalam attribute.
23. JavaScript Context
Ini lebih rumit.
Misalnya:
<script>
const username = "<?= $name ?>";
</script>
Jangan menganggap:
htmlspecialchars()
selalu merupakan solusi yang benar untuk semua JavaScript context.
Ini karena HTML escaping dan JavaScript escaping adalah dua context berbeda.
OWASP menjelaskan bahwa setiap context memerlukan encoding yang tepat, dan variabel sebaiknya tidak dimasukkan sembarangan ke dalam JavaScript, HTML attribute, CSS, atau context berbahaya lainnya.
Alternatif yang lebih baik sering kali adalah memindahkan data ke JSON dengan mekanisme encoding yang sesuai daripada menyusun JavaScript menggunakan string concatenation secara manual.
Contohnya:
<script>
const data = <?= json_encode(
$name,
JSON_HEX_TAG |
JSON_HEX_AMP |
JSON_HEX_APOS |
JSON_HEX_QUOT
) ?>;
</script>
Tetap pertimbangkan context dan bagaimana data digunakan setelah masuk ke JavaScript.
24. URL Context
Misalnya:
<a href="<?= $url ?>">
Open
</a>
Ini tidak otomatis aman hanya karena kita melakukan:
htmlspecialchars($url)
Kita juga perlu memvalidasi scheme dan bentuk URL.
Contohnya aplikasi seharusnya tidak menerima URL yang secara semantik menggunakan scheme berbahaya lalu memasukkannya begitu saja ke href.
OWASP secara khusus membahas URL context dan menyarankan URL encoding serta validasi URL/scheme yang tepat.
25. CSS Context
Context seperti:
<style>
.box {
background: VALUE;
}
</style>
juga berbeda.
Secara umum:
Jangan memasukkan untrusted input langsung ke CSS.
Kalau memungkinkan, desain aplikasi sedemikian rupa agar user tidak perlu mengontrol raw CSS.
Semakin sedikit kita mengizinkan data user masuk ke dangerous context, semakin kecil attack surface.
26. Dangerous Context
OWASP secara eksplisit mengidentifikasi beberapa context sebagai dangerous, termasuk:
<script>...</script>
inline JavaScript event handler:
onclick="..."
CSS:
<style>...</style>
dan berbagai sink JavaScript yang dapat mengeksekusi atau menginterpretasikan string sebagai code.
Jadi prinsip praktisnya:
Jangan masukkan data tidak tepercaya ke context yang memang dirancang untuk mengeksekusi code.
27. Bedakan Escaping dan Sanitization
Ini sangat penting.
Escaping / Encoding
Tujuannya:
Membuat data tampil sebagai data, bukan sebagai markup/code.
Contoh:
<script>
menjadi:
<script>
Gunakan ketika kita ingin menampilkan teks.
Sanitization
Tujuannya:
Menghapus atau mempertahankan bagian tertentu dari HTML berdasarkan kebijakan yang diizinkan.
Misalnya website kita memang ingin user bisa menulis:
<b>Halo</b>
<i>Selamat datang</i>
Kita tidak bisa sekadar melakukan:
htmlspecialchars()
karena tag <b> dan <i> memang ingin dipertahankan.
Kita memerlukan HTML sanitizer dengan allowlist yang ketat.
OWASP menekankan bahwa HTML sanitization dibutuhkan ketika aplikasi memang harus menerima HTML dari user.
28. Jangan Membuat Sanitizer Sendiri
Misalnya developer berkata:
$input = str_replace(
'<script>',
'',
$input
);
Jangan.
Kemudian:
$input = strip_tags($input);
juga tidak boleh dianggap sebagai solusi universal.
Context HTML sangat kompleks.
Lebih baik:
Tidak menerima HTML
↓
output encode
atau:
Memang perlu HTML
↓
gunakan sanitizer yang dirancang untuk HTML
daripada membuat blacklist sendiri.
29. PHP Native: Pola yang Aman
Untuk aplikasi PHP native yang hanya menampilkan text:
function e(string $value): string
{
return htmlspecialchars(
$value,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
}
Kemudian:
echo '<p>' . e($name) . '</p>';
Untuk attribute:
echo '<input value="' . e($name) . '">';
Ini membuat kode lebih konsisten.
Daripada developer lupa satu kali:
echo $name;
kita bisa membuat helper.
Tetapi tetap ingat:
Helper ini bukan solusi universal untuk semua context.
Jangan menggunakan helper HTML escaping secara membabi buta untuk JavaScript, CSS, atau URL.
30. Laravel dan XSS
Framework modern seperti Laravel membantu mengurangi risiko XSS karena templating engine secara default melakukan escaping terhadap output variable.
Contoh Blade:
{{ $name }}
Secara umum, ini adalah cara yang aman untuk menampilkan data yang tidak dipercaya sebagai text.
Sebaliknya, developer perlu berhati-hati ketika menggunakan raw HTML output:
{!! $html !!}
Karena syntax tersebut meminta Blade untuk tidak melakukan escaping biasa.
Ini menjadi sangat penting ketika:
$html
berasal dari:
user input
database
CMS
API external
OWASP juga menekankan bahwa framework modern biasanya membantu mencegah XSS melalui auto-escaping, tetapi vulnerability tetap bisa muncul ketika developer menggunakan "escape hatches" seperti raw HTML rendering.
31. Contoh Laravel yang Rentan
Misalnya:
<div>
{!! $comment->body !!}
</div>
dan:
comment->body
berisi:
<script>alert('XSS')</script>
Maka raw HTML tersebut dapat masuk ke halaman tanpa escaping.
Untuk komentar biasa, gunakan:
<div>
{{ $comment->body }}
</div>
32. Kapan {!! !!} Boleh Digunakan?
Misalnya kita memiliki editor rich text.
User memang diperbolehkan memasukkan:
<p>Hello</p>
<strong>World</strong>
Kita mungkin ingin menyimpannya sebagai HTML.
Tetapi jangan langsung:
{!! $content !!}
tanpa sanitization.
Alurnya harus lebih seperti:
User HTML
↓
Validate
↓
Sanitize
↓
Store sanitized HTML
↓
Render trusted HTML
Dan sanitizer harus memiliki allowlist yang jelas.
33. React dan XSS
React juga mempunyai perlindungan default.
Misalnya:
function Profile({ name }) {
return <h1>{name}</h1>;
}
Kalau:
name = "<script>alert('XSS')</script>"
React akan merendernya sebagai text, bukan menjalankannya sebagai HTML.
Masalah muncul ketika developer menggunakan:
dangerouslySetInnerHTML
Contoh:
<div
dangerouslySetInnerHTML={{
__html: content
}}
/>
OWASP memasukkan dangerouslySetInnerHTML sebagai salah satu escape hatch yang perlu mendapat perhatian karena dapat membuka jalan ke XSS jika HTML yang dimasukkan tidak disanitasi.
34. DOM XSS dengan JavaScript Modern
Contoh buruk:
const value = userInput;
element.innerHTML = value;
Lebih aman:
element.textContent = value;
atau bila sebenarnya ingin membuat element:
const span = document.createElement('span');
span.textContent = value;
element.appendChild(span);
Ini mengurangi kemungkinan data diperlakukan sebagai HTML.
35. innerHTML Bukan Selalu Salah
Kita juga harus berhati-hati agar tidak jatuh ke kesimpulan:
"
innerHTMLadalah vulnerability."
Tidak.
innerHTML hanyalah sebuah API.
Masalahnya adalah ketika:
innerHTML
digabung dengan:
untrusted input
Contoh aman:
element.innerHTML = `
<strong>Hello</strong>
`;
Karena string HTML tersebut berasal dari code yang kita kontrol.
Masalah:
element.innerHTML = userInput;
Karena:
userInput = attacker-controlled
Jadi yang harus kita periksa adalah data flow.
36. Content Security Policy
Selain escaping dan sanitization, ada satu lapisan defense yang sangat penting:
Content Security Policy atau CSP.
CSP adalah HTTP security policy yang memberi tahu browser sumber konten atau script apa yang diperbolehkan. OWASP menjelaskan CSP sebagai defense-in-depth untuk membatasi eksekusi script dan loading content tertentu, termasuk membantu mengurangi dampak XSS.
Misalnya:
Content-Security-Policy:
default-src 'self';
script-src 'self';
Artinya secara umum:
resource default → hanya dari origin sendiri
script → hanya dari origin sendiri
Policy production yang sebenarnya harus disesuaikan dengan aplikasi.
37. CSP Bukan Pengganti Output Encoding
Jangan berpikir:
Saya sudah memakai CSP
→ XSS selesai.
Tidak.
OWASP secara eksplisit memperingatkan bahwa CSP sebaiknya digunakan sebagai defense in depth, bukan sebagai satu-satunya mekanisme untuk mencegah XSS.
Urutannya tetap:
1. Context-aware output encoding
2. Safe DOM APIs
3. Sanitization bila HTML memang diperlukan
4. CSP sebagai defense-in-depth
38. HTTP Security Headers
Selain CSP, aplikasi production biasanya juga perlu memperhatikan security headers lain.
Contohnya:
Content-Security-Policy
X-Content-Type-Options
Referrer-Policy
dan konfigurasi cookie:
Secure
HttpOnly
SameSite
Tetapi perlu dibedakan:
XSS prevention
dan:
session hardening
Cookie HttpOnly, misalnya, dapat membantu mencegah JavaScript membaca cookie tertentu, tetapi bukan berarti XSS-nya sendiri sudah diperbaiki.
XSS tetap dapat memungkinkan JavaScript berjalan dalam context aplikasi dan melakukan aksi yang dapat dilakukan halaman tersebut.
39. XSS dan HttpOnly
Misalnya session cookie menggunakan:
Set-Cookie:
session=abc123;
HttpOnly;
Secure;
SameSite=Lax
HttpOnly membantu mencegah JavaScript membaca cookie melalui API seperti:
document.cookie
Tetapi kalau aplikasi memiliki XSS:
attacker JavaScript
↓
berjalan dalam origin aplikasi
↓
melakukan request
maka HttpOnly bukan solusi menyeluruh.
Ini alasan XSS harus diperbaiki pada root cause.
OWASP juga menekankan bahwa pertahanan XSS harus berlapis dan tidak bergantung pada satu mekanisme saja.
40. XSS dan CSRF
Keduanya sering tertukar.
XSS
Attacker berhasil menjalankan code dalam context website.
CSRF
Browser korban dibuat melakukan request yang sebenarnya tidak diinginkan ke aplikasi tempat korban sudah terautentikasi.
CSRF dan XSS berbeda, tetapi berhubungan.
Yang cukup mengejutkan adalah:
XSS dapat mengalahkan banyak perlindungan CSRF.
OWASP secara eksplisit menyebut bahwa XSS dapat mengalahkan CSRF mitigations, meskipun aplikasi yang menggunakan cookie-based authentication tetap perlu menerapkan CSRF protection.
Jadi:
Ada CSRF token
tidak berarti:
Aplikasi aman dari XSS
41. XSS dan SQL Injection Itu Berbeda
Contoh SQL Injection:
HTTP input
↓
SQL query
↓
Database
Contoh XSS:
HTTP input
↓
HTML/DOM
↓
Browser
Jadi:
Prepared statement
melindungi database query.
Sedangkan:
Output encoding
melindungi HTML/DOM context.
Kita membutuhkan kedua jenis pertahanan.
42. Bagaimana Mencari XSS di Project PHP?
Jangan hanya mencari:
<script>
Cari data flow.
Misalnya:
$_GET
$_POST
$_REQUEST
$_COOKIE
database data
API response
uploaded metadata
URL fragment
localStorage
kemudian telusuri apakah datanya masuk ke:
echo
print
HTML template
innerHTML
outerHTML
insertAdjacentHTML
dangerouslySetInnerHTML
atau context berbahaya lainnya.
OWASP juga menyarankan security review dengan mencari semua jalur dari HTTP input menuju HTML output karena XSS dapat muncul melalui banyak tempat.
43. Contoh Audit PHP Sederhana
Misalnya project memiliki:
echo $_GET['q'];
Jelas perlu diperiksa.
Kemudian:
echo $user['name'];
juga perlu diperiksa.
Kemudian:
<?= $comment->body ?>
juga perlu diperiksa.
Tetapi jangan hanya melihat file tersebut.
Tanyakan:
Data ini asalnya dari mana?
Misalnya:
Database
tidak berarti:
trusted
Data database bisa saja awalnya berasal dari user.
44. "Data dari Database Berarti Aman?"
Tidak.
Contoh:
User
↓
Comment form
↓
Database
↓
Admin dashboard
Database hanya tempat penyimpanan.
Kalau attacker memasukkan:
<script>alert('XSS')</script>
dan data tersebut disimpan ke database, database sekarang menyimpan payload.
Kemudian:
Admin dashboard
↓
render raw HTML
↓
JavaScript executed
Bahkan targetnya bisa berubah.
Attacker mengirim komentar.
Yang terkena adalah admin.
Ini salah satu alasan Stored XSS perlu diperhatikan secara serius.
45. Blind XSS
Ada juga istilah Blind XSS.
Ini biasanya merujuk pada XSS yang payload-nya disimpan atau diproses pada suatu tempat, tetapi attacker tidak langsung melihat hasil eksekusinya.
Contoh konseptual:
Public user
↓
Feedback form
↓
Database
↓
Admin panel
↓
Admin membuka laporan
↓
XSS executes
Attacker mungkin tidak pernah membuka admin panel tersebut.
Karena itu vulnerability ini dapat sulit dideteksi hanya dengan pengujian manual sederhana.
Untuk pembelajaran, cukup pahami alurnya.
46. Self-XSS
Self-XSS adalah istilah untuk trik sosial di mana korban sendiri memasukkan script ke browser atau console karena ditipu oleh attacker.
Ini berbeda dari vulnerability XSS yang biasanya terjadi karena aplikasi web memproses untrusted input secara tidak aman.
Jangan menyamakan:
"Paste this code into DevTools"
dengan:
aplikasi memiliki XSS vulnerability
47. Bagaimana Cara Melakukan Testing XSS?
Untuk aplikasi milik sendiri atau lab lokal, kita dapat melakukan testing sederhana.
Mulai dari payload paling dasar:
<script>alert('XSS')</script>
Kemudian context HTML lain:
<img src=x onerror=alert('XSS')>
Gunakan hanya pada:
local environment
staging yang memang Anda berwenang menguji
CTF/lab
Tujuannya adalah mengetahui:
apakah input dieksekusi?
di mana?
melalui context apa?
Bukan mengeksfiltrasi data pengguna.
48. Browser DevTools Sangat Berguna
Ketika melakukan debugging XSS, buka:
DevTools
kemudian periksa:
Elements
Apakah data masuk ke:
<div>
<input>
<script>
Console
Apakah JavaScript berhasil dieksekusi?
Network
Bagaimana request terjadi?
Sources
Script berasal dari mana?
Untuk DOM XSS, kita juga dapat melihat:
Source
↓
JavaScript
↓
DOM sink
49. Gunakan Automated Scanner Sebagai Tambahan
Tools seperti:
OWASP ZAP
Burp Suite
dapat membantu melakukan security testing.
Tetapi jangan berpikir:
scanner tidak menemukan XSS
→ aplikasi pasti aman
Tidak.
Automated scanner hanya membantu.
OWASP juga menyebut tool scanning dapat membantu menemukan XSS, tetapi tidak dapat menggantikan code review dan pengujian menyeluruh.
50. Cara Membuat Mini Lab XSS
Kalau ingin benar-benar belajar, saya menyarankan struktur project seperti:
xss-lab/
├── reflected.php
├── stored.php
├── dom.html
├── safe.php
├── database.php
└── database.sqlite
Kemudian buat dua versi:
vulnerable
safe
Misalnya:
reflected-vulnerable.php
reflected-safe.php
dan:
dom-vulnerable.html
dom-safe.html
Dengan begitu kita bisa melihat perbedaannya secara langsung.
51. Lab Reflected XSS
Vulnerable
<?php
$name = $_GET['name'] ?? '';
echo "
<h1>Hello $name</h1>
";
Payload
<script>alert('XSS')</script>
Safe
<?php
$name = $_GET['name'] ?? '';
$safeName = htmlspecialchars(
$name,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
echo "
<h1>Hello $safeName</h1>
";
52. Lab Stored XSS
Vulnerable
echo $comment['body'];
Safe
echo htmlspecialchars(
$comment['body'],
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
Alurnya:
Vulnerable
Input
↓
DB
↓
Raw HTML
↓
Browser
↓
XSS
Safe:
Input
↓
DB
↓
Output encoding
↓
Browser
↓
Text
53. Lab DOM XSS
Vulnerable
const input = location.hash.substring(1);
document.querySelector('#output')
.innerHTML = input;
Safe
const input = location.hash.substring(1);
document.querySelector('#output')
.textContent = input;
Perubahannya hanya satu method.
Tetapi secara security impact sangat besar.
54. Jangan Hanya Melakukan Encoding di Input
Ini salah satu pola yang sering membingungkan.
Misalnya:
$comment = htmlspecialchars(
$_POST['comment']
);
kemudian simpan ke database.
Ada kemungkinan developer berpikir:
"Sudah aman."
Masalahnya adalah data bisa digunakan pada context lain.
Misalnya:
database
↓
HTML
berbeda dengan:
database
↓
JSON
atau:
database
↓
JavaScript
Karena itu secara umum lebih baik memahami output context dan melakukan encoding pada titik penggunaan, sambil menjaga validasi data yang memang menjadi aturan bisnis.
OWASP merekomendasikan output encoding sebagai prinsip utama dan menekankan pentingnya context yang tepat.
55. Validasi Tetap Penting
Bukan berarti validation tidak diperlukan.
Tetap lakukan validation.
Contohnya:
if (strlen($name) > 100) {
throw new Exception('Invalid name');
}
atau:
filter_var(
$email,
FILTER_VALIDATE_EMAIL
);
Tetapi fungsi validation berbeda dengan XSS encoding.
Validation menjawab:
Apakah data sesuai aturan bisnis?
Encoding menjawab:
Bagaimana data ini harus ditampilkan agar tidak dianggap sebagai code?
Dua-duanya penting.
56. Allowlist Lebih Baik daripada Blacklist
Misalnya field:
gender
hanya boleh:
male
female
other
Kita tidak perlu mencoba mencari semua input jahat.
Cukup:
$allowed = [
'male',
'female',
'other',
];
if (!in_array($gender, $allowed, true)) {
throw new InvalidArgumentException();
}
Untuk data dengan domain terbatas, allowlist sangat efektif.
Tetapi untuk field seperti:
description
comment
article body
yang memang berupa free-form text, output encoding tetap harus diterapkan.
57. Bagaimana dengan Rich Text Editor?
Ini kasus menarik.
Misalnya blog pribadi mempunyai editor:
Bold
Italic
Link
List
Image
Kita ingin menyimpan:
<p>Hello</p>
<strong>World</strong>
Kita tidak bisa menggunakan:
htmlspecialchars()
karena semua tag akan berubah menjadi text.
Maka kita membutuhkan:
HTML Sanitizer
dengan allowlist.
Contoh kebijakan:
Allowed tags:
p
br
strong
em
ul
ol
li
a
Allowed attributes:
href
dan kemudian validasi:
allowed URL schemes
serta penghapusan elemen/attribute berbahaya.
OWASP merekomendasikan sanitization ketika user memang diperbolehkan memasukkan HTML, bukan menggantungkan semuanya pada blacklist.
58. Content Security Policy dalam Laravel
Untuk aplikasi Laravel, CSP bisa diterapkan melalui middleware atau web server.
Contoh sederhana header:
$response->headers->set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'"
);
Tetapi policy nyata sering membutuhkan konfigurasi tambahan untuk:
CSS
images
fonts
API
CDN
analytics
iframe
WebSocket
Jadi jangan copy-paste policy secara membabi buta.
Mulai dari kebutuhan aplikasi.
59. CSP dengan Nginx
CSP juga dapat diterapkan di Nginx.
Misalnya:
add_header Content-Security-Policy
"default-src 'self'; script-src 'self'"
always;
Kemudian lakukan testing karena CSP yang terlalu ketat dapat memblok resource yang memang diperlukan aplikasi.
Untuk deployment production, sebaiknya CSP diperlakukan sebagai policy aplikasi, bukan sekadar satu baris konfigurasi yang ditempel tanpa memahami dependency frontend.
OWASP menekankan CSP harus disesuaikan dengan aplikasi dan digunakan sebagai defense-in-depth.
60. Trusted Types
Untuk aplikasi frontend yang kompleks, ada juga mekanisme browser bernama Trusted Types.
Trusted Types membantu memastikan data melewati transformation/sanitization yang ditentukan developer sebelum diberikan ke DOM API yang dapat mengeksekusi atau menginterpretasikan data. MDN mendokumentasikan Trusted Types sebagai salah satu mekanisme untuk membantu mengurangi client-side XSS.
Konsep sederhananya:
untrusted string
↓
trusted transformation
↓
dangerous DOM sink
Trusted Types menjadi lebih menarik untuk aplikasi frontend yang besar dengan banyak penggunaan DOM APIs.
Tetapi ini tetap merupakan lapisan tambahan.
Bukan pengganti desain aplikasi yang aman.
61. XSS di Vue, React, dan Framework Frontend Modern
Prinsip dasarnya sama.
Framework modern biasanya memiliki auto-escaping atau mekanisme yang mengurangi XSS, tetapi vulnerability bisa muncul ketika developer menggunakan API yang memang melewati proteksi.
Contoh:
React
dangerouslySetInnerHTML
Vue
v-html
JavaScript
innerHTML
jQuery
Manipulasi HTML dengan data yang tidak dipercaya.
Pattern-nya sama:
Framework protection
↓
escape hatch
↓
untrusted data
↓
XSS
OWASP secara khusus menyebut escape hatch seperti React dangerouslySetInnerHTML, Angular bypassSecurityTrustAs*, dan Lit unsafeHTML sebagai area yang perlu perhatian.
62. XSS dalam API
API JSON juga tidak otomatis kebal.
Misalnya API menghasilkan:
{
"name": "<script>alert('XSS')</script>"
}
JSON tersebut mungkin aman sebagai JSON.
Masalah terjadi ketika frontend melakukan:
output.innerHTML = data.name;
Jadi:
Backend API
=
mengirim data
sedangkan:
Frontend
=
menentukan bagaimana data digunakan
Jika frontend menggunakan unsafe sink, XSS dapat terjadi di browser.
63. JSON Bukan Sanitizer
Ini penting.
Developer mungkin berpikir:
json_encode()
berarti aman.
Tidak selalu.
json_encode() membuat data menjadi JSON.
Tetapi belum tentu aman kalau setelah itu data dimasukkan ke context yang salah.
OWASP juga menekankan bahwa output encoding bergantung pada context. Untuk JSON, response seharusnya diperlakukan sebagai application/json dan tidak secara sembarangan disisipkan sebagai HTML.
64. Testing Checklist XSS
Ketika mengembangkan fitur baru yang menerima user input, lakukan pemeriksaan:
[ ] Data berasal dari user?
[ ] Data berasal dari external API?
[ ] Data berasal dari database yang awalnya user-controlled?
[ ] Data dimasukkan ke HTML?
[ ] Data dimasukkan ke attribute?
[ ] Data dimasukkan ke JavaScript?
[ ] Data dimasukkan ke URL?
[ ] Data dimasukkan ke CSS?
[ ] Data masuk ke innerHTML?
[ ] Ada raw HTML rendering?
[ ] Ada rich text?
[ ] Ada sanitizer?
[ ] Ada CSP?
Ini jauh lebih berguna daripada sekadar:
"Sudah pakai framework."
65. Checklist Audit Laravel
Untuk project Laravel, saya biasanya akan mencari pattern seperti:
{!! $variable !!}
kemudian:
Html::raw(...)
atau custom helper yang menghasilkan raw HTML.
Di frontend:
dangerouslySetInnerHTML
v-html
innerHTML
outerHTML
insertAdjacentHTML
document.write
Kemudian telusuri source datanya.
Misalnya:
{!! $comment->body !!}
Pertanyaannya:
comment->body
berasal dari mana?
Kalau:
user input
harus diperiksa.
66. Kesalahan yang Sering Dilakukan Developer
Ada beberapa pola yang sering terjadi.
"Saya sudah pakai prepared statement."
Itu untuk SQL Injection.
Bukan XSS.
"Saya sudah validasi input."
Validation tidak menggantikan output encoding.
"Saya sudah menghapus <script>."
Blacklisting sederhana bukan solusi menyeluruh.
"Saya sudah menggunakan CSP."
CSP bukan pengganti root-cause fix.
"Datanya berasal dari database."
Database tidak otomatis membuat data menjadi trusted.
"Framework saya sudah aman."
Framework membantu, tetapi escape hatch masih dapat membuka vulnerability.
67. Cara Berpikir yang Benar tentang XSS
Daripada berpikir:
"Input mana yang jahat?"
Lebih baik bertanya:
"Apakah data tidak tepercaya pernah masuk ke executable context?"
Misalnya:
User input
↓
Database
↓
Blade {{ }}
Aman untuk text output karena Blade melakukan escaping secara default.
Tetapi:
User input
↓
Database
↓
Blade {!! !!}
harus diperiksa.
Atau:
User input
↓
API JSON
↓
React
↓
dangerouslySetInnerHTML
juga harus diperiksa.
Atau:
location.hash
↓
innerHTML
adalah candidate DOM XSS.
68. Model Mental yang Sederhana
Kalau ingin mengingat XSS untuk interview atau pekerjaan, gunakan model ini:
SOURCE
↓
UNTRUSTED DATA
↓
TRANSFORMATION
↓
SINK
↓
BROWSER
Contoh:
$_GET['q']
↓
untrusted
↓
langsung
↓
echo "<div>...</div>"
↓
HTML parser
↓
XSS
Versi aman:
$_GET['q']
↓
untrusted
↓
htmlspecialchars()
↓
HTML
↓
text
DOM:
location.hash
↓
untrusted
↓
innerHTML
↓
HTML parser
↓
XSS
Versi aman:
location.hash
↓
untrusted
↓
textContent
↓
text
69. Simulasi Lengkap: Vulnerable vs Secure
Mari kita bandingkan satu fitur.
Vulnerable
<?php
$name = $_GET['name'] ?? '';
?>
<h1>
Hello <?= $name ?>
</h1>
Problem:
Input
↓
direct output
↓
HTML parsing
Secure
<?php
$name = $_GET['name'] ?? '';
?>
<h1>
Hello
<?= htmlspecialchars(
$name,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
) ?>
</h1>
Sekarang:
Input
↓
HTML entity encoding
↓
HTML parser
↓
text
70. Contoh Security Review
Misalkan kita menemukan kode:
echo "<div class='profile'>";
echo "<h1>" . $user->name . "</h1>";
echo "<p>" . $user->bio . "</p>";
echo "</div>";
Kita jangan langsung menulis:
htmlspecialchars()
di semua tempat tanpa berpikir.
Tanyakan dulu:
name:
harus text?
bio:
text biasa atau rich text?
Kalau:
name = plain text
gunakan encoding.
Kalau:
bio = rich HTML
gunakan HTML sanitizer.
Ini contoh sederhana dari context-aware security.
71. Bagaimana XSS Masuk ke Production?
Biasanya vulnerability muncul bukan karena developer tidak tahu XSS sama sekali.
Sering kali penyebabnya adalah:
"Ah, ini cuma admin panel."
atau:
"Ini data dari database."
atau:
"Ini HTML dari rich text editor."
atau:
"Ini cuma internal API."
Security boundary berubah seiring aplikasi berkembang.
Fitur yang awalnya dianggap internal bisa saja menjadi accessible oleh role lain.
Karena itu setiap data flow harus diperiksa berdasarkan trust boundary.
72. XSS pada Admin Panel
Admin panel sebenarnya menarik.
Misalnya public user dapat mengirim:
support ticket
Admin membuka ticket:
/admin/tickets/123
Kalau ticket body dirender dengan raw HTML tanpa sanitization:
Public user
↓
Ticket
↓
Database
↓
Admin panel
↓
XSS
Maka target payload adalah browser admin.
Ini contoh kenapa Stored XSS dapat berdampak besar meskipun attacker sendiri hanya memiliki akun biasa.
73. Bagaimana Menentukan Severity XSS?
Jangan hanya berkata:
"XSS selalu critical."
Tidak sesederhana itu.
Dampaknya tergantung:
siapa yang terkena
context
role korban
session model
data yang dapat diakses
aksi yang dapat dilakukan
persistensi
batasan browser/security controls
XSS pada area publik dan XSS yang hanya dapat dijalankan pada akun sendiri dapat memiliki dampak yang berbeda.
Untuk assessment formal, severity harus mengikuti konteks aplikasi dan threat model.
74. Defense in Depth
Pertahanan terbaik bukan satu trik.
Lebih baik:
XSS Defense
┌──────────────────────┐
│ Context-aware output │
│ encoding │
└───────────┬──────────┘
│
┌──────────┴──────────┐
│ │
v v
Safe DOM APIs Sanitization
│ │
└──────────┬──────────┘
v
CSP
│
v
Secure Cookies
│
v
Security Testing
Tidak semua proyek membutuhkan seluruh lapisan dalam tingkat yang sama, tetapi prinsipnya adalah defense in depth. OWASP secara konsisten merekomendasikan kombinasi kontrol, bukan satu pertahanan tunggal.
75. Apakah WAF Bisa Mengatasi XSS?
WAF dapat membantu mendeteksi atau memblokir beberapa pola serangan.
Tetapi:
WAF ≠ XSS fix
OWASP menjelaskan bahwa WAF tidak dapat diandalkan sebagai pertahanan utama XSS, karena rule-based filtering dapat dilewati dan tidak menyelesaikan root cause di aplikasi.
Jadi:
Application security
+
WAF
+
CSP
lebih masuk akal daripada:
WAF saja
76. Bagaimana Belajar XSS dengan Benar?
Daripada menghafalkan banyak payload, lebih baik pahami tiga hal:
Pertama: Source
Dari mana data datang?
GET
POST
Cookie
Database
URL
Hash
localStorage
API
Kedua: Context
Data dimasukkan ke mana?
HTML
Attribute
JavaScript
URL
CSS
DOM
Ketiga: Sink
API apa yang digunakan?
echo
template
innerHTML
dangerouslySetInnerHTML
v-html
Kalau ketiga hal ini dipahami, XSS menjadi jauh lebih mudah dianalisis.
77. Ringkasan Tiga Jenis XSS
Jenis | Payload berada di mana? | Contoh alur |
|---|---|---|
Reflected XSS | Request → response | URL → server → HTML → browser |
Stored XSS | Database/storage | Input → DB → HTML → browser |
DOM XSS | Browser/DOM | URL → JS → DOM → browser |
Perbedaan utamanya bukan sekadar "payload disimpan atau tidak", tetapi di mana injection terjadi dan bagaimana browser akhirnya memproses data tersebut.
78. Ringkasan Mitigasi
Untuk aplikasi PHP:
htmlspecialchars(
$value,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
Untuk DOM:
textContent
daripada:
innerHTML
Untuk rich HTML:
HTML Sanitizer
Untuk framework:
gunakan auto-escaping default
hindari raw HTML APIs
Untuk browser defense:
CSP
Untuk assessment:
code review
DAST
SAST
penetration testing
Dan semuanya tetap harus disesuaikan dengan context aplikasi.
79. Checklist Praktis Sebelum Production
Sebelum aplikasi PHP/Laravel masuk production, minimal cek:
[ ] Semua user input divalidasi sesuai aturan bisnis
[ ] HTML output di-escape sesuai context
[ ] Raw HTML rendering diaudit
[ ] Rich HTML disanitasi
[ ] innerHTML usage diaudit
[ ] dangerouslySetInnerHTML usage diaudit
[ ] v-html usage diaudit
[ ] URL input divalidasi
[ ] JavaScript context diaudit
[ ] CSP dipertimbangkan
[ ] Cookie diberi Secure/HttpOnly/SameSite sesuai kebutuhan
[ ] XSS test dilakukan pada fitur input
[ ] Admin panel ikut diuji
[ ] Error page tidak memantulkan input secara raw
[ ] API/frontend data flow diperiksa
80. Kesimpulan
XSS pada dasarnya adalah masalah trust boundary antara data dan code.
User seharusnya bisa mengirim:
Hello
dan aplikasi harus memastikan browser melihat:
Hello
sebagai data.
Masalah muncul ketika attacker mengirim:
<script>alert('XSS')</script>
dan aplikasi secara tidak sengaja membuat browser melihatnya sebagai:
CODE
Dari sini kita bisa memahami tiga jenis utama:
Reflected XSS
Stored XSS
DOM-based XSS
Kemudian pertahanan utamanya:
Context-aware output encoding
Safe DOM APIs
HTML sanitization ketika memang dibutuhkan
CSP sebagai defense-in-depth
Secure framework practices
Security testing
Untuk PHP native, fungsi sederhana seperti:
htmlspecialchars()
sangat penting ketika kita menampilkan text ke HTML. PHP mendokumentasikan fungsi tersebut sebagai mekanisme untuk mengubah special HTML characters menjadi HTML entities.
Untuk JavaScript, gunakan API yang memperlakukan input sebagai text ketika memang kita hanya ingin menampilkan text:
element.textContent = input;
bukan:
element.innerHTML = input;
Untuk Laravel, manfaatkan escaping default Blade:
{{ $value }}
dan berhati-hati terhadap:
{!! $value !!}
Untuk React, hati-hati dengan:
dangerouslySetInnerHTML
dan pada aplikasi modern, pertimbangkan CSP serta mekanisme browser seperti Trusted Types sebagai lapisan tambahan. OWASP dan MDN sama-sama menekankan bahwa framework membantu, tetapi developer tetap harus memahami escape hatch dan dangerous DOM/API contexts.
Yang paling penting, jangan belajar XSS dengan menghafalkan payload.
Belajarlah membaca data flow:
SOURCE
↓
Untrusted Input
↓
Processing
↓
SINK
↓
Browser
↓
Execute / Render
Kalau data berasal dari sumber yang tidak terpercaya dan akhirnya masuk ke context executable tanpa encoding atau sanitization yang tepat, di situlah alarm security harus berbunyi.
Dan kalau ingin mempraktikkannya, buatlah lab lokal.
Buat:
reflected.php
stored.php
dom.html
kemudian bandingkan:
vulnerable
dengan:
secure
Gunakan payload demonstrasi seperti:
<script>alert('XSS')</script>
atau:
<img src=x onerror=alert('XSS')>
hanya di lingkungan yang Anda miliki atau memang Anda berwenang mengujinya.
Dari eksperimen kecil tersebut, konsep XSS biasanya jauh lebih mudah dipahami daripada hanya membaca definisinya.
Referensi
OWASP — Cross Site Scripting Prevention Cheat Sheet
Membahas output encoding, HTML sanitization, dangerous contexts, framework protections, CSP, dan pendekatan defense-in-depth.MDN — Cross-Site Scripting (XSS)
Menjelaskan pengertian XSS, same-origin context, contoh serangan, dan mekanisme pertahanan.OWASP — DOM Based XSS Prevention Cheat Sheet
Membahas perbedaan XSS server-side dan DOM-based XSS, termasuk source dan sink pada browser.PHP Manual — htmlspecialchars()
Dokumentasi resmi PHP untuk HTML entity encoding.OWASP — Content Security Policy Cheat Sheet
Membahas CSP sebagai defense-in-depth untuk membatasi script dan resource yang dapat dijalankan browser.MDN — Trusted Types API
Dokumentasi Trusted Types untuk membantu melindungi DOM dari penggunaan data tidak tepercaya pada sink yang berbahaya.OWASP — Cross-Site Request Forgery Prevention
Menjelaskan hubungan XSS dengan CSRF dan mengapa CSRF protection tidak menggantikan XSS protection.
Komentar