PHP'de İki Kritik RCE: CVE-2026-109618 ve CVE-2026-109619

PHP 8.5.12, 8.4.27, 8.3.36 ve 8.2.35, kimlik doğrulama gerektirmeyen uzaktan kod çalıştırmaya yol açabilen iki açığı kapatıyor. Açıkların nasıl çalıştığı, kimlerin riskte olduğu, sunucunda nasıl kontrol edeceğin ve yama gelene kadar resmi geçici önlemler.

21 Ekim 2026’da PHP güvenlik ekibi iki kritik açığı kapatan sürümleri yayınladı: 8.5.12, 8.4.27, 8.3.36 ve 8.2.35. İki açık da kimlik doğrulama gerektirmeden uzaktan kod çalıştırmaya (RCE) yol açabiliyor ve ikisi de CVSS v4.0 ile 9.2 puan aldı. Duyuru bir gün önce yapılmıştı; ayrıntılar aynı gün sızınca yama planlanan saatten biraz önce çıktı.

Bu yazıda iki açığı resmi bildirilere dayanarak açıklıyorum, kimin ne kadar riskte olduğunu ve yama gelene kadar neyin işe yaradığını topluyorum. Sonunda kendi sunucumda adım adım ne yaptığımı da yazdım. Saldırı tarifi yok; her şey savunma tarafından.

Kısaca

Açıklar CVE-2026-109618 (GHSA-w8hw-w9fx-j79w): RCE via OOM corruption
CVE-2026-109619 (GHSA-6wmf-4rv5-p3rp): RCE via stale global state
CVSS v4.0 9.2 (ikisi de) · AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
CWE CWE-416 (use after free), CWE-755, CWE-824, CWE-1325, CWE-1419
Etkilenen 8.2.35, 8.3.36, 8.4.27 ve 8.5.12 öncesi bütün sürümler
Düzeltilen 8.2.35 · 8.3.36 · 8.4.27 · 8.5.12
Kimler riskte Web isteği karşılayan bütün PHP kurulumları: PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, LiteSpeed. En yüksek risk mod_php’de.
Tek tam çözüm Yeni sürüme güncellemek. Geçici önlemler sadece belirli yolları kapatır.

Zaman çizelgesi

Zaman Ne oldu
20 Ekim php.net “Upcoming Critical PHP Security Release” duyurusunu yayınladı: iki kritik açık, CVSS v4.0 9.2, yayın 21 Ekim 14:00 UTC.
20 Ekim Ayrıntılar sızdı. Sansec ve Huntress aynı gün uyarı yayınladı; sızıntı “php2shell” adıyla dolaştı.
21 Ekim 13:34 UTC İki GitHub Security Advisory yayınlandı.
21 Ekim 13:48 UTC PHP CNA, iki CVE kaydını yayınladı.
21 Ekim 8.5.12, 8.4.27, 8.3.36 ve 8.2.35 yayınlandı. Linux dağıtımları ve LTS sağlayıcıları yamaları önceden almıştı.

CVE-2026-109618: OOM sırasında yarım kalan istek belleği

PHP bellek yetmediğinde (out-of-memory, OOM) çalışan bağlamdan longjmp() ile çıkıyor ve isteğin işlenmesini kesiyor. Advisory’ye göre bazı bağlamlarda istek belleği tam başlatılmadan erişilebilir hale geliyor. Tam o anda herhangi bir bellek ayırma başarısız olursa PHP bu erken çıkışı hesaba katmıyor ve istek belleği geçersiz durumda kalıyor. Başarısız olan ayırmanın o bellekle ilgili olması bile gerekmiyor. Sonra isteği kapatan kod (request shutdown) bu geçersiz veriyi okuyor ya da ona göre dallanıyor.

