Node.js ekibi, çalışma zamanının içine gömülü Brotli dekoderini etkileyen ve saldırganın kontrol ettiği sıkıştırılmış veriyi açan her uygulamaya ulaşabilen kritik bir açığı (CVE-2026-111497) desteklenen üç sürüm hattı için kapattı. Etkilenen sürümleri, neden “sıradan bir kütüphane açığı” olmadığını ve SOC ekipleri için tespit önerilerini derledik.
CVE-2026-111497 · Out-of-bounds write (CWE-787) · Önem: Yüksek · Etkilenen: Node.js 22.x / 24.x / 26.x ve tüm EOL hatlar · 22.10.2026
Node.js ekibi 21 Ekim 2026'da, desteklenen 22.x, 24.x ve 26.x hatlarının tamamı için bir güvenlik güncellemesi yayımladı. Güncelleme on ayrı açığı kapatıyor; bunların en ciddisi, Node.js'in kendi içinde taşıdığı Brotli sıkıştırma kütüphanesinde bulunan ve projenin ifadesiyle uzaktan kod çalıştırmaya imkân veren bir bellek yazma hatasıdır. Hatanın ayırt edici özelliği, bir uygulamanın kodunda değil, doğrudan çalışma zamanının içinde yer almasıdır. Bu nedenle uygulamanın işlevi ya da hangi framework ile yazıldığı belirleyici değildir; uygulamanın bir noktada saldırganın gönderdiği Brotli veriyi açması yeterlidir. Bu belge, açığın teknik ayrıntılarını, risk altındaki kod yollarını ve alınması gereken önlemleri bir arada ele almaktadır.
Açığın geçmişi ve yama süreci
Düzeltmenin kökeni 21 Ekim'den öncesine dayanıyor. Google'ın Brotli projesinde 27 Şubat 2026'da “Detect and report invalid stream earlier” (“geçersiz akışı daha erken tespit et ve raporla”) başlıklı bir commit yer aldı. Commit bir güvenlik düzeltmesi olarak etiketlenmemiş, bir CVE numarası taşımıyordu; dışarıdan dekoderin hata yönetimini sıkılaştıran rutin bir iyileştirme gibi görünüyordu ve sekiz ay bu şekilde kaldı.

