Ne okumak istersin?

Bir konu, teknoloji veya merak ettiğin bir kelimeyle ara.

← Tüm yazılara dönYazılım Mühendisliği

PHP2Shell: OOM, Use-After-Free ve İstekler Arası Bellek Güvenliği

Bellek güvenliği yazısı için boş yonga alanını işaret eden prob ve bellek modülünün temsili yakın çekimi.

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.28.2.35
8.38.3.36
8.48.4.27
8.58.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:

BoyutToplanacak kanıt
RuntimeÇalışan paket, sağlayıcı düzeltmesi, süreç başlangıç zamanı
SAPI ve workermod_php/FPM/LSAPI, havuzlar, yeniden kullanım davranışı
İstek yoluProxy’den PHP’ye ulaşan gövde, etkin limitler, erken ayarlar
UygulamaUpload/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:

  1. Envanter: İnternete açık uygulamalar, PHP handler’ları, havuzlar ve imajlar eşleştirilmiş olmalı.
  2. 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ı.
  3. Çalışan süreç: Eski süreçlerin sonlandığı, yeni binary’nin trafik aldığı ve bütün instance’ların kapsandığı doğrulanmalı.
  4. Regresyon: Giriş, upload/import, zamanlanmış işler, API’ler ve extension bağımlılıkları kontrollü test edilmeli.
  5. İ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.