Saldırganın işi, isteği PHP’nin görünen sınırları içinde kalırken OOM’a sürüklemek. Advisory multipart ayrıştırmada bunu mümkün kılan birkaç yol sayıyor:

  • Kullanılabilir bir Content-Length olmayan multipart istek ilk post_max_size kontrolünü geçip gövde göndermeye devam edebiliyor.
  • Bir multipart parçası sınırsız sayıda header taşıyabiliyor. Bu header’ların PHP içindeki karşılığı, kablodaki boyutlarından çok daha fazla bellek tutuyor.
  • Boş dosya alanları max_file_uploads sınırına sayılmıyor ama yine dosya bilgisi dizileri oluşturuyor. İç içe alan adları bu işi katlıyor.

SAPI’ye göre fark

Burada hangi SAPI’yi kullandığın belirleyici:

  • Apache mod_php: İstek başlatma (request startup) başarısız olsa bile shutdown her zaman çalışıyor. Otomatik gövde ayrıştırması bellek sınırını tam o geçersiz anda aşabiliyor ve bütün bu süreç uygulama kodu çalışmadan önce oluyor. Tam RCE, varsayılan PHP girdi ve bellek sınırlarıyla mod_php üzerinde HTTP üzerinden doğrulanmış.
  • PHP-FPM: Startup başarısız olunca FPM child sürecini kapatıyor; geçersiz durum kullanılmadan süreç havuzdan çıkıyor. Ama normal startup’tan sonra da OOM’a ulaşılabiliyor. Kaynak kod analizi FPM’in de etkilendiğini gösteriyor, sadece FPM için tam bir exploit gösterilmemiş.
  • FrankenPHP: mod_php gibi shutdown’ı koşulsuz çağırıyor, bu yüzden uygulamadan bağımsız saldırılara muhtemelen açık.
  • LSAPI: FPM’e benziyor; uygulamaya özel exploit’ler muhtemel ama doğrulanmamış.

Yama longjmp() ile yapılan OOM çıkışını kaldırıyor. Yerine bir sonraki güvenli noktada devreye giren yumuşak bir OOM ve bellek ikinci, daha yüksek bir eşiğe ulaşırsa _exit() ile anında sonlanan sert bir OOM geliyor.

CVE-2026-109619: İstekten isteğe taşınan bayat durum

PHP’nin bir worker süreci birden çok isteği sırayla karşılıyor. Bu sırada hata, session, nesne ve extension gibi bazı global durumlar worker’da kalıyor ama bir isteğin belleğini gösteriyor. Advisory’ye göre bu durum, istek belleği bırakılmadan önce sıfırlanmalı. Sıfırlanmazsa bir sonraki istekte normal başlatmadan önce çalışan bazı işlemler serbest bırakılmış belleğe erişiyor: klasik bir use after free.

Açığın çalışması için iki parça gerekiyor:

  1. Üretici (producer): İsteğe ait durumu global bir yere yazan ve fatal error ya da OOM yüzünden sıfırlamayı atlayan bir işlem. Parse hataları, yakalanmamış hatalar, başarısız require, Maximum execution time exceeded bu kesintiye örnek.
  2. Tüketici (consumer): Aynı worker’ın sonraki isteğinde, normal başlatmadan önce bu bayat değeri okuyan bir işlem.

Advisory iki tüketiciyi doğruluyor:

  • İstek başlamadan önce gelen INI uyarıları. Geçersiz ya da kullanımdan kalkmış bir ayar başlatma sırasında uyarı üretirse bayat hata durumu kullanılıyor. Uyarının gösterilmesi ya da loglanması gerekmiyor. error_reporting, @ ya da display_errors bu yüzden koruma sağlamıyor.
  • Session upload progress. Upload progress, session’ı normal başlatmadan önce işliyor ve kesintiye uğramış bir isteğin bıraktığı session durumunu kullanabiliyor.