Bu commit'in kapattığı davranışın bir bellek bozulmasına yol açtığı anlaşıldığında durum değişti. Node.js güvenlik ekibi düzeltmeyi özel (private) bir güvenlik PR'ı üzerinden üç hatta da geri taşıdı ve 21 Ekim 2026'da yama CVE-2026-111497 kimliğiyle yayımlandı. Aynı gün Google da Brotli'yi 1.2.1 sürümüyle (geriye dönük 1.1.1 ve 1.0.10 ile birlikte) güvenlik sürümü olarak çıkardı. Bu ayrıntı önemli: Node.js dışında kendi Brotli kopyasını paketleyen her yazılımın aynı güncellemeyi ayrıca alması gerekiyor.
Açığı Node.js tarafında Matteo Collina raporladı, düzeltmeyi Antoine du Hamel hazırladı; upstream commit ise Google'dan Evgenii Kliuchnikov'a ait. Node.js bu açığı KRİTİK değil, YÜKSEK önem derecesinde değerlendiriyor. CVE kaydı ve NVD girdisi yayın anında henüz kamuya açık olmadığından resmî bir CVSS vektörü bulunmuyordu.
Açık tam olarak nerede?
Brotli, bir akışın içinde veriyi farklı “blok tiplerine” ayırır; literal bloklar, komut (insert/copy) blokları ve mesafe (distance) blokları. Dekoder akışın ortasında bu tipler arasında geçiş yapabilir ve kaç blok tipinin tanımlı olduğu, geçişlerin nerede yapılacağı akışın kendi içinde kodlanır. Normal bir akışta bu tutarlıdır.
Sorun, tutarsız bir akışta ortaya çıkıyor. Bir akış yalnızca tek bir blok tipi tanımladığı hâlde yine de bir blok geçişi talep ederse, bu geçersiz bir durumdur. Dekoderin DecodeBlockTypeAndLength fonksiyonu bunu fark edip bir başarısızlık değeri döndürüyordu. Buraya kadar sorun yok. Asıl kusur, bu başarısızlığın ne yapıldığında: hızlı çözümleme yolundaki blok geçişi sarmalayıcıları (literal, komut ve mesafe için üç ayrı fonksiyon) geriye hiçbir değer döndürmeyen void fonksiyonlardı. Dolayısıyla alttaki fonksiyonun ürettiği “bu akış geçersiz” sinyali yukarıya hiç taşınmıyor, sessizce yutuluyordu. Dekoder, iç durumu (blok tipi ring-buffer'ı ve ilgili Huffman ağacı indeksleri) tutarsız kalmışken çözümlemeye devam ediyor ve bu da hedeflenen tamponun dışına yazma (out-of-bounds write) ile sonuçlanıyordu. Bu yazma işlemi native kod içinde, doğrudan Node.js sürecinin bellek alanına düşüyor.
Yamanın yaptığı iş tam olarak bu sessiz yutmayı ortadan kaldırmak. DecodeBlockTypeAndLength artık bir boolean yerine gerçek bir hata kodu döndürüyor; tek blok tipi varken geçiş istendiğinde yeni tanımlanan BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH (hata kodu -17) üretiliyor. Üç blok geçişi sarmalayıcısı da artık bu hata kodunu yukarı taşıyor ve yeni bir makro (BROTLI_SAFE_WITH_STATUS) başarısız durumu gördüğü anda çözümlemeyi durduruyor. Kısacası düzeltme, “yaz ve devam et”i “fark et ve dur”a çeviriyor. İkinci bir Node commit'i de bu yeni sabiti zlib.constants üzerinden dışarı açıyor; birazdan göreceğimiz gibi bu, yama durumunu doğrulamak için elimize tek satırlık bir test veriyor.
Etkinin genişliği
Web açıklarının çoğu belirli bir eklentiye, bir framework'e ya da hatalı yazılmış uygulama koduna bağlıdır. Bu açıkta durum farklı, çünkü kusurlu kod uygulamada değil, çalışma zamanının içinde. Node.js'in çekirdeğindeki http modülü kendiliğinden hiçbir şeyi açmaz; ancak Brotli çözümlemesi, uygulama kodunun bunu açıkça istemeden tetikleyebileceği birçok yoldan devreye girer. Açığın kapsamını geniş kılan da budur.

İlk yol fetch(). Node'un yerleşik fetch'i undici üzerinden çalışır ve bir yanıt Content-Encoding: br başlığıyla geldiğinde gövdeyi Brotli dekoderine verir. Burada kritik nokta şudur: istemci isteğinde Brotli istemese bile, yanıtın hangi kodlamayla açılacağını yanıtın başlığı belirler. Dolayısıyla çağrı yapılan sunucu ya da bu çağrının trafiğine müdahale edebilen bir taraf, kodlamayı dayatabilir. Bu durum açığı yalnızca gelen istekler meselesi olmaktan çıkarıp, uygulamanın dışarıya yaptığı çağrıları da kapsayan bir soruna dönüştürür.
İçeri gelen tarafta ise Express 5 öne çıkıyor. body-parser 2.0.0 ile Brotli desteği geldi ve istek gövdelerini varsayılan olarak açıyor. Express 5, body-parser 2.x'e bağımlı olduğu için express.json() ve express.urlencoded(), rotaya ulaşabilen herhangi birinin gönderdiği Content-Encoding: br yükünü kabul edip açar. axios da Node tarafında varsayılan olarak Brotli isteyip yanıtı açtığı için aynı yola düşer. Buna kendi yazdığınız zlib.brotliDecompress() / createBrotliDecompress() çağrılarını (proxy, crawler, webhook alıcısı, dosya yükleme hattı) ve self-hosted Next.js'in görsel optimizasyonundaki upstream Brotli yanıtlarını da ekleyin.
İstismar durumunun değerlendirmesi
Kaynaklar, gösterilen etki konusunda ayrışıyor. Vercel'in bülteni, araştırmacıların yamasız 22.x, 24.x ve 26.x üzerinde hem bellek ifşası hem de komut çalıştırma gösterdiğini belirtiyor. Buna karşılık 22 Ekim'de yayımlanan kavram kanıtı (PoC), kod çalıştırmayı yalnızca kasıtlı olarak zayıflatılmış, ASLR'siz (non-PIE) bir test derlemesinde elde edebildi. Standart bir Node.js derlemesinde aynı PoC'nin sonucu kod çalıştırma değil, çökme (crash) ve sınırlı bir bellek okuması oldu; PoC'nin yazarı da normal bir derlemede çalışan bir saldırı elde edemediğini belirtiyor. Kontrol akışını ele geçirmek için gereken heap düzeni kamuya açıklanmış değil.
Dolayısıyla mevcut durumda, standart bir Node.js kurulumuna karşı kanıtlanmış ve vahşi doğada çalışan bir uzaktan kod çalıştırma istismarı bulunmuyor. 22 Ekim itibarıyla ne bir saldırı raporu var ne de açık CISA KEV kataloğunda yer alıyor. Bununla birlikte risk azımsanmamalıdır: Brotli açan her rota için güvenilir bir uzaktan çökme (DoS) imkânı mevcuttur ve bellek ifşası riski gerçektir. Bellek ifşası, kod çalıştırmaya hiç ulaşılmadan da parola, token ve anahtar gibi hassas verilerin dışarı sızmasına yol açabilir. Bu nedenle açık, NVD skorunun yayımlanması beklenmeden öncelikli olarak yamalanmalıdır.
Yamalı mıyım? Tek satırlık kontrol
Yama yeni bir dekoder hata kodu eklediği ve Node bu kodu bir sabit olarak dışarı açtığı için, sürüm tablolarına bakmaya gerek kalmadan doğrudan test edebilirsiniz:
node -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH"
# -17 => yamalı (22.23.4, 24.22.1, 26.12.1)
# undefined => zafiyetli
Bu kontrolü laptopunuzda değil, ürettiğiniz her imajın ve her CI runner'ının içinde çalıştırın. Geliştirici makinesinin doğru sürümde olması, Ağustos'ta çekilmiş bir base image hakkında hiçbir şey söylemez. Yama sonrası bir süreç bozuk akışı bir hata koduyla reddeder; bu kodu loglarınızda görmeniz, birinin o şekilde hazırlanmış bir akış gönderdiği ya da çağırdığınız bir sunucunun bozuk veri yolladığı anlamına gelir. İkisi de bakmaya değer.
Hangi sürümler etkileniyor, hangileri yama alıyor?
Node.js yalnızca desteklenen hatlar için yeni sürüm yayımladı. Güvenlik sürümlerinde, kural gereği, ömrü dolmuş (EOL) hatlar her zaman etkilenmiş kabul edilir.
| Çalıştırılan sürüm | Yükseltilecek sürüm | Durum |
|---|---|---|
| 26.x | 26.12.1 | Current (güncel hat) |
| 24.x | 24.22.1 | Aktif LTS |
| 22.x | 22.23.4 | Aktif LTS |
| 20.x ve öncesi | Resmî yama yok | Ömrü doldu; 22.x veya 24.x hattına taşınmalı |
Node.js 20 “kapsam dışı” değil, “destek dışı”. Yani zafiyetli kalmaya devam ediyor ama düzeltme almayacak. Konteyner kullanan ortamlarda yamalı tag'e geçmek (node:24.22.1-bookworm-slim ve muadilleri), imajı yeniden derleyip dağıtmak ve çalışan sürümü konteynerin içinde node -v ile doğrulamak gerekiyor. Vercel gibi yönetilen platformlarda 22.x ve 24.x çalışan projelerin yama sürümünü platform yönetir; ancak hâlâ Node 20'ye sabitlenmiş projeler sürümü elle 22.x veya 24.x'e çekip yeniden dağıtmalıdır.
Aynı güncelleme, Brotli'nin yanında dokuz açık daha kapatıyor. Bunların arasında IP adresi non-canonical biçimde ya da tam genişlikli (fullwidth) rakamlarla yazıldığında TLS sunucu adı doğrulamasını atlatan iki yüksek önemli açık (CVE-2026-106069 ve CVE-2026-105483), dns.resolve'un adları gömülü NUL byte'ta kestiği bir açık (CVE-2026-105482) ve crypto.randomUUID() ile randomInt()'in startup snapshot'larda aynı değerleri tekrarlamasına yol açan bir açık (CVE-2026-106074) var. Sürüm ayrıca ele geçirildiği bildirilen sekiz eMudhra ara CA anahtarını blokluyor ve bundled undici, OpenSSL, c-ares ile SQLite kopyalarını güncelliyor. Tek bir çalışma zamanı güncellemesi bunların tamamını kapatıyor.
Bugün yapılması gerekenler
Envanter çıkarılmalıdır. Sunucular, konteyner imajları, CI ortamları ve geliştirici makineleri listelenmelidir. Node.js'i kendi içinde getiren ürünler de göz ardı edilmemelidir: masaüstü uygulamaların Electron katmanları, bazı ağ cihazlarının yönetim arayüzleri ve Node tabanlı kurumsal araçlar da kapsamdadır.
İnternete açık ve Brotli açan sistemlere öncelik verilmelidir. İlk sırada, dışarıdan gelen istek gövdelerini açan (Express 5 / body-parser 2.x) ve dış sunuculara
fetch()ile çağrı yapan servisler ele alınmalıdır.Güncelleme yapılmalı ve süreçler yeniden başlatılmalıdır. Yeni binary'yi kurmak tek başına yetmez. PM2, systemd ya da konteyner orkestrasyonu altında çalışan süreçler yeniden başlatılmazsa eski kod bellekte çalışmaya devam eder. Konteynerlerde imajlar yeni base image ile yeniden derlenmeli ve kullanılan etiketin gerçekten yamalı sürümü gösterdiği teyit edilmelidir.
Sonuç doğrulanmalıdır. Güncellemeden sonra çalışan sürüm, yukarıdaki
zlib.constantskontrolüyle ve mümkünse gerçekten trafik işleyen süreç üzerinde teyit edilmelidir.Destek dışı sistemler ayrıca ele alınmalıdır. Node 20 ve öncesini kullanan sistemler desteklenen bir hatta taşınmalıdır. Bu mümkün değilse sistem internetten çekilip erişimi kısıtlanmalıdır.
Kendi Brotli kopyasını taşıyan diğer bileşenler unutulmamalıdır. Python binding'leri, native modüller ve başka runtime'lar kendi güncellemelerini bekler; Node sürümü yalnızca Node'u kapsar.
Yama hemen uygulanamıyorsa
Node.js'in güvenlik bildirisi, güncellemenin tek tam çözüm olduğunu açıkça vurguluyor. Yine de yamayı uygulayana kadar bilinen giriş yollarını daraltmak mümkün, ama bu önlemlerin sınırlarını bilmek şart.
İçeri gelen tarafta, Express/body-parser kullanılıyorsa express.json(), express.urlencoded(), express.text() ve express.raw() çağrılarına { inflate: false } verilebilir. Bu durumda body-parser sıkıştırılmış gövdeleri açmak yerine 415 ile reddeder. Benzer şekilde Content-Encoding: br taşıyan istek gövdeleri proxy ya da yük dengeleyici seviyesinde düşürülebilir. WAF kuralları da (Vercel yayın günü kendi ağı için yayımladı) zaman kazandırır, ancak yalnızca o servisten geçen trafiği korur; sunucuya doğrudan IP üzerinden ulaşılabiliyorsa bu koruma devre dışı kalır.
Dışarı yapılan çağrılar tarafında ise temiz bir geçici çözüm yok. Accept-Encoding başlığından br'yi çıkarmak sunucudan Brotli kullanmamasını “rica eder”, ama undici'nin fetch'i yanıtın iddia ettiği Content-Encoding'i yine de açtığı için kötü niyetli bir sunucu bu ricayı yok sayabilir. Fetch edilen yanıtları yalnızca çalışma zamanı güncellemesi güvene alır. Bu da aslında neden yamanın tek gerçek çözüm olduğunu özetliyor.
MDR ve SOC ekipleri için izleme rehberi
Yama gerçekleşmiş bir sızmayı geri almaz. Şu an için resmî bir IOC listesi (IP, hash, imza) yayımlanmış değil, bu yüzden tespit davranışa dayalı kurgulanmalı. Dahası saldırı uygulama katmanının altında, çalışma zamanının native kodunda gerçekleştiği için uygulama logları çoğu zaman sessiz kalacak. Asıl görünürlük web sunucusu loglarından, Node süreç telemetrisinden (çökme ve yeniden başlatmalar) ve uç nokta telemetrisinden gelecek.
Görünürlük: hangi loglar lazım?
Varsayılan erişim logları bu açık için eksik kalıyor. Nginx ve Apache standart formatta istek gövdesinin boyutunu ve Content-Encoding başlığını kaydetmez; oysa bu iki alan, Brotli tabanlı denemeleri ayırt etmenin en kolay yolu. Log formatına bu alanları eklemek değerli olur. Bunun yanında Node süreç yöneticisinin (PM2, systemd) ve konteyner çalışma zamanının çökme/yeniden başlatma olaylarının, EDR ya da auditd üzerinden süreç oluşturma kayıtlarının ve web sunucusunun dış bağlantılarını gösteren ağ verisinin SIEM'e aktığından emin olunmalıdır. Reverse proxy kullanılıyorsa yanıt başlıklarının loglanması, outbound tarafta gerçekleşebilecek bellek ifşasını görmenin tek yolu olabilir.
Tespit senaryoları
Aşağıdaki tablo istismarın farklı aşamalarında beklenen sinyalleri bir arada gösteriyor. İlk satırlar deneme ve çökme aşamasını, sonraki satırlar başarılı bir sızma sonrasını kapsıyor.
| Sinyal | Veri kaynağı | Neden önemli |
|---|---|---|
Content-Encoding: br başlıklı POST/PUT isteğinin hemen ardından HTTP 500 ya da süreç çökmesi |
Erişim logu + Node/süreç yöneticisi logu | Bilinen giriş yolları bu iki adımı izliyor |
| Node worker'larının SIGSEGV (signal 11) ile çökmesi, PM2/systemd/konteyner yeniden başlatma sayısında ani artış | PM2 logu, journald, dmesg | Bellek bozulmasının en net belirtisi (ve uzaktan DoS) |
| Aynı kaynaktan kısa sürede art arda HTTP 500 yanıtları | Erişim logu, WAF, reverse proxy | Bellek düzenine dayalı denemeler çoğu zaman tekrar gerektirir |
Dışarı yapılan çağrıların döndüğü anormal/ikili içerik, beklenmedik Content-Encoding: br yanıtları |
Reverse proxy/istemci logları | Outbound bellek ifşası kanalı |
| Node sürecinden kabuk, betik yorumlayıcısı ya da indirme aracı başlatılması | EDR, auditd, Sysmon for Linux | Başarılı kod çalıştırmanın en net işareti |
| Web sunucusundan beklenmeyen dış bağlantılar | Firewall, NetFlow, EDR ağ telemetrisi | Reverse shell, C2 ya da veri sızdırma |
| Çalışma dizininde yeni veya değişmiş betik dosyaları, yeni cron/systemd servisi ya da hesap | FIM, auditd, EDR | Kalıcılık ve yetki yükseltme |
Örnek Sigma kuralı
Başarılı bir kod çalıştırmanın ardından en güvenilir sinyal, Node süreçlerinin kabuk ya da araç başlatması. Aşağıdaki kural Linux süreç oluşturma olayları için yazıldı ve kullanılan SIEM'e göre dönüştürülebilir:
title: Node.js sürecinden kabuk veya araç başlatılması
status: experimental
description: CVE-2026-111497 sonrası olası kod çalıştırma
logsource:
category: process_creation
product: linux
detection:
selection_parent:
ParentImage|endswith:
- '/node'
selection_child:
Image|endswith:
- '/sh'
- '/bash'
- '/dash'
- '/python3'
- '/perl'
- '/curl'
- '/wget'
- '/nc'
- '/ncat'
- '/socat'
condition: selection_parent and selection_child
falsepositives:
- child_process ile mesru olarak alt surec cagiran uygulamalar
level: high
Node uygulamaları child_process üzerinden meşru olarak alt süreç çağırabildiği için, kural devreye alınmadan önce ortamdaki normal davranış bir süre gözlemlenmeli ve istisnalar buna göre tanımlanmalıdır.
Microsoft Defender için KQL
Defender for Endpoint kullanan ortamlarda aynı mantık şu sorguyla geriye dönük olarak taranabilir:
DeviceProcessEvents
| where Timestamp > datetime(2026-10-20)
| where InitiatingProcessFileName in~ ("node", "node.exe")
| where FileName in~ ("sh", "bash", "dash", "python3", "perl",
"curl", "wget", "nc", "cmd.exe", "powershell.exe", "certutil.exe")
| project Timestamp, DeviceName, AccountName,
InitiatingProcessFileName, FileName, ProcessCommandLine
Sunucu üzerinde hızlı kontrol
EDR'ı olmayan ya da SIEM'e log göndermeyen sunucularda ilk kontrol için şu komutlar iş görür. Yollar dağıtıma ve kuruluma göre değişebilir:
# Coken Node surecleri (cekirdek mesajlari)
journalctl -k --since "2026-10-20" \
| grep -Ei "node.*(segfault|general protection)"
# Son 3 gunde calisma dizininde degisen betik dosyalari
find /srv/app -name "*.js" -mtime -3 -ls
# 500 donen POST/PUT isteklerinin kaynak IP dagilimi
awk '$6 ~ /POST|PUT/ && $9 == 500 {print $1}' /var/log/nginx/access.log \
| sort | uniq -c | sort -rn | head -20
Komut satırlarında /dev/tcp/, mkfifo, base64 -d gibi ifadeler ya da web kullanıcısı adına çalıştırılmış whoami, id, uname -a gibi keşif komutları görülmesi durumunda bu doğrudan olay olarak ele alınmalıdır.
Alarm geldiğinde
Sunucu ağdan izole edilmeli, ancak kapatılmamalıdır. Bellek ve çalışan süreçler delil değeri taşır.
Web sunucusu, Node süreç yöneticisi ve sistem logları, mümkünse bellek imajıyla birlikte koruma altına alınmalıdır.
Çalışma dizinindeki yeni dosyalar, yeni cron görevleri, systemd servisleri, SSH anahtarları ve kullanıcı hesapları incelenmelidir.
Sunucunun bağlandığı dış adresler çıkarılmalı ve aynı adreslerin diğer varlıklarla iletişimi SIEM üzerinde aranmalıdır.
Sistem temiz bir imajdan, yamalı Node sürümüyle yeniden kurulmalıdır. Sızılmış bir sunucuyu yalnızca yamalamak yeterli değildir.
Son olarak bellek ifşası ihtimali göz ardı edilmemelidir. Bu açık kod çalıştırma izi bırakmadan da veri sızdırabildiği için, yamanın gecikmeli uygulandığı internete açık sistemlerde veritabanı parolalarını, API anahtarlarını, uygulama sırlarını ve gerekiyorsa TLS sertifikalarını yenilemek makul bir önlem olacaktır.
Umut Yıldız
Siber Güvenlik Mühendisi