Bloga Dön

php2shell Zafiyeti: Oyun Sunucusu Koruması İçin 6 Saatlik Yama Runbook'u

Yayınlanma tarihi 23 Ekim 2026
php2shell Zafiyeti: Oyun Sunucusu Koruması İçin 6 Saatlik Yama Runbook'u Yapay zekâ yardımıyla oluşturuldu

Özet olarak

Önlem alın: php2shell'e karşı 6 saatlik yama runbook'u ile tüm PHP runtime'larınızı derhal envantere alın, yamalayın ve saldırı yüzeyini küçültün.

Oyununuzun PHP katmanı, muhtemelen stack'inizde kimsenin bakmadığı kısımdır. Kozmetik satan web mağazası. Topluluk yöneticinizin kullandığı admin paneli. 2022'de yazıp bir daha hiç dokunmadığınız eski leaderboard endpoint'i. php2shell'in saldırdığı yüzey işte bu.

21 Ekim 2026'da Calif.io, Wiz, OpenAI ve Quarkslab, topluca php2shell olarak adlandırılan CVE-2026-109618 ve CVE-2026-109619 olarak izlenen iki PHP runtime güvenlik açığını ifşa etti. CVSS v4.0 onları 9.2 olarak puanlıyor. PHP 5.4'ten itibaren tüm PHP kurulumlarını etkiliyorlar; kimlik doğrulamasız uzaktan kod çalıştırmaya, veri ifşasına ve hizmet reddine olanak tanıyorlar. Cloudflare, kamuya duyurudan önce bile gerçek müşterilere karşı mutasyona uğrayan payload'lar içeren canlı saldırı trafiği gözlemledi.

Bu bir WordPress eklentisi sorunu değil. Bu, PHP runtime'ının kendisindeki bir bellek güvenliği hatası. Framework'ünüzü, CMS'inizi veya Composer bağımlılıklarınızı güncellemek hiçbir şey yapmaz. İçinde herhangi bir PHP olan bir oyun backend'i işletiyorsanız, bu runbook sizin için.

php2shell Aslında Neyi Bozar

Her iki CVE de PHP'nin istek işleme ters gittiğinde belleği nasıl yönettiğiyle ilgilidir. Normalde PHP bir istek için bellek ayırır, kullanır ve istek bittiğinde serbest bırakır. Hatalar, bu temizleme yolu baştan beri hiç geçerli olmamış bir duruma karşı çalıştığında ortaya çıkar.

CVE-2026-109618: out-of-memory kaynaklı bellek bozulması

PHP, istek sırasında bellek tavanına ulaşır ve bazı iç yapılar başlatmayı bitirmeden işlemi durdurur. Temizleme rutini daha sonra bu yarı oluşturulmuş veriyi meşru kabul eder. OOM koşullarını güvenilir şekilde zorlayabilen bir saldırgan — büyük istek gövdeleri, derin iç içe girdiler, aşırı büyük yüklemeler — bu tamamlanmamış belleğin içeriğini kontrol etme şansı elde eder.

Oyun backend'leri için pratik tetikleyici açıktır: boyut sınırı olmadan gövde kabul eden herhangi bir endpoint. Maç sonucu gönderimi, ekran görüntüsü yükleme, mod meta verisi, telemetri grupları.

CVE-2026-109619: istekler arasında bayat global durum

Ölümcül bir hata bir isteği keser ve PHP sürecinin, istek sonunda serbest bırakılan belleğe referanslar tutmasına neden olur. Aynı worker süreci sonraki isteği aldığında bu sarkan referansları izleyebilir. Bu bir veri ifşasıdır — bir oyuncunun oturum verisi veya veritabanı kimlik bilgilerinin başka bir oyuncunun yanıtına sızması — ve doğru koşullar altında kod çalıştırmadır.

