21 Ekim 2026’da yayımlanan PHP güvenlik güncellemeleri, PHP2Shell adıyla gündeme gelen iki kritik açığı kapattı: CVE-2026-109618 ve CVE-2026-109619. İkisi de uygun koşullarda kimlik doğrulama gerektirmeden uzaktan kod çalıştırmaya ulaşabiliyor. CVSS v4.0 puanları 9.2. Teknik tablo, tek bir hatalı endpoint’ten daha derin: istek belleğinin, hata işleme mekanizmasının ve uzun yaşayan worker durumunun birbirleriyle ilişkisi bozuluyor. PHP Foundation duyurusu
Ben bu olayı üç sınır üzerinden okuyorum: bir verinin ne zaman geçerli sayıldığı, ne kadar süre yaşadığı ve hata oluştuğunda hangi kodun hâlâ çalışmaya devam ettiği. Bu sınırlar doğru çizilmediğinde, normalde yalnızca başarısız olması gereken bir HTTP isteği bellek güvenliği problemine dönüşebiliyor.
Bilgi kesiti: 22 Ekim 2026. Aşağıdaki modeller açıklama amaçlıdır; PHP kaynak kodundan alınmış bir exploit veya birebir uygulama değildir. Ortama özgü sömürülebilirlik ile güncelleme gerekliliğini ayrı değerlendiriyorum.
Önce operasyonel gerçek: Hangi sürümler yamalı?
Desteklenen dallar için 21 Ekim güvenlik sürümleri şöyle:
| PHP dalı | Bu iki açık için ilk yamalı sürüm |
|---|---|
| 8.2 | 8.2.35 |
| 8.3 | 8.3.36 |
| 8.4 | 8.4.27 |
| 8.5 | 8.5.12 |
Bu tablo upstream sürümleri gösteriyor. Dağıtımınız daha eski görünen bir sürüme güvenlik düzeltmesini backport etmiş olabilir; karar paket sağlayıcısının bu iki CVE’yi kapattığını belirten duyurusuyla verilmelidir. PHP 8.1 ve daha eski dallar için PHP projesinin normal güvenlik desteği yok. Bazı eski dallara yayımlanan sınırlı kaynak yamaları, o dalların yeniden desteklendiği anlamına gelmiyor. Resmî PHP güvenlik duyurusu
Bir güncelleme kaydını kapatırken arayacağım kanıt yalnızca bir sürüm ekranı olmaz: hangi paketin düzeltmeyi içerdiği, hangi instance’lara dağıtıldığı ve çalışan süreçlerin yeni binary ile başladığı birlikte doğrulanmalı.
İstek biter, worker yaşamaya devam eder
Bir web runtime’ını yalnızca request → response olarak düşünmek eksik kalıyor. Bellek sahipliği açısından iki ayrı zaman ölçeği var: isteğe ait nesnelerin ömrü ve o istekleri işleyen sürecin ömrü.
Worker ömrü
├─ İstek A: başlat → ayrıştır → uygulama → temizle
├─ İstek B: başlat → ayrıştır → uygulama → temizle
└─ İstek C: ...
Güvenlik invariantı:
İstek belleği serbest bırakıldığında,
worker içinde o belleğe kullanılabilir referans kalmamalı.
Buradaki invariant, sistemin her durumda doğru kalması gereken özelliği. Bir isteğin hata vermesi bu sözleşmeyi ortadan kaldırmıyor. Özellikle hata yolu; yarım kalmış nesneleri, daha önce yayımlanmış referansları ve sırayla çalışması beklenen temizleme adımlarını hesaba katmalı.
Bu benim için kod incelemesinde çok somut bir soruya dönüşüyor: Bu iki satırın arasına bir başarısızlık koyarsam sistem hangi durumda kalır? Yalnızca fonksiyonun dönüş değerine bakmak yerine, o noktada kimlerin hangi veriye ulaşabildiğine bakmak gerekiyor.
CVE-2026-109618: OOM, neden yalnızca bir kapasite problemi değil?
İlk açıkta PHP’nin bellek tükenmesi yolunda kullandığı longjmp(), yürütmeyi verinin henüz tam başlatılmadığı bir noktadan çıkarabiliyor. Temizleme kodu daha sonra bu geçici durumu geçerli veri gibi kullanabiliyor. Sorunu tetikleyen tahsisin, bozuk durumda kalan nesneyle doğrudan ilişkili olması da gerekmiyor.
Multipart ayrıştırmada metadata maliyeti bu tabloyu ağırlaştırabiliyor: çok sayıda parça başlığı ve iç içe alan yapıları, ağda taşınan boyuttan daha pahalı bir bellek temsili oluşturabiliyor. Kullanılabilir içerik uzunluğu bulunmayan istekler de ilk boyut kontrolünün varsayımlarını etkileyebiliyor.
Yama, OOM anındaki bu güvensiz sıçramayı değiştiriyor: uygun VM güvenli noktasında geciktirilmiş soft OOM; ikinci, daha yüksek eşikte ise _exit() ile hard OOM uygulanıyor. Böylece yarım durumun normal temizleme yolunda tüketilmesi önleniyor. CVE-2026-109618 teknik duyurusu
Bu tasarımdan çıkardığım mühendislik dersi şu: kaynak sınırını aşan bir işi durdurmak ile onu güvenli bir noktada durdurmak farklı garantiler. Bir hata mekanizması, başarısız işlemi durdururken temizleme kodunun varsayımlarını bozabiliyor. Dolayısıyla hata yönetimi de saldırı yüzeyinin bir parçası olarak incelenmeli.
CVE-2026-109619: Serbest bırakılan bellek, unutulmayan referans
İkinci açıkta isteğe ait durum, belleği serbest bırakıldıktan sonra worker içinde referanslanabilir kalabiliyor. Aynı worker’ın sonraki isteği, normal başlatma o durumu sıfırlamadan önce referansı kullanırsa use-after-free oluşuyor. Teknik duyuru bunu bir producer ve consumer ilişkisiyle açıklıyor. FPM için uygulama yürütmesiyle durum üreten ve sonraki istekte session upload progress ile tüketen bir yol da doğrulanmış durumda. CVE-2026-109619 teknik duyurusu
Aşağıdaki şema, bellek sahipliğini anlatan kavramsal bir model:
İstek A
request_state oluştur
worker_reference = request_state
beklenmeyen hata → referansı temizleyen adım tamamlanamaz
istek belleği serbest bırakılır
Aynı worker'daki istek B
erken çalışan işlem → worker_reference kullanılır
normal reset henüz gerçekleşmemiştir
Sorun: referans var; referansın gösterdiği nesnenin ömrü bitmiş.
Burada yalnızca free() çağrısının doğruluğunu sorgulamak yetmez. Belleğin sahipliği değişirken yayımlanmış referansların da geçersizleştirilmesi gerekir. İyi bir yaşam döngüsü incelemesi; oluşturma, görünür kılma, kullanım, sıfırlama ve serbest bırakma adımlarının bütününe bakar.
mod_php ile PHP-FPM neden aynı değerlendirilmemeli?
PHP, web isteği karşılayan FPM, mod_php, CGI, FrankenPHP ve LiteSpeed kurulumlarının güncellenmesini istiyor. Özellikle mod_php için uygulama kodu başlamadan tetiklenebilen kimlik doğrulamasız bir saldırı gösterilmiş durumda. Burada Laravel middleware’i veya WordPress giriş kontrolü, runtime’ın daha önce işlediği veriye zamanında müdahale edemeyebilir. PHP’nin risk kapsamı
FPM’nin başlangıç başarısızlığında child süreci sonlandırması, belirli bir mod_php akışının aynen devam etmesini engelliyor. Bu davranış bütün uygulama ve extension yollarını güvenli yapmıyor. OOM duyurusundaki kaynak analizi ile stale-state duyurusundaki doğrulanmış FPM yolu aynı kanıt seviyesini ve aynı akışı anlatmıyor. OOM kapsamı, stale-state kapsamı
Benim değerlendirme matrisim bu nedenle tek bir “PHP kullanılıyor mu?” sütunundan oluşmaz:
| Boyut | Toplanacak kanıt |
|---|---|
| Runtime | Çalışan paket, sağlayıcı düzeltmesi, süreç başlangıç zamanı |
| SAPI ve worker | mod_php/FPM/LSAPI, havuzlar, yeniden kullanım davranışı |
| İstek yolu | Proxy’den PHP’ye ulaşan gövde, etkin limitler, erken ayarlar |
| Uygulama | Upload/import, callback, hata ve cleanup akışları |
| Yetki sınırı | Worker’ın okuyabildiği sırlar, yazabildiği dizinler, ağ erişimi |
HTTP gövde boyutu, heap maliyetinin üst sınırı değildir
Gövdeyi 8 MB ile sınırlamak, çalışma belleğini de 8 MB ile sınırladığımız anlamına gelmez. Bir parser; byte dizisini anahtarlar, değerler, diziler ve metadata nesnelerine dönüştürürken yeni tahsisler yapar. Bunu analiz etmek için kullandığım basit maliyet modeli şöyle:
M_toplam ≈ B_gövde
+ N_alan × C_alan
+ N_başlık × C_başlık
+ M_iç_içe_yapı
+ M_geçici_kopyalar
Bu bir PHP benchmark sonucu veya kesin heap formülü değil; savunma tasarlarken sorulacak soruları görünür kılan bir model. Aynı byte bütçesi içinde nesne sayısı artabiliyorsa, yalnızca toplam boyutu ölçmek eksik bir kontrol olabilir. Ben bu yüzden test planına gövde boyutunun yanında alan sayısını, başlık sayısını, derinliği ve ayrıştırma sonrası bellek tüketimini de koyarım.
Normal PHP yapılandırmasında post_max_size, POST verisinin boyutunu; memory_limit ise script’in tahsis edebileceği belleği sınırlar. Bu iki değer farklı kaynakları ölçer. Konfigürasyon ilişkileri için PHP çekirdek direktifleri belgesi esas alınmalı; bir limitin varlığı başka bir kaynağın sınırlandığını tek başına kanıtlamaz.
Geçici önlemler: Ayarın yeri de değeri kadar önemli
PHP’nin duyurusunda session.upload_progress.enabled=0; PHP 8.5 için ayrıca fatal_error_backtraces=0 belirli doğrulanmış yolları daraltan geçici önlemler arasında. Worker’ların yeniden başlatılması gerekiyor. Session önlemini uygulama içindeki ini_set() veya FPM .user.ini ayarına bırakmak, erken ayrıştırma aşaması için yeterli değil. Bu önlemler bütün varyantları kapatmıyor. Resmî geçici önlemler
.user.ini zaten ayrı bir konfigürasyon mekanizması: CGI/FastCGI bağlamında çalışıyor ve varsayılan yeniden okuma aralığı 300 saniye. Bu da “dosyayı değiştirdim” ile “bütün süreçler doğru ayarı doğru aşamada kullanıyor” ifadelerini birbirinden ayırmak için ek bir neden. PHP .user.ini belgesi
memory_limit=-1 önerisi de bağlamından koparılmamalı. OOM duyurusu bunu kontrollü PHP limit aşımını kaldıran kısmi bir önlem olarak açıklıyor; alttaki bellek tahsisi yine başarısız olabilir. OOM önleminin sınırı
Normalde bellek kullanımını sınırlayan bu değeri kaldırmak, kapasite planını da değiştirir. Ben böyle bir geçişi; host kapasitesi, worker başına tüketim ve işletim sistemi seviyesindeki sınırlar değerlendirilmeden standart bir kopyala-yapıştır çözümü olarak önermem. Hedef, bir RCE yolunu daraltırken bütün sunucuyu erişilebilirlik problemine sürüklememek olmalı.
FPM’deki pm.max_requests, bir child’ın kaç istekten sonra yeniden oluşturulacağını belirler. Çok düşük değerler süreç yenileme maliyetini artıracağı için trafik ve kapasite ölçümleriyle değerlendirilmelidir; worker yenilemek, düzeltilmiş runtime’ın yerine konmamalı. FPM yapılandırma belgesi
WordPress güncel olabilir; altında çalışan PHP eski kalabilir
WordPress’in açıklaması net: sorun WordPress’e özel değil ve bu iki açık için ayrı bir WordPress güncellemesi planlanmıyor. Runtime sürümü panelde Araçlar → Site sağlığı → Bilgi → Sunucu altında görülebiliyor. Yani WordPress çekirdeğinin, eklentilerin ve temanın güncel olması PHP yamasının uygulandığını göstermiyor. WordPress resmî duyurusu
Birden fazla domain, havuz veya container bulunan ortamda bunu her yürütme bağlamı için kaydetmek gerekir. Ben aşağıdaki küçük teşhis çıktısını yalnızca erişimi kısıtlanmış bir tanılama bağlamında kullanırım; herkese açık bir endpoint olarak bırakmam:
<?php
// Yalnızca kontrollü tanılama bağlamında çalıştırın.
// CLI sonucu, web isteğini işleyen PHP'nin kanıtı değildir.
echo json_encode([
'version' => PHP_VERSION,
'sapi' => PHP_SAPI,
'loaded_ini' => php_ini_loaded_file(),
'memory_limit' => ini_get('memory_limit'),
'upload_progress' => ini_get('session.upload_progress.enabled'),
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
Çıktı bir envanter girdisidir; zafiyet tarayıcısı değildir. Backport durumunu söylemez, diğer havuzların ayarını göstermez ve eski worker’ların tamamen kapandığını kanıtlamaz. İşletim ekibinden isteyeceğim kayıt; domain → handler → paket → süreç → yama kanıtı zinciridir.
WAF koruması ve origin’e ulaşan gerçek trafik
Cloudflare, bu açıklar için request ve response sinyallerini birlikte değerlendiren “full transaction detection” yaklaşımını duyurdu; korumalar varken de PHP’nin yamalanmasını istiyor. Bu önemli bir ayrım: sınırdaki filtre çalışma zamanının bellek sahipliği hatasını düzeltmiyor. Cloudflare teknik açıklaması
Benim buradan çıkardığım kontrol listesi şu: hangi hostname proxy’den geçiyor, alternatif origin adresi dışarıya açık mı, başka porttan aynı uygulamaya erişiliyor mu, staging aynı backend’e ulaşıyor mu? WAF’ın koruduğu trafik kümesiyle runtime’a ulaşabilen trafik kümesi eşleşmiyorsa, paneldeki koruma göstergesi ortamın tamamını temsil etmez.
Yamayı kurmak ile olayı kapatmak arasında
Üretim müdahalesini ben şu kanıtlarla tamamlarım:
- Envanter: İnternete açık uygulamalar, PHP handler’ları, havuzlar ve imajlar eşleştirilmiş olmalı.
- Yama: Paket sağlayıcısının iki CVE için düzeltme kanıtı ve dağıtılan paket kimliği kayda alınmalı.
- Çalışan süreç: Eski süreçlerin sonlandığı, yeni binary’nin trafik aldığı ve bütün instance’ların kapsandığı doğrulanmalı.
- Regresyon: Giriş, upload/import, zamanlanmış işler, API’ler ve extension bağımlılıkları kontrollü test edilmeli.
- İzleme: Proxy, web sunucusu, PHP ve süreç telemetrisi aynı zaman çizgisinde incelenmeli.
Hata artışı, worker çökmesi veya olağandışı süreç davranışı araştırma sinyalidir; tek başına başarılı sömürü kanıtı değildir. Tersi de geçerli: yalnızca erişim loglarında belirgin bir şey görmemekle olay kapatılmaz. Müdahale kaydı, gözlemin kapsadığı sistemleri ve zaman aralığını açıkça söylemeli.
Bir ihlal şüphesi varsa benim tercih edeceğim akış, adli izleri koruyup olay müdahalesini başlatmak; erişilebilen sırları ve yetkileri o incelemenin kapsamına almaktır. Yama gelecekteki tetiklemeyi engellerken geçmişte ne olduğunu geriye dönük açıklamaz.
AI tarafındaki ders: Karmaşıklık bir güvenlik kontrolü sayılmamalı
Wiz, bellek yerleşimine göre uyarlama ve tekrarlı hata ayıklama gerektiren sömürü çalışmalarında frontier modellerin süreci hızlandırdığını anlatıyor. Foundation ise bu hata sınıfının izlerinin 2024’te görüldüğünü; uyumluluk, performans ve kaynak kısıtlarının düzeltme sürecini zorlaştırdığını açıklıyor. Wiz araştırması, Foundation’ın süreç değerlendirmesi
Benim çıkarımım, teknik karmaşıklığı kalıcı bir savunma gibi saymamak gerektiği. “Bunu çalışır saldırıya çevirmek çok zor” değerlendirmesi, araçlar ve otomasyon değiştiğinde hızla eskiyebilir. Savunma tarafında bunun karşılığı; daha kısa yama süresi, hata yollarını da kapsayan testler ve çalıştırdığımız runtime’a ilişkin doğrulanabilir envanterdir.
PHP2Shell’de beni en çok düşündüren nokta, normal istek akışı kadar başarısızlığın da tasarlanması gerektiği. Veri geçerli olmadan görünür olmamalı; ömrü bittiğinde erişilebilir kalmamalı; hata yolu bu iki garantiyi bozuyorsa sistem hâlâ güvenli sayılmamalı.
Bugünkü operasyonel karar ise açık: sağlayıcısı tarafından doğrulanmış PHP düzeltmesini çalışan web ortamına ulaştırmak ve bunu süreç seviyesinde kanıtlamak.
Serhat Diker sitesinden daha fazla şey keşfedin
Son gönderilerin e-postanıza gönderilmesi için abone olun.