Doğrulanmış iki somut durum var:

  • PHP 8.5’te fatal_error_backtraces: Açıkken fatal error raporlaması isteğe ait bir backtrace saklıyor ve shutdown bunu hiç sıfırlamıyor. Doğrulanmış mod_php exploit’i bunun üzerine kurulu.
  • Session upload progress yaşam döngüsü: Mod_php’de üretici ve fatal error multipart startup sırasında oluşabiliyor. FPM’de doğrulanmış yol farklı: durum uygulama çalışırken yazılıyor, fatal error geliyor ve upload progress erken tüketici oluyor. Normal dinamik bir FPM havuzunda çalışıyor, ama uygun bir uygulama işlemine bağlı.

FPM’de INI yolu ne zaman açık?

Bu kısım FPM kullananlar için önemli. Advisory’ye göre php.ini ve FPM pool değerleri worker başlarken uygulanıyor; .user.ini ise normal başlatmadan sonra. Bunlar erken tüketici sağlamıyor. Ama nginx ya da Apache mod_proxy_fcgi, FPM’e PHP_VALUE / PHP_ADMIN_VALUE parametresi gönderiyorsa ve bu değerler başlatmadan önce bir uyarı üretiyorsa FPM de bu yoldan açık.

CloudPanel gibi paneller her siteye fastcgi_param PHP_VALUE ile memory_limit, post_max_size, date.timezone gibi değerler gönderiyor. Bu değerler geçerliyse uyarı oluşmuyor. Kullanımdan kalkmış ya da geçersiz bir değer varsa risk büyüyor.

Kimler riskte?

  • En yüksek risk: Apache mod_php ve FrankenPHP. Saldırı uygulama kodundan önce ve uygulamadan bağımsız çalışabiliyor.
  • Orta risk: PHP-FPM ve LSAPI. Doğrulanmış yollar var, ama çoğu uygulamaya ya da yapılandırmaya bağlı. Bu, güncellemeyi erteleme sebebi değil.
  • 8.1 ve öncesi: PHP projesi artık bu sürümlere güncelleme yayınlamıyor. Bu kez istisna olarak 7.4, 8.0 ve 8.1 için kaynaktan derleyenlere küçük backport’lar verildi (PHP-7.4, PHP-8.0, PHP-8.1 dalları). Dağıtım paketi kullanıyorsan sağlayıcına sor ya da desteklenen bir sürüme geç.
  • 8.2: 31 Aralık 2026’da destek dışı kalıyor. Bu yamayı kur, sonra yükseltmeyi planla.

Etki, web worker’ının hesabı ile kod çalıştırma. Yani saldırgan, PHP’nin çalıştığı kullanıcının yetkileriyle sunucunda komut çalıştırabilir.

Sunucunda nasıl kontrol edersin?

# Kurulu sürümler ve paket sürümleri (Debian/Ubuntu)
ls /etc/php/
php -v
dpkg -l | grep -E '^ii  php[0-9.]+-(fpm|cli|common) '

# Gerçekten çalışan FPM servisleri
systemctl list-units --type=service 'php*-fpm*'

# Apache mod_php var mı? (en yüksek risk)
apache2ctl -M 2>/dev/null | grep -i php

# Web sunucusu FPM'e PHP_VALUE gönderiyor mu?
grep -rn "PHP_VALUE\|PHP_ADMIN_VALUE" /etc/nginx/ /etc/apache2/ 2>/dev/null

# Depoda yeni sürüm var mı?
apt-get update && apt-cache policy php8.4-fpm

CLI ile FPM’in ayarları farklı olabilir. FPM’in gerçekte ne kullandığını görmek için php-fpm8.4 -i kullan.

Güncelleme

Advisory’ye göre Linux dağıtımları ve LTS sağlayıcıları yamaları önceden aldı, yani dağıtım paketleri hızlı gelmeli. Bildiride iki not daha var: gönüllülerin yönettiği rpms.remirepo.net deposu bugün güncellenmeyecek, bu depoyu kullanıyorsan dağıtım paketlerine geç ya da kaynaktan derle. Windows ikili dosyaları php.net indirme sayfasında.

