Pembuatan skrip lintas situs berbasis DOM (DOM XSS) terjadi saat data dari sumber yang dikontrol pengguna (seperti nama pengguna, atau URL pengalihan yang diambil dari fragmen URL) mencapai sink, yang merupakan fungsi seperti eval()
atau setter properti seperti .innerHTML
yang dapat mengeksekusi kode JavaScript arbitrer.
XSS DOM adalah salah satu kerentanan keamanan web yang paling umum, dan tim dev sering kali secara tidak sengaja memasukkannya ke dalam aplikasi mereka. Trusted Types memberi Anda alat untuk menulis, meninjau keamanan, dan menjaga aplikasi bebas dari kerentanan XSS DOM dengan membuat fungsi web API yang berbahaya menjadi aman secara default. Trusted Types tersedia sebagai polyfill untuk browser yang belum mendukungnya.
Latar belakang
Selama bertahun-tahun, XSS DOM telah menjadi salah satu kerentanan keamanan web yang paling umum dan berbahaya.
Ada dua jenis pembuatan skrip lintas situs. Beberapa kerentanan XSS disebabkan oleh kode sisi server yang membuat kode HTML yang membentuk situs secara tidak aman. Yang lain memiliki penyebab utama di klien, tempat kode JavaScript memanggil fungsi berbahaya dengan konten yang dikontrol pengguna.
Untuk mencegah XSS sisi server, jangan buat HTML dengan menggabungkan string. Gunakan library template dengan pelepasan otomatis yang aman dan kontekstual, serta Kebijakan Keamanan Konten berbasis nonce untuk mitigasi bug tambahan.
Sekarang browser juga dapat membantu mencegah XSS berbasis DOM sisi klien dengan menggunakan Trusted Types.
Pengantar API
Trusted Types berfungsi dengan mengunci fungsi sink yang berisiko berikut. Anda mungkin sudah mengenali beberapa di antaranya, karena vendor browser dan framework web sudah mengarahkan Anda untuk tidak menggunakan fitur ini karena alasan keamanan.
- Manipulasi skrip:
<script src>
dan menyetel konten teks<script>
elemen. - Membuat HTML dari string:
- Mengeksekusi konten plugin:
- Kompilasi kode JavaScript runtime:
eval
setTimeout
setInterval
new Function()
Trusted Types mengharuskan Anda memproses data sebelum meneruskannya ke fungsi sink ini. Penggunaan string saja akan gagal, karena browser tidak tahu apakah data dapat dipercaya:
anElement.innerHTML = location.href;
Untuk menandakan bahwa data diproses dengan aman, buat objek khusus - Trusted Type.
anElement.innerHTML = aTrustedHTML;
TrustedHTML
untuk tujuan yang mengharapkan cuplikan HTML. Ada juga objek
TrustedScript
dan TrustedScriptURL
untuk sink
sensitif lainnya.
Trusted Types secara signifikan mengurangi area serangan XSS DOM aplikasi Anda. Hal ini menyederhanakan peninjauan keamanan, dan memungkinkan Anda menerapkan pemeriksaan keamanan berbasis jenis yang dilakukan saat mengompilasi, melakukan linting, atau memaketkan kode Anda saat runtime, di browser.
Cara menggunakan Trusted Types
Mempersiapkan laporan pelanggaran Kebijakan Keamanan Konten
Anda dapat men-deploy pengumpul laporan, seperti reporting-api-processor atau go-csp-collector open source, atau menggunakan salah satu alternatif komersial. Anda juga dapat menambahkan logging kustom dan men-debug pelanggaran di browser menggunakan ReportingObserver:
const observer = new ReportingObserver((reports, observer) => {
for (const report of reports) {
if (report.type !== 'csp-violation' ||
report.body.effectiveDirective !== 'require-trusted-types-for') {
continue;
}
const violation = report.body;
console.log('Trusted Types Violation:', violation);
// ... (rest of your logging and reporting logic)
}
}, { buffered: true });
observer.observe();
atau dengan menambahkan pemroses peristiwa:
document.addEventListener('securitypolicyviolation',
console.error.bind(console));
Menambahkan header CSP hanya laporan
Tambahkan header Respons HTTP berikut ke dokumen yang ingin Anda migrasikan ke Trusted Types:
Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
Sekarang semua pelanggaran dilaporkan ke //my-csp-endpoint.example
, tetapi
situs tetap berfungsi. Bagian berikutnya menjelaskan cara kerja
//my-csp-endpoint.example
.
Mengidentifikasi pelanggaran Jenis Tepercaya
Mulai sekarang, setiap kali Trusted Types mendeteksi pelanggaran, browser akan mengirimkan
laporan ke report-uri
yang dikonfigurasi. Misalnya, saat aplikasi Anda
meneruskan string ke innerHTML
, browser akan mengirim laporan berikut:
{
"csp-report": {
"document-uri": "https://my.url.example",
"violated-directive": "require-trusted-types-for",
"disposition": "report",
"blocked-uri": "trusted-types-sink",
"line-number": 39,
"column-number": 12,
"source-file": "https://my.url.example/script.js",
"status-code": 0,
"script-sample": "Element innerHTML <img src=x"
}
}
Hal ini menyatakan bahwa di https://my.url.example/script.js
pada baris 39, innerHTML
dipanggil dengan string yang dimulai dengan <img src=x
. Informasi ini akan membantu Anda mempersempit bagian kode yang mungkin menyebabkan XSS DOM dan perlu diubah.
Memperbaiki pelanggaran
Ada beberapa opsi untuk memperbaiki pelanggaran Trusted Type. Anda dapat menghapus kode yang melanggar, menggunakan library, membuat kebijakan Trusted Type atau, sebagai upaya terakhir, membuat kebijakan default.
Menulis ulang kode yang melanggar
Ada kemungkinan kode yang tidak sesuai tidak diperlukan lagi, atau dapat ditulis ulang tanpa fungsi yang menyebabkan pelanggaran:
el.textContent = ''; const img = document.createElement('img'); img.src = 'xyz.jpg'; el.appendChild(img);
el.innerHTML = '<img src=xyz.jpg>';
Menggunakan library
Beberapa library sudah menghasilkan Trusted Types yang dapat Anda teruskan ke fungsi sink. Misalnya, Anda dapat menggunakan DOMPurify untuk membersihkan cuplikan HTML, menghapus payload XSS.
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});
DOMPurify mendukung Trusted Types
dan menampilkan HTML yang telah di-sanitasi yang di-wrap dalam objek TrustedHTML
sehingga browser
tidak menghasilkan pelanggaran.
Membuat kebijakan Trusted Type
Terkadang Anda tidak dapat menghapus kode yang menyebabkan pelanggaran, dan tidak ada pustaka untuk membersihkan nilai dan membuat Trusted Type untuk Anda. Dalam kasus tersebut, Anda dapat membuat objek Trusted Type sendiri.
Pertama, buat kebijakan. Kebijakan adalah factory untuk Trusted Types yang menerapkan aturan keamanan tertentu pada inputnya:
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
createHTML: string => string.replace(/\</g, '<')
});
}
Kode ini membuat kebijakan bernama myEscapePolicy
yang dapat menghasilkan objek TrustedHTML
menggunakan fungsi createHTML()
-nya. Aturan yang ditentukan akan melakukan escape HTML pada karakter <
untuk mencegah pembuatan elemen HTML baru.
Gunakan kebijakan seperti ini:
const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML); // true
el.innerHTML = escaped; // '<img src=x onerror=alert(1)>'
Menggunakan kebijakan default
Terkadang Anda tidak dapat mengubah kode yang bermasalah, misalnya, jika Anda memuat library pihak ketiga dari CDN. Dalam hal ini, gunakan kebijakan default:
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
trustedTypes.createPolicy('default', {
createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
});
}
Kebijakan bernama default
digunakan di mana pun string digunakan di sink yang hanya menerima Trusted Type.
Beralih ke penerapan Kebijakan Keamanan Konten
Jika aplikasi Anda tidak lagi menghasilkan pelanggaran, Anda dapat mulai menerapkan Trusted Types:
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
Sekarang, seberapa pun kompleksnya aplikasi web Anda, satu-satunya hal yang dapat memunculkan kerentanan XSS DOM adalah kode di salah satu kebijakan Anda, dan Anda dapat mengamankannya lebih lanjut dengan membatasi pembuatan kebijakan.