Script pihak ketiga adalah JavaScript yang kamu muat dari server orang lain: analytics, widget live chat, iklan, atau library dari CDN. Satu baris <script src> memberi kode itu akses yang sama persis dengan kodemu sendiri. Bayangin kamu menitipkan kunci rumah ke jasa bersih-bersih. Kuncinya membuka semua kamar, bukan cuma dapur. Dan kalau kantor jasa itu kebobolan, pencurinya ikut pegang kunci rumahmu.
🧪 Tebakan cepat: halaman checkout-mu memuat
<script src="https://widget.example.net/chat.js">. Bisa gak script itu membaca nomor kartu yang diketik di form checkout-mu? Tebak dulu, jawabannya ada di bagian "Script pihak ketiga jalan dengan hak penuh".
Script pihak ketiga jalan dengan hak penuh
Browser gak membedakan script milikmu dan script dari domain lain. Begitu dimuat lewat <script src>, script itu jalan di dalam halamanmu, dengan origin halamanmu. Same-origin policy (aturan browser yang memisahkan satu origin dari origin lain) gak menolong di sini, karena bagi browser script itu bagian dari halamanmu. Jadi script chat tadi bisa:
- membaca dan mengubah semua isi halaman, termasuk nilai input di form;
- membaca cookie yang gak
HttpOnlydan isilocalStorage; - mengirim request ke mana pun yang diizinkan halamanmu.
Itu jawaban tebakannya: bisa. Selama dia jalan di halamanmu, nomor kartu di form ikut terbaca.
Ini bukan berarti vendor-nya jahat. Risikonya ada di rantai pasokan: kalau server, CDN, atau akun vendor itu dibobol, kode penyerang ikut jalan di semua situs yang memuatnya. Serangan lewat pemasok seperti ini disebut supply chain attack. Contoh nyata: tahun 2024 domain polyfill.io pindah tangan, lalu script-nya mulai menyisipkan kode jahat ke lebih dari 100.000 situs yang memuatnya.
Inventaris: catat dan nilai setiap tag
Langkah pertama defender: daftar semua tag pihak ketiga di halaman, lalu nilai satu per satu. Ini inventaris halaman https://tokokopi.example.com/checkout, sama dengan yang ada di Lab:
| Tag | Lewat HTTPS? | Isinya dikunci? | Perlu menyentuh halaman? |
|---|---|---|---|
/js/app.js |
ya, origin sendiri | gak perlu, file sendiri | ya, ini kode toko |
dayjs.min.js dari cdn.jsdelivr.net |
ya | belum | ya, dipanggil kode toko |
analytics.js dari cdn.example.com |
tidak, masih http:// |
gak bisa, vendor mengubahnya | ya, tugasnya mengukur halaman |
chat.js dari widget.example.net |
ya | gak bisa, vendor mengubahnya | tidak, cuma butuh kotaknya sendiri |
Dari tabel itu keluar tiga perbaikan:
http://jadihttps://. Script lewathttp://bisa diubah siapa pun di jaringan yang sama (Wi-Fi publik, router), ini disebut serangan man-in-the-middle. Di halaman HTTPS, browser modern malah langsung memblokirnya sebagai mixed content, jadi fiturnya juga mati.- File berversi dikunci dengan SRI.
[email protected]gak akan berubah, jadi isinya bisa dikunci. - Yang gak perlu menyentuh halaman dipindah ke iframe. Widget chat cukup jalan di kotaknya sendiri.
SRI: kunci isi file dengan hash
SRI (Subresource Integrity) adalah atribut integrity berisi hash dari isi file. Browser menghitung hash file yang datang dan menolak menjalankannya kalau beda satu byte pun. Hash-nya dibuat sekali dari file aslinya:
$ curl -s https://cdn.jsdelivr.net/npm/[email protected]/dayjs.min.js | openssl dgst -sha384 -binary | openssl base64 -A
DpVxUeeBWjUvUV1czyIHJAjh+jYUZFu2lLakbdua5vbwOrBGi1UgaKCHjTC+x3Ky
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dayjs.min.js"
integrity="sha384-DpVxUeeBWjUvUV1czyIHJAjh+jYUZFu2lLakbdua5vbwOrBGi1UgaKCHjTC+x3Ky"
crossorigin="anonymous" defer></script>
integrity="sha384-...": nama algoritmanya (sha384), tanda minus, lalu hash dari perintah tadi.crossorigin="anonymous": meminta file lewat CORS tanpa cookie. Wajib untuk file dari origin lain: tanpa itu browser gak boleh membaca isi file untuk dicek, jadi script-nya diblokir.defer: script jalan setelah HTML selesai dibaca, gak menahan tampilan halaman.
SRI cuma cocok untuk file yang isinya tetap. analytics.js dan chat.js diubah vendornya kapan saja, jadi hash-nya bakal basi.
CSP: daftar host yang boleh
CSP (Content Security Policy) adalah aturan dari halaman ke browser: script dan iframe boleh datang dari mana saja. Isinya diambil dari inventaris tadi:
<meta http-equiv="Content-Security-Policy"
content="script-src 'self' https://cdn.jsdelivr.net https://cdn.example.com; frame-src https://widget.example.net">
script-src: host yang boleh menyuplai script.'self'artinya origin halaman itu sendiri.frame-src: host yang boleh tampil di iframe.- Script dari host yang gak terdaftar diblokir.
http://cdn.example.comjuga diblokir, karena skemanya beda darihttps://di daftar.
Widget di iframe sandbox
Widget chat gak perlu membaca form checkout-mu. Muat dia sebagai halaman terpisah:
<iframe src="https://widget.example.net/embed"
sandbox="allow-scripts allow-forms allow-popups"
title="Chat"></iframe>
- Iframe dari origin lain adalah dokumen terpisah. Same-origin policy sekarang berlaku: widget gak bisa membaca DOM, cookie, atau
localStoragehalamanmu. - Atribut
sandboxmembatasi lagi apa yang boleh dilakukan isi iframe. Tanpa nilai, semuanya dilarang: script, form, popup, sampai mengarahkan halaman induk ke URL lain. Kamu cuma menambahkan izin yang dibutuhkan:allow-scripts,allow-forms,allow-popups.
⚠️ Jebakan yang sering ketemu
integritytanpacrossorigin. Script dari CDN langsung diblokir, lengkap dengan error di console. Pasang keduanya.- SRI di file yang diubah vendor. Begitu vendor merilis ulang, hash-nya gak cocok dan fiturnya mati diam-diam. SRI untuk file berversi, CSP untuk sisanya.
sandbox="allow-scripts allow-same-origin"untuk halaman dari origin-mu sendiri. Dengan dua izin itu, script di dalam iframe bisa mencopot sandbox-nya. Untuk konten dari origin sendiri, jangan gabungkan keduanya.- Menambah tag tanpa mencatatnya. Tag yang gak ada di inventaris gak pernah dinilai. CSP membantu: host yang belum terdaftar langsung diblokir.
TL;DR: Script pihak ketiga jalan dengan hak yang sama dengan kodemu, jadi kalau pemasoknya dibobol (supply chain attack), kode penyerang jalan di halamanmu. Catat setiap tag di inventaris dan nilai: wajib https:// (http:// bisa diubah di jalan dan diblokir di halaman HTTPS), file berversi dikunci dengan integrity plus crossorigin="anonymous", CSP script-src cuma berisi host dari inventaris, dan widget yang gak perlu menyentuh halamanmu dipindah ke iframe dengan atribut sandbox.