# Önce sitelerinin durumunu kaydet
for d in site1.com site2.com; do echo "$d $(curl -s -o /dev/null -w '%{http_code}' https://$d/)"; done

# Sadece kurulu PHP paketlerini güncelle, ayar dosyalarını koru
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install --only-upgrade -y \
  -o Dpkg::Options::=--force-confold $(dpkg -l | awk '/^ii  php/{print $2}')

# Doğrula
php8.4 -v
sudo php-fpm8.4 -t && systemctl is-active php8.4-fpm

Docker: Resmi php imajlarında etiketi yeni sürüme çek (php:8.4.27-fpm gibi) ve konteynerleri yeniden oluştur. 8.4-fpm gibi kayan etiketler ancak docker pull sonrası güncellenir.

Paylaşımlı hosting: Erişimin yoksa firmana bugün yaz: hangi sürümde olduğunu ve yamanın ne zaman kurulacağını sor.

Güncelleyemiyorsan: resmi geçici önlemler

php.net bildirisi açık: bunlar sadece belirli saldırı yollarını kapatır, kurulumu güvenli yapmaz.

CVE-2026-109618 (OOM) için:

  • memory_limit=-1 ayarla ve uygulamanın değiştiremeyeceği bir yerde zorla, örneğin FPM pool’da php_admin_value. Hiçbir pool, dizin ya da uygulama ayarının sınırı geri getirmediğini kontrol et. Bu, kontrol edilebilir OOM’u kaldırıyor ama alttaki bir mmap() hatası yine aynı yola gidebilir. Ayrıca sınırsız bellek, paylaşımlı bir sunucuda kendi başına bir denial of service riski demek; bunu bilerek yap.
  • post_max_size’ı düşük tut: dosya yüklemeyen uygulamalar için 8M, yükleme gerekiyorsa 32M. upload_max_filesize her zaman bunun altında kalsın.
  • Web sunucusunda ya da proxy’de çözülmüş gövde boyutu, alan sayısı, multipart parça sayısı, header sayısı ve iç içe geçme derinliği için sınır koy. Bu sınırlar Content-Length olmayan istekleri de kapsamalı. Uygulaman multipart form kullanmıyorsa bu istekleri PHP’ye ulaşmadan engelle.
  • Konteyner ya da cgroup bellek sınırı bir emniyet çizgisi olabilir. RLIMIT_AS ya da RLIMIT_DATA kullanma: bunlar tam da açığın tetiklendiği ayırma hatasını üretir.

CVE-2026-109619 (bayat durum) için:

  • session.upload_progress.enabled=0 ayarla ve bütün worker’ları yeniden başlat. Ayarı php.ini’ye, FPM pool’a ya da istek ayrıştırılmadan önce uygulanan sunucu yapılandırmasına yaz. ini_set() ya da FPM .user.ini işe yaramaz. Bu, doğrulanmış session yollarını kapatır, diğer varyantları kapatmaz.
  • PHP 8.5’te fatal_error_backtraces=0 ayarla ve bütün worker’ları yeniden başlat. Bu sadece doğrulanmış backtrace yolunu kapatır.
  • İstek başına uygulanan geçersiz ya da kullanımdan kalkmış ayarları kaldır (.htaccess, Apache php_value, nginx PHP_VALUE).
  • Saldırganın tetikleyebildiği fatal error ve zaman aşımlarını düzelt: callback’ler, yükleme ve içe aktarma işleri, cleanup.
; /etc/php/8.5/fpm/conf.d/99-gecici.ini
session.upload_progress.enabled = 0
fatal_error_backtraces = 0   ; sadece PHP 8.5
sudo php-fpm8.5 -t && sudo systemctl reload php8.5-fpm
php-fpm8.5 -i | grep -E '^(session.upload_progress.enabled|fatal_error_backtraces)'

Cloudflare kuralı işe yarar mı?

