Clickjacking (kadang disebut UI redressing, "mendandani ulang tampilan") adalah serangan yang menipumu agar mengklik sesuatu yang berbeda dari yang kamu lihat. Caranya: halaman asli disembunyikan di dalam frame transparan, ditaruh pas di atas tombol umpan. Bayangin kaca bening yang ditempel di atas poster: kamu mengira menekan tombol di poster, padahal jarimu menyentuh kaca yang menutupinya. Yang kamu lihat poster, yang kamu sentuh kaca.
🧪 Tebakan cepat: sebuah halaman memuat
bank.exampledi dalam<iframe>, lalu bikin iframe itu transparan (opacity: 0) dan menaruhnya di atas tombol "Klaim Hadiah". Satu HTTP header dibank.examplebisa bikin iframe ini gak pernah tampil. Header apa? Tebak dulu, jawabannya ada di bagian "Dua header yang menolak dibingkai".
Gimana jebakannya tersusun
Di Lab semua ini terjadi di dalam kanvas Lab sendiri: ada "halaman umpan" dan "halaman bank", dua-duanya milik Lab, bukan situs sungguhan. Halaman umpan punya tombol besar "Klaim Hadiah". Di atasnya, sebuah iframe memuat tombol "Konfirmasi Transfer" dari halaman bank.
Tiga potong CSS yang menyusun jebakan:
.jebakan {
position: absolute; /* lepas dari aliran, bisa ditaruh di mana saja */
top: 0; left: 0;
width: 100%; height: 100%;
opacity: 0; /* transparan: iframe-nya gak kelihatan */
z-index: 10; /* di atas tombol umpan */
}
position: absolutedengantop/leftmenaruh iframe tepat menutupi tombol umpan.opacity: 0bikin iframe tembus pandang. Isinya tetap ada dan tetap bisa diklik, cuma gak terlihat.z-index: 10menaruhnya di lapisan paling atas, jadi klik mengenai iframe duluan, bukan tombol di bawahnya.
Waktu kamu mengklik tombol "Klaim Hadiah", jarimu sebenarnya jatuh ke tombol "Konfirmasi Transfer" di iframe transparan. Kamu login di bank, jadi bank memproses klik itu seolah kamu sendiri yang menekannya. Itulah "jacking": klikmu dibajak ke tempat lain.
Jenis-jenisnya satu pola yang sama, beda sasaran: like/follow jacking (tanpa sadar nge-like akun penyerang), payment jacking (klik tombol bayar tersembunyi), dan permission jacking (klik tombol "Izinkan" di dialog kamera atau mikrofon).
Dua header yang menolak dibingkai
Clickjacking butuh satu syarat: halaman korban boleh dimuat di dalam iframe situs lain. Pertahanannya memotong syarat itu, yaitu melarang halaman dibingkai. Ada dua header, dikirim server bersama halaman:
# Cara lama, masih didukung semua browser
X-Frame-Options: DENY
# Cara modern, lewat Content-Security-Policy
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options punya dua nilai yang dipakai:
DENY: halaman gak boleh dibingkai oleh siapa pun, termasuk situs sendiri.SAMEORIGIN: cuma halaman dari origin yang sama yang boleh membingkainya.
frame-ancestors di CSP lebih fleksibel dan mengganti X-Frame-Options:
frame-ancestors 'none': setaraDENY.frame-ancestors 'self': setaraSAMEORIGIN.frame-ancestors https://partner.example: cuma origin itu yang boleh membingkai.
Kalau bank.example mengirim salah satu header ini, browser menolak menampilkan iframe-nya: frame-nya kosong, dan jebakan runtuh. Itu jawaban tebakannya: X-Frame-Options (atau frame-ancestors).
Bedanya penting: X-Frame-Options cuma bisa "semua atau origin sendiri", sedangkan frame-ancestors bisa menyebut beberapa origin tertentu. Pasang keduanya kalau mau aman di browser lama sekalipun. Kalau dua-duanya dikirim, frame-ancestors yang diutamakan: browser yang mendukungnya mengabaikan X-Frame-Options. Perhatikan: ini header yang dikirim halaman yang dilindungi (bank.example), bukan halaman penyerang. Penyerang gak akan memasangnya.
Memasang header di server
Di server Express, satu middleware memasang header ini di setiap response:
app.use((req, res, next) => {
res.setHeader("X-Frame-Options", "DENY");
res.setHeader("Content-Security-Policy", "frame-ancestors 'none'");
next();
});
res.setHeader("X-Frame-Options", "DENY")melarang halaman dibingkai di browser mana pun.- Baris
frame-ancestors 'none'melakukan hal yang sama lewat CSP, untuk browser modern. next()meneruskan ke handler berikutnya. Karena dipasang diapp.usepaling atas, semua halaman dapat perlindungan ini.
💡 Frame-busting JavaScript sebagai cadangan
Kalau karena satu alasan header gak bisa dipasang, ada cara cadangan di sisi klien. Halaman mengecek apakah dirinya sedang berada di dalam frame:
if (window.self !== window.top) {
// Halaman ini sedang dibingkai: paksa keluar.
window.top.location = window.self.location;
}
window.selfadalah window halaman ini;window.topadalah window paling luar. Kalau keduanya beda, berarti halaman ini di dalam frame.- Baris di dalamnya memaksa window terluar pindah ke alamat halaman ini, jadi halaman "merebut" seluruh tab.
Ini cuma cadangan, karena bisa diakali. Header jauh lebih kuat: browser yang menolak membingkai, bukan script yang berharap sempat jalan.
⚠️ Jebakan yang sering ketemu
- Lupa memasang header sama sekali. Tanpa
X-Frame-Optionsatauframe-ancestors, halaman boleh dibingkai siapa saja. Itu kondisi default yang rentan. - Memasang header di halaman penyerang. Header dipasang di halaman yang kamu lindungi, bukan di halaman yang membingkai. Kamu gak punya kendali atas halaman penyerang.
- Mengira transparan berarti nonaktif. Iframe
opacity: 0tetap bisa menerima klik. Transparan cuma soal terlihat, bukan soal aktif. - Hanya mengandalkan frame-busting. Script bisa diakali. Jadikan header pertahanan utama, script cuma cadangan.
TL;DR: Clickjacking menyembunyikan halaman korban di iframe transparan (opacity: 0, z-index tinggi) di atas tombol umpan, jadi klikmu jatuh ke tombol yang gak kamu lihat. Pertahanannya melarang halaman dibingkai, dengan dua header yang dikirim halaman yang dilindungi: X-Frame-Options: DENY (atau SAMEORIGIN) untuk semua browser, dan Content-Security-Policy: frame-ancestors 'none' (atau 'self', atau origin tertentu) untuk browser modern. frame-ancestors lebih fleksibel karena bisa menyebut origin tertentu. Frame-busting JavaScript cuma cadangan. Jangan lupa: header dipasang di halamanmu, bukan di halaman penyerang.