Bu, oyun sunucuları için daha sinsi bir hatadır çünkü PHP-FPM worker'ları tasarım gereği uzun ömürlüdür. Tek bir zehirlenmiş worker, geri dönüştürülmeden önce yüzlerce sonraki isteğe hizmet edebilir. pm.max_requests = 0 (varsayılan, yani asla geri dönüştürme) çalıştırıyorsanız, bozulmuş bir worker konteynerin ömrü boyunca kalıcı olabilir.

Adım 1: Sahip Olduğunuz Her PHP Runtime'ını Envantere Alın

Bulamadığınız şeyi yamalayamazsınız. Çoğu stüdyo, olayın ortasında hizmetlerinden üçünün PHP çalıştırdığını ve birinin iki yıl önce oluşturulmuş bir temel imgeden 8.1 üzerinde olduğunu keşfeder. Başka bir şey yapmadan önce bunu çalıştırın.

#!/usr/bin/env bash
# php2shell exposure inventory (CVE-2026-109618 / CVE-2026-109619)
set -euo pipefail

declare -A FIXED=( [8.5]="8.5.12" [8.4]="8.4.27" [8.3]="8.3.36" [8.2]="8.2.35" )
SERVICES=("game-api" "web-store" "admin-panel" "forums")

for svc in "${SERVICES[@]}"; do
  ver=$(docker compose exec -T "$svc" php -r 'echo PHP_VERSION;' 2>/dev/null | tr -d '[:space:]') || {
    echo "SKIP  $svc (no php binary)"; continue; }

  branch="${ver%.*}"          # "8.3.12" -> "8.3"
  min="${FIXED[$branch]:-}"

  if [[ -z "$min" ]]; then
    echo "EOL   $svc on $ver -> no upstream patch, upgrade required"
  elif php -r 'exit(version_compare($argv[1], $argv[2], ">=") ? 0 : 1);' "$ver" "$min"; then
    echo "OK    $svc on $ver"
  else
    echo "VULN  $svc on $ver (fixed in $min)"
  fi
done

Bu betiğin bilinçli olarak yaptığı iki şey var: EOL dallarını ayrıca işaretler ve host üzerindeki php -v çıktısının konteynerin gerçek runtime'ını yansıttığını asla varsaymaz. Kubernetes kullanıyorsanız, docker compose exec yerine kubectl exec koyun ve hizmet adları yerine deployment'lar üzerinde döngü yapın.

Düzeltilmiş upstream sürümleri 8.5.12, 8.4.27, 8.3.36 ve 8.2.35'tir. 8.1, 8.0, 7.x veya 5.x üzerindeki her şey ömrünü tamamlamıştır ve asla bir düzeltme almayacaktır. Yama yapmıyorsunuz, yükseltiyorsunuz.

Adım 2: Uygulamayı Değil, Runtime'ı Yamalayın

Böyle bir olayın ilk saatinde en yaygın hata modu, uygulama kodunu güncelleyen, yeniden deploy eden, yeşil CI gören ve işin bittiğini varsayan bir ekiptir. İş bitmemiştir.

  • PHP-FPM ve mod_php: temel imgeyi yeniden oluşturun veya paketi yükseltin. Debian/Ubuntu: apt-get install --only-upgrade php8.3-fpm php8.3-cli php8.3-common. Alpine: apk upgrade php83. Çalışan konteynerin içinde php -r 'echo PHP_VERSION;' ile doğrulayın, build host'unda değil.
  • FrankenPHP / php-cgi: bunlar runtime'ı paketler, bu yüzden bir paket yükseltmesine değil, yeni bir release binary'sine veya temel imgeye ihtiyacınız var.
  • Yönetilen barındırma: bir sağlayıcı PHP'yi kontrol ediyorsa, hemen bir ticket açın ve danışma referansını isteyin. "Farkındayız" yanıtını bir çözüm olarak kabul etmeyin.
  • Docker temel imajları: tag ile değil, digest ile sabitleyin. php:8.3-fpm bir sonraki pull'da farklı bir build'e çözülebilir; bu yamalama için harikadır, tekrarlanabilirlik için korkunçtur. Sürümü doğruladıktan sonra digest'i sabitleyin.

