WEB-SECURITY JAVASCRIPT FRONTEND-SECURITY VULNERABILITY PROTOTYPE-POLLUTION NODEJS BACKEND-SECURITY DATA-INTEGRITY APPLICATION-SECURITY

Menguak Ancaman Prototype Pollution: Kerentanan JavaScript yang Sering Terlupakan di Aplikasi Web Anda

⏱️ 12 menit baca
👨‍💻

Menguak Ancaman Prototype Pollution: Kerentanan JavaScript yang Sering Terlupakan di Aplikasi Web Anda

1. Pendahuluan

Di dunia pengembangan web yang serba cepat, fokus kita seringkali tertuju pada fitur-fitur baru, performa, dan pengalaman pengguna. Namun, ada satu area krusial yang tidak boleh terlewatkan: keamanan. Kita sudah sering mendengar tentang XSS, CSRF, atau SQL Injection, tapi bagaimana dengan kerentanan yang lebih “silent” dan spesifik JavaScript? Salah satunya adalah Prototype Pollution.

📌 Apa itu Prototype Pollution? Bayangkan Anda memiliki resep dasar untuk semua kue Anda (Object.prototype). Resep ini berisi instruksi umum seperti “cara memanggang” atau “cara menghias”. Prototype Pollution adalah kondisi di mana penyerang bisa menyuntikkan atau memodifikasi instruksi di resep dasar ini, sehingga semua kue baru yang Anda buat (objek JavaScript baru) akan secara otomatis mengikuti instruksi yang telah dimanipulasi oleh penyerang.

Kerentanan ini muncul ketika aplikasi JavaScript (baik di frontend maupun backend Node.js) secara tidak sengaja mengizinkan pengguna untuk memodifikasi prototipe dasar objek, khususnya Object.prototype. Karena hampir semua objek di JavaScript mewarisi dari Object.prototype, perubahan pada prototipe ini bisa memiliki dampak global dan berbahaya.

Mengapa penting untuk developer web? Kerentanan ini bisa berujung pada berbagai serangan serius, mulai dari Denial of Service (DoS), bypass otorisasi, hingga Remote Code Execution (RCE) di sisi server Node.js. Sayangnya, banyak developer yang belum sepenuhnya memahami mekanisme atau potensi dampaknya, sehingga sering terlewatkan dalam proses pengembangan dan peninjauan kode.

Mari kita selami lebih dalam!

2. Memahami JavaScript Prototype Chain: Fondasi Kerentanan

Sebelum kita bisa memahami Prototype Pollution, kita perlu menyegarkan kembali pemahaman tentang bagaimana JavaScript bekerja dengan objek dan pewarisan.

Di JavaScript, setiap objek memiliki sebuah prototipe (prototype), dan prototipe ini juga bisa memiliki prototipe, membentuk apa yang disebut prototype chain. Ketika Anda mencoba mengakses properti dari sebuah objek, JavaScript akan mencarinya di objek itu sendiri. Jika tidak ditemukan, ia akan naik ke prototipe objek tersebut, dan seterusnya, hingga mencapai Object.prototype (prototipe paling dasar) atau null.

const obj = {};
// obj.__proto__ === Object.prototype
// Object.prototype.__proto__ === null

const myArray = [];
// myArray.__proto__ === Array.prototype
// Array.prototype.__proto__ === Object.prototype
// Object.prototype.__proto__ === null

Properti __proto__ (atau [[Prototype]] secara internal) adalah referensi ke objek prototipe. Object.prototype adalah objek yang menjadi akar dari sebagian besar rantai prototipe objek di JavaScript.

💡 Contoh Sederhana Pewarisan:

const animal = {
  eats: true,
  walk() {
    console.log("Animal walks.");
  }
};

const rabbit = {
  jumps: true,
  __proto__: animal // Mengatur 'animal' sebagai prototipe 'rabbit'
};

rabbit.walk(); // Output: Animal walks. (diwarisi dari animal)
console.log(rabbit.eats); // Output: true (diwarisi dari animal)
console.log(rabbit.jumps); // Output: true (milik rabbit sendiri)

Dalam contoh di atas, rabbit tidak memiliki metode walk() secara langsung, tetapi ia dapat mengaksesnya karena animal adalah bagian dari prototype chain-nya.

Kerentanan Prototype Pollution memanfaatkan fakta bahwa jika penyerang bisa memodifikasi Object.prototype, maka properti atau metode baru yang disuntikkan akan tersedia untuk semua objek baru yang dibuat menggunakan literal objek {} atau new Object(), dan juga untuk objek yang secara implisit mewarisi dari Object.prototype.

3. Bagaimana Prototype Pollution Bekerja? Anatomi Serangan

Serangan Prototype Pollution terjadi ketika ada fungsi dalam aplikasi Anda yang secara rekursif melakukan deep merge (menggabungkan properti dari beberapa objek secara mendalam) atau clone objek, dan fungsi tersebut tidak secara hati-hati menangani properti khusus seperti __proto__ atau constructor.prototype.

Kondisi Umum Kerentanan: Sebuah aplikasi rentan jika:

  1. Menerima input yang dikontrol pengguna (misalnya dari body JSON, query string, atau form data).
  2. Menggunakan fungsi utilitas (baik kustom maupun dari library pihak ketiga yang belum di-patch) yang melakukan deep merge atau extend objek secara rekursif.
  3. Fungsi merge/extend tersebut tidak memiliki validasi atau sanitasi yang memadai untuk properti __proto__ atau constructor.prototype.

