flawopen.com/xss/Javascript
XSS JavaScript: amankan DOM, React, dan Express
Gunakan API teks; sanitasi HTML, validasi URL, dan terapkan Trusted Types untuk DOM/React (CWE-79).
Bayangkan panel aba-aba teater. Jika pengatur panggung menganggap setiap catatan penonton sebagai instruksi panggung, catatan itu dapat menambahkan adegan tak terduga; jika ditampilkan sebagai dialog biasa, isinya tetap teks. Di browser, innerHTML dan React dangerouslySetInnerHTML meminta DOM mengurai teks sebagai markup, sedangkan textContent menampilkannya secara harfiah. Escape teks biasa, sanitasi hanya sedikit HTML yang memang diizinkan, dan validasi skema URL sebelum membuat tautan.
Konsep Kunci & Istilah
innerHTML vs textContent- Menetapkan nilai ke
innerHTML,outerHTML, atauinsertAdjacentHTMLmembuat browser mengurai string sebagai HTML, sehingga<img onerror>di dalamnya menjadi elemen aktif.textContentdancreateTextNodehanya menyisipkan teks; tidak ada bagian nilai yang diurai sebagai markup. Template literals in res.send()res.send()di Express mengirim string sebagaitext/htmlsecara bawaan. Template literal mengevaluasi${req.query.q}sebelum dikirim, sehingga respons berisi apa pun yang dimasukkan pengguna ke query string.req.query.qjuga bisa berupa array atau objek, jadi periksa tipenya.dangerouslySetInnerHTML + DOMPurify- React meng-escape teks di JSX secara otomatis;
dangerouslySetInnerHTMLadalah pengecualian eksplisitnya. Lewatkan nilai melaluiDOMPurify.sanitize(), yang menguraikannya dengan parser browser itu sendiri dan membuang skrip, event handler, serta URL berbahaya sebelum React menyisipkannya. javascript: URLs in href- JSX meng-escape nilai atribut, tetapi
href="javascript:..."adalah URL valid yang menjalankan kode saat diklik. Urai dengannew URL()dan hanya izinkanhttp:danhttps:; parser URL membuang spasi dan tab di awal yang lolos dari pemeriksaan prefiks sederhana. Trusted Types- Dengan direktif CSP
require-trusted-types-for 'script', browser yang mendukung menolak string biasa di sink sepertiinnerHTMLdan hanya menerima objekTrustedHTMLyang dibuat oleh policy yang Anda definisikan, biasanya policy yang memanggil DOMPurify. Sink baru tidak bisa menyelinap tanpa ketahuan.
Pemetaan kerangka kerja
- Referensi CWE untuk kelemahan yang dijelaskan dalam panduan ini: CWE-79
- OWASP Top 10:2025 — A05:2025 Injection
Alur Serangan Langkah demi Langkah
Input sampai ke sink
Kueri pencarian, field profil yang dimuat dari API, atau tautan yang disimpan pengguna lain datang sebagai string dan diberikan ke innerHTML, dangerouslySetInnerHTML, href, atau res.send().
Nilai berubah menjadi markup
Dengan nilai uji yang tidak berbahaya <img src=x onerror=alert(1)>, innerHTML dan res.send() menghasilkan elemen <img> sungguhan; tautan tersimpan javascript:alert(1) menjadi skrip yang bisa diklik.
Browser menjalankannya
Gambar gagal dimuat dan memicu onerror, atau pengguna mengklik tautan. Kode langsung berjalan tanpa tindakan lanjutan dari penyerang.
Skrip bertindak sebagai situs
Skrip berjalan dengan origin halaman: ia bisa membaca DOM, memanggil API situs dengan cookie korban atau token di localStorage, dan mengubah apa yang dilihat pengguna.
Kode Sumber: Rentan vs Aman
// profile.jsx: setiap fungsi di bawah membiarkan string diurai sebagai HTML
import express from 'express';
const app = express();
// Express: nilai query ditaruh di HTML sebelum dikirim
app.get('/search', (req, res) => {
res.send(`<h1>Results for ${req.query.q}</h1>`);
});
// innerHTML mengurai name sebagai markup
export function showGreeting(el, name) {
el.innerHTML = `Hello, ${name}`;
}
// React menyisipkan HTML persis seperti yang diterima
export function Bio({ html }) {
return <div className="bio" dangerouslySetInnerHTML={{ __html: html }} />;
}
// URL javascript: berjalan saat tautan diklik
export function Website({ url }) {
return <a href={url}>Website</a>;
}
// profile.jsx: teks tetap teks; HTML dan URL diperiksa lebih dulu
import DOMPurify from 'dompurify';
import escapeHtml from 'escape-html';
import express from 'express';
const app = express();
// hanya string yang diterima, dan di-escape sebagai HTML
app.get('/search', (req, res) => {
const q = typeof req.query.q === 'string' ? req.query.q : '';
res.send(`<h1>Results for ${escapeHtml(q)}</h1>`);
});
// textContent tidak pernah mengurai markup
export function showGreeting(el, name) {
el.textContent = `Hello, ${name}`;
}
// DOMPurify membuang skrip dan event handler sebelum disisipkan
export function Bio({ html }) {
return <div className="bio" dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />;
}
// hanya URL http dan https yang tersisa; sisanya menjadi #
function safeHref(url) {
try {
const parsed = new URL(url, window.location.origin);
return ['http:', 'https:'].includes(parsed.protocol) ? parsed.href : '#';
} catch {
return '#';
}
}
export function Website({ url }) {
return <a href={safeHref(url)}>Website</a>;
}
Daftar Periksa Penguatan Sistem Rekayasa
- ✓Aktifkan
react/no-dangerdaneslint-plugin-no-unsanitizeddi CI, dan blokirinnerHTML,outerHTML,insertAdjacentHTML, sertadocument.writedengan nilai non-literal. - ✓Gunakan
textContentuntuk teks; setiap HTML yang harus ditampilkan dilewatkan keDOMPurify.sanitize()di titik penyisipan. - ✓Validasi setiap URL dari pengguna dengan
new URL()dan allowlisthttp:/https:sebelum sampai kehref,src, atauwindow.location. - ✓Di Express, render lewat view engine yang meng-escape otomatis atau
escape-html, dan tolak parameter query yang bukan string. - ✓Kirim CSP dengan
script-srcberbasis nonce, tanpa'unsafe-inline', dan denganrequire-trusted-types-for 'script'; simpan token sesi di cookieHttpOnly, bukan dilocalStorage.