Yamaladıktan sonra mevcut tüm worker'ları öldürün. Eski süreçleri paylaşılan bir socket üzerinde canlı bırakan rolling restart, savunmasız worker'ların trafik sunmaya devam etmesine neden olur. docker compose restart veya kubectl rollout restart — süreç başlangıç zamanının değiştiğini doğrulayın.

Adım 3: Beklerken Patlama Yarıçapını Küçültün

Yamalama zaman alır. "Biliyoruz" ile "tüm ortamlar yeniden oluşturuldu" arasında bir pencereniz var. Bunu kullanın.

İstek gövdelerini uçta sınırlayın

# Shrink the php2shell trigger surface
client_max_body_size 2m;          # game APIs rarely need more than this
client_body_timeout 10s;
client_header_timeout 10s;
large_client_header_buffers 4 8k;

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_pass php-fpm:9000;
    fastcgi_read_timeout 15s;
    fastcgi_buffering on;
}

2 MB'lık bir sınır, kararlı bir saldırganı durdurmaz — birçok küçük istekle OOM tetikleyebilirler — ancak önemsiz tek istek tetikleyicisini ortadan kaldırır ve size hiçbir maliyeti olmaz.

Worker'ları agresif şekilde geri dönüştürün

; /usr/local/etc/php-fpm.d/zz-php2shell.conf
php_admin_value[memory_limit] = 256M
php_admin_value[max_execution_time] = 20
pm.max_requests = 500
pm.max_children = 24
request_terminate_timeout = 20s

İşte ödünleşim matematiği. 24 worker arasında saniyede 200 istekte, her worker kabaca 8,3 istek/sn işler. pm.max_requests = 500 ile her worker yaklaşık her 60 saniyede bir geri dönüştürülür. Bu, CVE-2026-109619 ile bozulmuş bir sürecin ne kadar süre trafik sunabileceğini sınırlar, ancak aynı zamanda dakikada 24 süreç doğuşu anlamına gelir — cold-start ağırlıklı framework'lerde ölçülebilir bir ek yük.

memory_limit değerini 128M'den 256M'ye çıkarmak OOM'ye ne sıklıkla ulaştığınızı azaltır (daha az CVE-2026-109618 tetikleyicisi) ancak en kötü durum belleğini ikiye katlar: 24 child × 256M = node'da artık ihtiyacınız olan 6 GB headroom. Node'unuz 4 GB ise, belleğin varmış gibi davranmak yerine pm.max_children değerini düşürün.

Bunların hiçbiri bir çözüm değil. Size saatler kazandırır. Bunu triyaj olarak görün.

Adım 4: Daha Önce Vurulup Vurulmadığınızı Tespit Edin

İfşa, ilk istismar anlamına gelmez. Resmi yayın tarihinden önce saldırı trafiği gözlemlendi. Yoklandığınızı varsayın.

# 1. OOM aborts — strongest single signal for CVE-2026-109618
grep -c "Allowed memory size" /var/log/php-fpm/error.log

# 2. Worker crashes — signal for both CVEs
journalctl -u php8.3-fpm --since "48 hours ago" \
  | grep -Ei "segfault|SIGSEGV|exited on signal 11"

# 3. Which paths are generating 500s
awk '$9 == 500 {print $7}' /var/log/nginx/access.log \
  | sort | uniq -c | sort -rn | head -20

# 4. Same source IP hitting the same 500-ing path repeatedly
awk '$9 == 500 {print $1, $7}' /var/log/nginx/access.log \
  | sort | uniq -c | sort -rn | head -20