⚠️ Payload Khas Serangan: Penyerang biasanya akan menyertakan salah satu dari dua bentuk payload dalam input mereka:

Payload ini, ketika diproses oleh fungsi merge yang rentan, akan “menyuntikkan” maliciousProperty langsung ke Object.prototype.

Mari kita lihat contoh kode JavaScript yang rentan:

// ❌ CONTOH KODE RENTAN: Implementasi deep merge yang tidak aman
function unsafeDeepMerge(target, source) {
    for (const key in source) {
        // ⚠️ Masalah utama: Tidak ada validasi untuk '__proto__' atau 'constructor'
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
                if (!target[key] || typeof target[key] !== 'object' || Array.isArray(target[key])) {
                    target[key] = {};
                }
                unsafeDeepMerge(target[key], source[key]); // Rekursif
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}

const defaultConfig = {
    env: 'development',
    db: {
        host: 'localhost',
        port: 5432
    }
};

// Input dari pengguna yang dikontrol oleh penyerang
// Penyerang mencoba menyuntikkan properti 'isAdmin' ke Object.prototype
const maliciousInput = JSON.parse('{"db": {"host": "malicious.com"}, "__proto__": {"isAdmin": true}}');

// Mencoba menggabungkan input pengguna ke konfigurasi
// Anggap saja 'defaultConfig' adalah objek yang ingin kita gabungkan
// Untuk demonstrasi, kita akan mulai dengan objek kosong agar lebih jelas dampaknya
const finalConfig = {};
unsafeDeepMerge(finalConfig, defaultConfig); // Gabungkan default dulu
unsafeDeepMerge(finalConfig, maliciousInput); // Kemudian gabungkan input berbahaya

console.log('Final Config:', finalConfig);
// Output: { env: 'development', db: { host: 'malicious.com', port: 5432 } }
// Objek 'finalConfig' terlihat normal. Tapi apa yang terjadi di balik layar?

// Sekarang, buat objek baru. Apakah properti 'isAdmin' muncul?
const newUser = { name: 'Alice' };
console.log('newUser.isAdmin:', newUser.isAdmin); // Output: true!

// Bahkan objek kosong pun kini memiliki properti ini
console.log('{}.isAdmin:', {}.isAdmin); // Output: true!

// 💡 Catatan: Object.create(null) tidak akan terpengaruh karena tidak mewarisi dari Object.prototype.
console.log('Object.create(null).isAdmin:', Object.create(null).isAdmin); // Output: undefined

Dalam contoh di atas, meskipun finalConfig terlihat normal, Object.prototype telah dimodifikasi. Properti isAdmin: true sekarang ada di prototipe dasar, sehingga setiap objek baru yang dibuat dengan literal {} atau new Object() akan secara otomatis mewarisi isAdmin: true.

🎯 Dampak yang Mungkin Terjadi:

4. Skenario Serangan di Aplikasi Web

Prototype Pollution bukan hanya ancaman teoritis; ia bisa dieksploitasi dalam berbagai konteks aplikasi web.

4.1. Frontend (Browser)

Di sisi klien, Prototype Pollution seringkali dikombinasikan dengan kerentanan lain seperti Cross-Site Scripting (XSS) atau di lingkungan di mana ada input pengguna yang tidak divalidasi dengan baik.

4.2. Backend (Node.js)

Sisi backend dengan Node.js adalah tempat Prototype Pollution bisa paling merusak, terutama karena potensi RCE.

5. Strategi Pencegahan dan Mitigasi ✅

Melindungi aplikasi Anda dari Prototype Pollution membutuhkan pendekatan berlapis.

5.1. ✅ Validasi dan Sanitasi Input Ketat

Ini adalah pertahanan pertama dan terpenting.

5.2. ✅ Hindari Deep Merge Rekursif yang Tidak Aman

5.3. ✅ Gunakan Object.freeze(), Object.seal(), atau Object.preventExtensions()

Untuk objek-objek penting atau konfigurasi global, Anda bisa membuatnya tidak dapat diubah (immutable).

Membekukan Object.prototype itu sendiri di awal aplikasi bisa menjadi langkah ekstrem tapi efektif untuk mencegah polusi. Namun, ini bisa memiliki efek samping yang tidak terduga pada library pihak ketiga yang mungkin mencoba memodifikasinya.

5.4. ✅ Perbarui Dependensi Secara Teratur

Kerentanan Prototype Pollution sering ditemukan dan di-patch di library populer seperti Lodash, jQuery, atau Express. Pastikan Anda selalu menggunakan versi terbaru dari semua dependensi proyek Anda. Tools seperti npm audit atau yarn audit dapat membantu mengidentifikasi dependensi yang rentan.

5.5. ✅ Gunakan Map atau WeakMap untuk Penyimpanan Data

Jika Anda menyimpan data yang mungkin berasal dari input pengguna dan tidak memerlukan pewarisan prototipe, pertimbangkan untuk menggunakan Map alih-alih objek biasa. Map tidak menggunakan prototype chain dan kebal terhadap serangan Prototype Pollution.

6. Mendeteksi Prototype Pollution

Mendeteksi kerentanan Prototype Pollution bisa jadi rumit karena sifatnya yang tersembunyi.