Trusted Types Menempatkan Sink Injeksi DOM di Balik Policy

Cross-site scripting berbasis DOM sering muncul pada langkah terakhir sebuah aliran data. Sebuah nilai bergerak di dalam kode aplikasi sebagai string biasa, lalu mencapai API yang menafsirkannya sebagai HTML, script, atau URL script. Batas berbahayanya bukan string itu sendiri, melainkan saat string tersebut masuk ke injection sink.

Trusted Types mengubah batas itu. Pada browser yang mendukungnya, aplikasi dapat mewajibkan sink yang tercakup menerima objek seperti TrustedHTML, TrustedScript, atau TrustedScriptURL, bukan string mentah. Objek tersebut dibuat melalui policy yang ditentukan aplikasi.

Kontrol ini tidak menentukan HTML mana yang aman. Trusted Types menyediakan titik enforcement tempat aplikasi harus membuat keputusan tersebut secara eksplisit.

Enforcement berada di CSP

Membuat policy Trusted Types saja tidak mencegah kode lain menetapkan string langsung ke sink. Enforcement berasal dari Content Security Policy.

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types app-html;

Directive require-trusted-types-for 'script' mewajibkan Trusted Types pada sink injeksi DOM XSS yang tercakup. Directive trusted-types app-html membatasi pembuatan policy pada nama yang tercantum.

Saat enforcement aktif, assignment string langsung ke sink yang tercakup dapat gagal dengan TypeError:

const target = document.querySelector("#output");
target.innerHTML = userControlledString;

Kode aplikasi kemudian membuat policy dan meneruskan nilai bertipe:

const policy = trustedTypes.createPolicy("app-html", {
  createHTML(value) {
    return sanitizeHTML(value);
  },
});

const target = document.querySelector("#output");
target.innerHTML = policy.createHTML(userControlledString);

sanitizeHTML() pada contoh ini adalah logika yang disediakan aplikasi. Trusted Types tidak menyediakan sanitizer dan tidak membuktikan bahwa transformasi sebuah policy sudah benar.

Kode policy menjadi batas keamanan yang sempit

Tanpa enforcement, sanitasi dapat tersebar di banyak call site. Satu jalur mungkin membersihkan nilai, sementara jalur lain mencapai sink yang sama secara langsung. Trusted Types membuat perbedaan itu terlihat melalui tipe yang diterima sink.

Sebuah policy dapat memusatkan transformasi:

input
  |
  +--> pemrosesan aplikasi biasa
  |
  v
batas policy
  |
  +--> validasi / sanitasi
  |
  v
TrustedHTML
  |
  v
sink HTML

Struktur ini hanya berguna jika fungsi policy ketat. Policy yang sekadar mengembalikan input akan mengubah string sembarang menjadi objek tepercaya tanpa mengurangi risiko.

Karena itu, pembuatan policy layak mendapat pemeriksaan yang sama seperti batas privileged lainnya. Directive CSP trusted-types mempersempit policy bernama yang boleh dibuat, sehingga kode lain lebih sulit membuat policy permisif yang tidak direncanakan.

TrustedHTML bukan sertifikat keamanan umum

Objek TrustedHTML mencatat bahwa sebuah nilai melewati policy yang dapat menghasilkan TrustedHTML. Makna keamanannya bergantung pada policy tersebut.

Policy dapat membersihkan HTML, melakukan escaping data, memilih konten dari kumpulan template tetap, atau menolak input yang tidak sesuai aturan khusus aplikasi. Pilihan itu memiliki semantik berbeda. Browser menegakkan batas tipe, bukan kualitas transformasinya.

Perbedaan ini penting: Trusted Types tidak membuat markup sembarang menjadi aman. Kontrol ini memindahkan otoritas untuk membuat nilai yang kompatibel dengan sink ke kode policy yang eksplisit.