Önce temel çizgiyi çıkarın. PHP-FPM günlüğünüzde zaten bellek açısından doyumsuz bir export endpoint'inden günde 40 OOM durması varsa, bu sayı gürültüdür. Aradığınız şey orandaki bir değişimdir — signal 11 çıkışlarında bir sıçrama, 500 listesinde yeni bir yolun görünmesi veya tek bir IP'nin bir endpoint'e karşı düzinelerce hata üretmesi.

Başarılı bir istismar kanıtı bulursanız, bunu tam bir ihlal olarak ele alın: veritabanı kimlik bilgilerini, oturum imzalama anahtarlarını, ödeme sağlayıcı sırlarını ve PHP sürecinden erişilebilen tüm API anahtarlarını yenileyin. Bunun playbook'u PHP'ye özgü değildir — bir stüdyonun oyun backend'lerini ihlallere dayanacak şekilde mimarileştirmesini sağlayan disiplin burada da geçerlidir ve saldırganın yalnızca okuma erişimine değil, kod çalıştırmaya sahip olduğunu varsaymakla başlar.

Backend'iniz zaten istekleri düşürüyorsa ve bunun yük mü yoksa saldırı mı olduğundan emin değilseniz, bu sunucu donması teşhis protokolündeki çökme triyaj sırası geçerlidir: yapılandırmaya dokunmadan önce arızalı katmanı izole edin.

Neden Yalnızca İstek Tabanlı WAF Kuralları Başarısız Oldu

Cloudflare'in yazısı, tüm olaydaki en aktarılabilir mühendislik dersini içeriyor. İlk korumaları istek tabanlıydı ve saldırganlar payload'ları mutasyona uğratarak yanıt verdi — hangi varyantların eşleştiğini ve hangilerinin kaçtığını yoklayarak, eşleşme/eşleşmeme sinyalini bir oracle olarak kullandı.

Yalnızca istek verisi, istismar trafiğini meşru trafikten ayıramazdı, çünkü iç içe diziler içeren büyük bir POST, normal bir oyun istemcisinin maç özeti gönderirken gönderdiği şeydir de. Belirsizliği çözen sinyal yanıttı: kaynak, PHP fatal error izi içeren bir 500 döndürdü mü? Bu kombinasyon — şüpheli istek artı teşhis yanıtı — yüksek güvenilirliktir. Yalnızca istek değildir.

Cloudflare bunu "tam işlem tespiti" olarak yayınladı: aynı HTTP işlemi içinde istek ve yanıtı ilişkilendirip yanıtı isteyene ulaşmadan önce engelliyor. Kendi edge'inizi veya reverse proxy'nizi çalıştırıyorsanız, bu deseni kopyalamaya değer:

def is_probable_php2shell(resp) -> bool:
    """Correlate request shape with origin response, not request alone."""
    body = resp.body[:4096]
    return (
        resp.status == 500
        and b"Fatal error" in body
        and resp.origin_latency_ms < 50      # exploit aborts fast
        and b"Allowed memory size" in body   # OOM path = 109618 signature
    )

Ders bu CVE'nin ötesine genelleniyor: yalnızca gelen trafiği gören herhangi bir tespit sistemi, giden sonucu görebilen bir saldırgan tarafından atlatılır. Yanıtları günlüğe kaydedin. Bunları ilişkilendirin. Bu, ayakta kalan bir kural ile bulmaca haline gelen bir kural arasındaki farktır.

