Mengenal XSS: Jenis, Cara Kerja, Simulasi dengan PHP Native, dan Cara Mencegahnya

Admin
0
Ukuran Font
Kecil Besar

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>
    &lt;script&gt;alert('XSS')&lt;/script&gt;
</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:

&lt;script&gt;alert(&#039;XSS&#039;)&lt;/script&gt;

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:

&lt;script&gt;

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:

"innerHTML adalah 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

  1. OWASP — Cross Site Scripting Prevention Cheat Sheet
    Membahas output encoding, HTML sanitization, dangerous contexts, framework protections, CSP, dan pendekatan defense-in-depth.

  2. MDN — Cross-Site Scripting (XSS)
    Menjelaskan pengertian XSS, same-origin context, contoh serangan, dan mekanisme pertahanan.

  3. OWASP — DOM Based XSS Prevention Cheat Sheet
    Membahas perbedaan XSS server-side dan DOM-based XSS, termasuk source dan sink pada browser.

  4. PHP Manual — htmlspecialchars()
    Dokumentasi resmi PHP untuk HTML entity encoding.

  5. OWASP — Content Security Policy Cheat Sheet
    Membahas CSP sebagai defense-in-depth untuk membatasi script dan resource yang dapat dijalankan browser.

  6. MDN — Trusted Types API
    Dokumentasi Trusted Types untuk membantu melindungi DOM dari penggunaan data tidak tepercaya pada sink yang berbahaya.

  7. OWASP — Cross-Site Request Forgery Prevention
    Menjelaskan hubungan XSS dengan CSRF dan mengapa CSRF protection tidak menggantikan XSS protection.

Komentar

Belum ada komentar. Jadilah yang pertama menulis komentar!