Prinsip yang sama berlaku untuk TrustedScript dan TrustedScriptURL. Keberadaan tipe tersebut menandai jalur konstruksi terkontrol untuk nilai yang dikonsumsi sink terkait; tipe itu tidak menetapkan bahwa perilaku script yang dihasilkan bersifat aman.

Default policy cocok untuk migrasi, bukan batas akhir

Trusted Types mendukung policy bernama default. Saat enforcement aktif, default policy dapat menerima string yang masih dikirim kode lama ke sink yang tercakup dan mengembalikan nilai tepercaya yang sesuai.

Mekanisme ini dapat mengurangi gangguan selama migrasi, tetapi juga mengubah pola kegagalan. Alih-alih langsung memperlihatkan setiap assignment string mentah yang tersisa, default policy dapat mengonversi assignment tersebut.

Migrasi dapat memakainya secara terarah: instrumentasikan default policy, identifikasi pemanggilan sink lama, pindahkan call site itu ke policy eksplisit, lalu hapus jalur kompatibilitas. Membiarkan default policy yang longgar secara permanen dapat menyamarkan batas yang semestinya dibuat jelas oleh enforcement.

Jika default policy mengembalikan null atau undefined untuk sebuah nilai, operasi sink gagal alih-alih menerima nilai tersebut.

Mode report-only dapat menunjukkan kerusakan sebelum enforcement

Aplikasi yang telah lama berjalan dapat memiliki pemakaian sink di kode framework, package pihak ketiga, template, atau utility lama. Mengaktifkan enforcement tanpa inventaris dapat merusak jalur yang sah.

Mode report-only CSP dapat membantu menampilkan violation sebelum header enforcement diterapkan:

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; trusted-types app-html;

Report merupakan bukti adanya jalur kode yang perlu ditinjau, bukan bukti bahwa setiap assignment yang dilaporkan dapat dieksploitasi. Unit pemeriksaan yang berguna adalah pemanggilan sink beserta aliran data yang mencapainya.

Migrasi praktis dapat memisahkan tiga kasus: sink yang bisa dihapus, sink yang dapat diganti dengan API yang lebih aman, dan sink yang benar-benar memerlukan policy Trusted Types. Mengurangi penggunaan sink sering lebih sederhana daripada membungkus setiap assignment yang sudah ada.

API DOM yang lebih aman tetap penting

Trusted Types bukan alasan untuk melewatkan teks biasa melalui parser HTML. Jika aplikasi hanya perlu menampilkan teks, textContent menjaga operasi itu tetap di luar sink injeksi HTML:

target.textContent = userControlledString;

API konstruksi DOM juga dapat menyatakan struktur tanpa merakit string markup. Trusted Types paling berguna ketika aplikasi memang membutuhkan API yang menafsirkan HTML, script, atau URL script.

Pendekatan ini menjaga jumlah policy tetap sedikit. Beberapa policy dengan cakupan sempit lebih mudah ditinjau daripada banyak policy yang sekadar meniru call site lama.

Enforcement browser adalah satu lapisan

Trusted Types menangani batas tertentu di sisi client. Kontrol ini tidak menggantikan output encoding, sanitasi sesuai konteks, pembatasan source melalui CSP, validasi sisi server, review dependency, atau pola framework yang aman.

Trusted Types juga tidak dapat melindungi browser yang tidak menerapkan enforcement terkait. Kompatibilitas karena itu perlu masuk dalam perencanaan deployment, terutama ketika aplikasi masih mendukung client lama.

Properti keamanannya lebih sempit dan konkret: pada browser yang mendukung dengan directive CSP aktif, sink injeksi yang tercakup tidak dapat bebas menerima string biasa. Kode harus melewati batas policy eksplisit atau operasi akan gagal.

Dengan begitu, konvensi implisit—ingat melakukan sanitasi sebelum sink ini—berubah menjadi kontrak yang ditegakkan browser pada titik injeksi DOM tertentu.

Referensi