En İyi Uygulamalar

  1. Bir programa göre yamalayın, ifşaya göre değil. "CVE yayınlandı" ile "vahşi ortamda istismar" arasındaki boşluk artık saatlerle, bazen negatif olarak ölçülüyor. Hiçbir şey duyurulmasa bile PHP temel imajlarını haftalık yeniden oluşturun ve bir geri alma yolu bulundurun.
  2. Production'da asla pm.max_requests = 0 çalıştırmayın. Sınırsız worker ömrü, her bellek güvenliği hatasını kalıcı bir tutunma noktasına dönüştürür. Oyun API'leri için 500–1000 mantıklı bir varsayılandır.
  3. Her istek gövdesini uçta sınırlayın. JSON API'leri için 2 MB, yüklemeler için 10 MB ve eşleşen bir content type'ı olmayan her şeyi reddedin. Bu, bellek baskısı istismarlarının tüm sınıflarını PHP'ye ulaşmadan öldürür.
  4. 5xx hataları için yanıt gövdelerini örneklemeli olarak günlüğe kaydedin. Kaynak yanıtlarınız observability stack'iniz için görünmezse tam işlem tespiti kuramazsınız. %10 örnekleyin ve 7 gün saklayın.
  5. İşlettiğiniz her runtime'ın envanterini tutun. Yalnızca PHP değil — Node, Python, Go, web katmanınızı ne çalıştırıyorsa. php2shell'e bir saatten kısa sürede yanıt veren stüdyoların bir hizmet kataloğu vardı. Bir gün sürenler Dockerfile'ları grep'liyordu.

Daha Büyük Ders: Web Katmanınız Saldırı Yüzeyidir

php2shell, oyun backend'lerinin hurda biriktiğinin bir hatırlatıcısıdır. Birinci yılda hızlı olduğu için bir PHP admin paneli yayınlarsınız. Üçüncü yılda hâlâ oradadır, hâlâ 8.1 üzerindedir, hâlâ oyuncu tablosuna yazma erişimi olan bir veritabanı kimlik bilgisi tutar — ve patlatılan şey, sağlamlaştırılmış oyun sunucu binary'niz değil, işte bu paneldir.

Yapısal çözüm, sahip olduğunuz runtime miktarını azaltmaktır. Bir PHP web katmanı işletmenizin tek nedeni oyuncu hesapları, cloud save'ler ve leaderboard'ları yönetmekse, bunlar tam olarak horizOn tarafından yönetilen API'ler olarak sunulan yeteneklerdir — hesaba bağlı oturumlarla son kullanıcı kimlik doğrulaması, istemcinin çakışmaları çözdüğü revizyon farkındalıklı okuma ve yazma ile Cloud Save, leaderboard'lar ve uzak yapılandırma. Gece 3'te yamalayacak bir PHP-FPM havuzu yok çünkü PHP-FPM havuzu yok.

Bu, yönetilen hizmetlerin güvenlik açıklarına karşı bağışık olduğu iddiası değil. Bu, işlettiğiniz her runtime'ın envanterini çıkarmanız, yamalamanız ve izlemeniz gereken bir runtime olduğu ve çoğu stüdyonun düşündüğünden daha fazlasına sahip olduğu iddiasıdır.

Bunu Şimdi Yapın

Bu makaleden tek bir eylem alacaksanız, bunu Adım 1'deki envanter betiği yapın. Her ortama karşı çalıştırın — production, staging, o yayıncı sunumundan kalma unutulmuş demo sunucusu. Neredeyse kesin olarak unuttuğunuz bir runtime bulacaksınız ve neredeyse kesin olarak bir EOL dalında olacak.

Sonra yamalayın, worker'ları geri dönüştürün ve uç kurallarını yerinde bırakın. WAF kuralları ve gövde sınırları derinlemesine savunmadır, satıcı düzeltmesinin yerine geçmez — PHP danışma belgesi doğruluk kaynağıdır ve deliği gerçekten kapatan şeydir.

Envanter, ileride sahiplenmek istediğinizden daha fazla PHP çıkarırsa, horizOn hesap, kayıt ve leaderboard katmanını yönetilen hizmetler olarak kapsar; bu, yamalamaktan sorumlu olduğunuz yüzeyi küçültmenin en hızlı yoludur. Her iki durumda da: önce envanter, sonra yama ve stack'inizin sıkıcı kısmının güvenli kısım olduğunu varsaymayı bırakın.


Kaynak: Cloudflare, php2shell zafiyetine karşı korumalar dağıtıyor