Sızıntıdan sonra paylaşılan bir Cloudflare WAF kuralı, gövdesinde PHP_SESSION_UPLOAD_PROGRESS alanı geçen istekleri engelliyor:

any(http.request.body.form.names[*] == "PHP_SESSION_UPLOAD_PROGRESS")
or http.request.body.raw contains "PHP_SESSION_UPLOAD_PROGRESS"

Action: Block. Ama bu kural sadece upload progress tüketicisini hedefliyor; OOM açığını ve INI yolunu hiç kapsamıyor. İki sınırı daha var:

  • Cloudflare gövdenin sadece ilk kısmına bakıyor. Alan daha geride gelirse kural görmüyor.
  • Sunucuna IP adresiyle doğrudan ulaşılabiliyorsa saldırgan Cloudflare’den hiç geçmiyor.

İkincisini kendi sunucumda denedim ve kendi sitem dahil bütün siteler doğrudan IP’yle açılıyordu. İlk denemede ana sitem kapalı görünmüştü, çünkü sunucuda self-signed sertifika var ve curl bağlantıyı reddetti. -k ile sertifika kontrolünü atlayınca 200 döndü. Saldırgan da sertifikaya bakmaz:

curl -sk -o /dev/null -w "%{http_code}\n" --resolve siten.com:443:SUNUCU_IP https://siten.com/

200 dönüyorsa origin’in açık. Çözüm: sunucuda sadece Cloudflare IP aralıklarını kabul et ya da Authenticated Origin Pulls kullan.

Kendi sunucumda ne yaptım?

Sunucum Ubuntu 26.04 üzerinde CloudPanel. Bütün siteler nginx arkasında PHP-FPM ile çalışıyor; Apache ya da mod_php yok.

Saat (TSİ) Adım
11:26 Envanter: 7.1’den 8.5’e on sürüm kurulu, ama sadece 8.4 ve 8.5’in FPM servisi çalışıyor. Sürümler 8.4.22 ve 8.5.9.
11:26 Asıl sürpriz: 8.4’te acil yama gelmeden önce bile üç güvenlik sürümü gerideydim (8.4.23, 8.4.24, 8.4.26).
11:59 Bütün sitelerin HTTP durumunu kaydettim, sonra eldeki son sürümleri kurdum: 8.4.26 ve 8.5.11 (188 paket, yarım saatten uzun sürdü). Siteler aynı cevabı verdi.
12:01 8.4 ve 8.5’te session.upload_progress.enabled=0, FPM yeniden yüklendi.
16:48 Advisory’leri okudum. nginx’in her siteye PHP_VALUE gönderdiğini gördüm; değerlerin hepsi geçerli, open_basedir yok, yani INI uyarısı üretmiyorlar.
16:48 8.5’te fatal_error_backtraces açıktı. Kapattım, FPM’i yeniden yükledim, siteleri tekrar kontrol ettim.
17:29 8.5.12 CloudPanel deposuna düştü ve kuruldu. FPM yeniden başladı, geçici önlemler açık kaldı, bütün siteler aynı cevabı verdi.
bekliyor 8.4.27 depoda henüz yok. Düştüğü anda kuracağım ve bu satırı güncelleyeceğim.

memory_limit=-1 önlemini uygulamadım. Sunucuda birden çok site var ve sınırsız bellek tek bir kötü isteğin bütün sunucuyu yavaşlatmasına yol açabilir. FPM için tam exploit gösterilmemiş olması ve paketin yakında gelecek olması bu kararı verdirdi. mod_php kullansaydım farklı düşünürdüm.

Bunu kısa bir video olarak da anlattım: Pixel Shorts · PHP’ye acil yama.

Teşekkür

Advisory’lere göre açıkları lcfr-hax, sagitzadik-wiz, tomash-areion ve amyb-oai bildirdi. Düzeltmeleri arnaud-lb, iluuu1994, bukka ve ndossche geliştirdi.

Kaynaklar


Bu yazıyı Claude Code ile birlikte hazırladım.