Özet. Automattic'in 200.000'den fazla etkin kuruluma sahip WordPress performans eklentisi Page Optimize, önbellek anahtarını çözümlenmiş ve doğrulanmış dosya listesi yerine ham istek URI'sinden üretiyor. Kimlik doğrulamasız, uzaktaki bir ziyaretçi bu uyumsuzluğu kullanarak sunucuya sınırsız sayıda yinelenen önbellek dosyası yazdırabilir; sonuçta disk alanı ve inode'lar tükenir (CWE-770). Yazıda kusurlu kodu, kusura kavramsal olarak nasıl ulaşıldığını, erişilebilirlik etkisini, düzeltmeyi ve site sahipleri için tespit yöntemini bulacaksınız. Koordineli ifşa gereği üretici bilgilendirildi; çalışan kavram kanıtı, yama çıkana kadar yayımlanmayacak.
Anahtar kelimeler: Page Optimize, WordPress, CWE-770, kontrolsüz kaynak tüketimi, hizmet reddi, önbellek anahtarı, disk tükenmesi, inode tükenmesi.
1. Giriş
Page Optimize, bir sitenin JavaScript ve CSS dosyalarını birleştirip özel bir /_static/ uç noktası üzerinden sunar. 200.000'i aşan etkin kurulumuyla eklentinin istek işleme davranışı, WordPress saldırı yüzeyinin azımsanmayacak bir bölümünü kaplar.
Önbellekleme kodu erken, sık ve her ziyaretçi için çalıştığından incelemeye değer. Talep üzerine dosya oluşturan bir önbellek katmanının en kritik kararı, bir önbellek girdisini neyin tanımladığıdır. Bu kimlik gevşek seçilirse iş tasarrufu için yazılmış özellik, sınırsız iş üreten bir özelliğe dönüşür. Page Optimize bu kimliği yanlış seçiyor ve anonim bir ziyaretçi, önbelleği kontrolsüz kaynak tüketimine (CWE-770) sürükleyebiliyor. Aşağıdaki açıklama hem geliştiricilerin kalıbı tanıması hem de site sahiplerinin tespit edip sınırlaması için yazıldı.
2. Etkilenen bileşen
| Eklenti | Page Optimize (Automattic) — slug page-optimize |
| Test edilen sürüm | 0.6.3 (test anında güncel/son sürüm) |
| Etkin kurulum | 200.000+ |
| Güvenlik açığı türü | Kontrolsüz Kaynak Tüketimi / Hizmet Reddi |
| CWE | CWE-770 — Sınır veya Kısıtlama Olmadan Kaynak Tahsisi (ayrıca CWE-400) |
| Gereken yetki | Yok — kimlik doğrulamasız, uzaktan |
| Kullanıcı etkileşimi | Yok |
| CVSS 3.1 | 5.3 (Orta) — AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L (diski tamamen dolduran sürekli bir sel altında 7.5 / A:H'ye doğru yükselir) |
| Durum | Taze (0-day), kodda doğrulandı ve güncel sürümde localhost laboratuvarında yeniden üretildi; yama yok |
3. Teknik analiz
3.1 Uç nokta herkese yanıt verir
Eklenti, eklenti-global kapsamda bir yönlendirici kurar. Her ön yüz isteğinde — oturumu kapalı ziyaretçiler dahil — istek URI'sini okur ve yol /_static/ ile başladığında denetimi statik servise devreder. Bu yolu koruyan hiçbir nonce, yetki ya da kimlik doğrulama denetimi yok:
// page-optimize.php (lines 28-31)
if ( isset( $_SERVER['REQUEST_URI'] ) && '/_static/' === substr( $_SERVER['REQUEST_URI'], 0, 9 ) ) {
require_once __DIR__ . '/service.php';
exit;
}Birleştirilmiş varlıkları anonim ziyaretçilere sunmak tasarım gereğidir. Sorun, önbellek ıskasında (cache miss) başlar.
3.2 Önbellek anahtarı ham istek URI'sidir
Iskada servis, birleştirilmiş çıktıyı üretir ve iki dosya yazar: bir yük dosyası ve bir meta veri dosyası. İkisi de ham istek URI'sinin — sorgu dizesinin tamamıyla birlikte — md5'inden adlandırılır:
// service.php (lines 27-30, 73-76)
$request_uri_hash = md5( $_SERVER['REQUEST_URI'] ); // <-- anahtar ham URI'dir, saldırgan denetimli
$cache_file = PAGE_OPTIMIZE_CACHE_DIR . "/page-optimize-cache-$request_uri_hash";
$cache_file_meta = PAGE_OPTIMIZE_CACHE_DIR . "/page-optimize-cache-meta-$request_uri_hash";
...
file_put_contents( $cache_file, $content );
file_put_contents( $cache_file_meta, json_encode( array( 'headers' => $headers ) ) );3.3 Anahtar, çıktının bağlı olmadığı baytları taşır
Can alıcı nokta burası. Servis hangi dosyaları birleştireceğine karar vermeden önce, sondaki ?version işaretinden itibaren her şeyi atar. Dolayısıyla sürüm/çöp son eki ne çözümlenen dosya listesine ne de çıktı içeriğine dokunur; buna rağmen §3.2'de hesaplanan md5 anahtarının parçası olmayı sürdürür:
// service.php (lines 117-121) — sürüm son eki dosya çözümlemesinden ÖNCE kırpılır
$version_string_pos = strpos( $args, '?' );
if ( false !== $version_string_pos ) {
$args = substr( $args, 0, $version_string_pos );
}Neden yanlış? Anahtar ile önbelleğe alınan değer birbirinden kopmuş durumda. Değer, çözümlenmiş ve doğrulanmış dosya listesinin saf bir fonksiyonu; anahtar ise saldırganın denetlediği ve çıktının attığı fazladan baytlarla dolu ham URI'nin fonksiyonu. Bayt bayt aynı çıktıyı veren iki istek bile ayrı anahtarlar altında saklanıyor. Doğru bir önbellek, girdiyi yalnızca değeri belirleyen girdilere göre anahtarlar; ne fazlası ne eksiği.
Kusuru geçici olmaktan çıkarıp kalıcılaştıran iki ayrıntı:
- Önbellekleme varsayılan olarak açıktır:
PAGE_OPTIMIZE_CACHE_DIRher zaman tanımlıdır (wp-content/cache/page_optimize). - Temizlik (
page_optimize_cache_cleanup) yalnızca günlük WP-cron üzerinde çalışır ve yalnızca 24 saatten eski dosyaları siler; WP-cron ise yalnızca organik trafikte tetiklenir. Bu yüzden yeni oluşan dosyalar en az bir gün hayatta kalır.
4. Kusura nasıl ulaşılır (kavramsal)
Sink'e (kaynağa) ulaşmak yetki istemez, çünkü /_static/ yönlendirici tasarım gereği anonim GET isteklerini yanıtlar. Saldırgana yeten tek şey herkese açık bir bilgidir: sitenin gerçekten sunduğu geçerli bir varlık yolu. WordPress ile gelen yaygın çerçeve varlıkları hemen her yerde bulunur.
Gerisi §3.3'ten doğrudan gelir. Anahtar, çıktının bağlı olmadığı baytları içerdiği için aynı birleştirme fiilen sınırsız sayıda farklı anahtarla adreslenebilir: yok sayılan son ekin her çeşitlemesi yeni bir md5, dolayısıyla diskte aynı içeriği tutan yeni bir dosya çiftidir. IP başına bir sınır, girdi sayısında bir tavan, toplam önbellek boyutunda bir üst sınır, uç noktada bir hız kısıtı yok. Sunucunun ıska saydığı her istek baştan yazma yapar.
Sonuç, hacim/birikim türünden bir hizmet reddidir: sunucu, tek bir gerçek kaynağın asla gerektirmeyeceği kadar çok veri ve çok sayıda ayrı dosya yazmaya itilebilir. Her girdi iki dizin kaydı (iki inode) tükettiği ve en az bir gün kaldığı için hem boş alan bütçesi hem de inode tablosu risktedir. İstek dizisini, son ek değerlerini ve herhangi bir tekrar oynatma komutunu bilerek yayımlamıyoruz; yukarıdaki mekanizma sorunu anlamaya ve savunmaya yeter, işlevsel ayrıntı ise yamayı bekler.
5. Etki değerlendirmesi
Kimlik doğrulamasız, uzaktaki bir aktör, eklentinin önbellek dizinini kalıcı ve sınırsız biçimde büyütebilir. İki ayrı tükenme yolu var:
- Boş alan tükenmesi — yazılan baytlar sunulan kaynağın boyutuyla kabaca orantılı birikir; yükseltme oranı 1:1'e yakın olduğundan anlamlı bir kesinti sürekli istek hacmi ister.
- Inode tükenmesi — her girdi bir çift küçük dosya olduğundan tüketilen inode sayısı hızla tırmanır ve özellikle küçük VPS sunucularında, ham baytlar diski doldurmadan çok önce dosya sisteminin inode tablosunu bitirebilir.
Bu bütçelerden biri tükendiğinde yazma gerektiren tüm WordPress işlemleri — veritabanı geçici dosyaları, medya yüklemeleri, oturum verileri, ileri önbellekleme — başarısız olmaya başlar ve site çöker. Temel puanın kısmi erişilebilirlik etkisi (A:L) için 5.3 (Orta) seviyesinde kalmasının, diski ya da inode tablosunu baştan sona dolduran sürekli bir sel altında 7.5 / A:H'ye yükselmesinin nedeni budur. Gizlilik ya da bütünlük etkisi yoktur; mesele baştan sona erişilebilirliktir.
Kapsam adına kusurun ne olmadığını da kayda geçirelim. Dosya çözümlemesi sağlamlaştırılmıştır — .. ve null-byte geçişi engellenir, var olmayan yollar yazma yapmadan 404 döner — dolayısıyla bu bir yol geçişi ya da keyfi yazma-konumu kusuru değildir. İsteğe bağlı gzuncompress() dalı pratik bir açma-bombası (decompression bomb) vektörü değildir. Yönetici ayarları ise çekirdek Settings API'sini nonce ve manage_options ardında kullanır. Geriye kalan, dürüstçe Orta olarak sınıflandırılan gerçek bir kaynak tüketimi kusurudur; yüksek yükseltmeli bir yansıtıcı kılığına sokulmamıştır.
6. Düzeltme
Belirleyici düzeltme, önbellek anahtarını sakladığı değerle yeniden hizalamaktır:
// Önbelleği, ham istek URI'si yerine ÇÖZÜMLENMİŞ, DOĞRULANMIŞ dosya listesine göre anahtarla.
// Sürüm son eki kırpıldıktan ve yollar çözümlenip doğrulandıktan sonra:
$resolved = page_optimize_resolve_and_validate( $args ); // sıralı, doğrulanmış dosya listesi
$request_uri_hash = md5( implode( "\0", $resolved ) ); // anahtar == tam olarak çıktıyı
// belirleyen girdilerÇözümlenmiş, doğrulanmış dosya listesine göre anahtarlamak, aynı isteğin tüm çöp son ek çeşitlemelerini tek bir girdide toplar ve çoğalmayı kaynağında keser. Derinlemesine savunma olarak eklenti ayrıca şunları yapmalı:
- Önbellek girdisi sayısını ve/veya toplam önbellek dizini boyutunu sınırla; yalnızca 24 saatlik yaşa değil, bir üst sınıra göre tahliye et.
/_static/uç noktasını istemci başına hız sınırlamasına tabi tut.- Temizliği yalnızca organik trafiğe bağlı günlük cron'da değil, yazma baskısında da tetikle.
Resmî düzeltilmiş sürüm çıkana kadar site sahipleri §7'deki önlemleri uygulamalı.
7. Site sahipleri için tespit ve sağlamlaştırma
Buradaki her madde yukarıdaki mekaniğe dayanır; uydurma ürün özelliği yoktur.
- Önbellek dizinini izleyin.
wp-content/cache/page_optimizedizinini yalnızca boyut değil dosya sayısı açısından anormal büyümeye karşı gözleyin. Aynı yük içeriğine sahip, hızla artanpage-optimize-cache-*/page-optimize-cache-meta-*çiftleri en net göstergedir. - Yalnızca diski değil inode'ları da izleyin. Küçük sunucularda inode tablosu önce tükenebilir. Boş alanın yanında inode kullanımına da uyarı koyun (Linux'ta
df -i). - Erişim günlüklerinde
/_static/kalıplarına bakın. Aynı alttaki varlığa yalnızca değişen bir sondaki son ekle ayrılan çok sayıda isteği, özellikle tek bir kaynaktan ya da dar bir kaynak kümesinden geleni arayın. - En az maruziyeti uygulayın. Birleştirme özelliği gerekmiyorsa eklentiyi devre dışı bırakın ya da kaldırın. Gerekiyorsa, altyapınızın elverdiği yerde diske önbelleklemeyi kısıtlamayı veya kapatmayı düşünün.
- Sanal yama / WAF. Bir WAF ya da kenar kuralı,
/_static/'i hız sınırına tabi tutabilir ve sorgusunda yüksek kardinaliteli, çıktıyla ilgisiz bir son ek taşıyan istekleri reddedebilir. Bunu geçici bir önlem sayın; önbellek anahtarı düzeltmesinin yerini tutmaz. - Zamanlanmış temizlik. Dahili temizlik WP-cron'un tetiklenmesine bağlı olduğundan, bayat önbellek dosyalarının gerçekten silinmesi için bunu gerçek bir sistem cron'u ile destekleyin.
8. Daha geniş ders
İlke basit ve geniş çapta geçerlidir: önbellek anahtarı, önbelleğe alınan değeri belirleyen girdilerin tam olarak bir fonksiyonu olmalı; ne fazlası ne eksiği. Anahtara, değerin yok saydığı saldırgan denetimli baytlar girerse önbellek aynı içeriğin sınırsız kopyasını saklamaya zorlanabilir; anahtar, değerin bağlı olduğu baytları atlarsa önbellek karışması ya da zehirlenmesi doğar. İki yöndeki sapma da birer kusurdur. Aynı disiplin memoizasyon, tekilleştirme tabloları, hız sınırlayıcı kovaları ve istek verisiyle anahtarlanan talep üzerine her kaynak için geçerlidir. Doğru anahtarlamayı açık kaynak tavanlarıyla — girdi sayısı, toplam boyut, istemci başına hız sınırı — birlikte kurun; çünkü sınırı olmayan bir önbellek, anonim bir ziyaretçinin sürebileceği bir tahsis motorudur.
9. Sorumlu ifşa
Bu araştırma koordineli (sorumlu) ifşa kapsamında yürütüldü. Üretici özel olarak bilgilendirildi; tam işlevsel ayrıntı — tam istek dizisi, son ek değerleri ve yeniden üretim komutları — bir düzeltme yayımlanana kadar bu herkese açık yazıdan bilerek çıkarıldı. Şimdi yayımlamanın amacı savunmacıdır: düzeltme hazırlanırken site sahiplerinin hata sınıfını tanımasını, tespit etmesini ve sınırlamasını sağlamak.
Tüm testler yalnızca, herkese açık GPL eklentisini çalıştıran, yalıtılmış ve localhost'a bağlı bir Docker WordPress laboratuvarında yapıldı. Üçüncü taraf ya da üretim sistemlerine erişilmedi; laboratuvar ve önbellek dizini testin ardından kaldırıldı. Noroxi LLC yalnızca yetkili güvenlik araştırması yürütür ve test etme izni olmayan sistemleri test etmez.
Kaynaklar
- CWE-770: Allocation of Resources Without Limits or Throttling — https://cwe.mitre.org/data/definitions/770.html
- CWE-400: Uncontrolled Resource Consumption — https://cwe.mitre.org/data/definitions/400.html
- FIRST CVSS v3.1 Spesifikasyonu ve Hesaplayıcı — https://www.first.org/cvss/calculator/3.1
- OWASP: Denial of Service — https://owasp.org/www-community/attacks/Denial_of_Service
- WordPress Plugin Developer Handbook — Nonces — https://developer.wordpress.org/plugins/security/nonces/
- WordPress Plugin Developer Handbook — Checking User Capabilities — https://developer.wordpress.org/plugins/security/checking-user-capabilities/
Savunma ve eğitim amacıyla yayımlanmıştır. Noroxi LLC yalnızca yetkili güvenlik araştırması yürütür; işlevsel istismar ayrıntısı, bir düzeltme yayımlanana kadar saklı tutulur.
Bu hafta sömürülenler · her pazartesi
KEV'e girenler, yeni PoC'ler, WordPress triyajı ve bu hafta ne yapmalı. Hesap gerekmez.