Sitenizde bir "CV gönder" formu, bir ürün fotoğrafı yükleme alanı ya da destek talebine ek dosya iliştirme özelliği var. Kod tarafında bir uzantı kontrolü de yazmışsınız: gelen dosyanın adı .jpg ile bitmiyorsa kabul edilmiyor. Aradan haftalar geçiyor ve bir gün erişim günlüklerinde /wp-content/uploads/2026/03/dosya.php?cmd=id gibi bir istek görüyorsunuz. Cevap kodu 200. Yani o dosya var ve çalışıyor.
Bu tabloya götüren yol neredeyse her zaman aynıdır: doğrulama dosya adına bakar, saldırgan ise dosya içeriğini kontrol eder. Uzantı, MIME başlığı, hatta bazen görsel boyutu — bunların hepsi ya istemcinin gönderdiği ya da kolayca taklit edilebilen verilerdir. Yükleme güvenliği tek bir kontrolle çözülen bir problem değildir; birbirinden bağımsız çalışan katmanlarla çözülür. Bir katman aşılsa bile bir sonraki, saldırganın yüklediği dosyanın çalıştırılmasını engeller.
Bu rehberde önce klasik doğrulamaların neden delindiğini somut örneklerle göstereceğiz, sonra beş katmanlı bir savunma kuracağız: sunucu tarafında içerik doğrulama, dosya adının tamamen yeniden üretilmesi, yükleme dizininde kod yürütmenin kapatılması, dosyaların web kökü dışına taşınması ve görsellerin yeniden işlenmesi. Örnekler PHP, Apache ve Nginx içindir; mantık her platformda aynıdır.
Uzantı Kontrolü Neden Tek Başına Yetmez?#
Uzantı kontrolü kötü bir fikir değildir, sadece yetersiz bir fikirdir. Üç ayrı teknik onu tek başına bıraktığınızda işe yaramaz hâle getirir.
Çift Uzantı ve Sondaki Karakterler#
Klasik yöntem zararli.php.jpg gibi bir ad göndermektir. Adın sonuna bakan bir kontrol bunu .jpg sanır. Sunucu tarafında Apache eski bir yapılandırmayla AddHandler kullanıyorsa, dosya adında geçen .php parçası hâlâ PHP yorumlayıcısını tetikleyebilir — çünkü bazı yapılandırmalar uzantıyı sonda değil, herhangi bir yerde arar.
Aynı ailenin başka üyeleri de vardır: sonuna nokta eklenen shell.php. (bazı dosya sistemlerinde nokta düşer), boşluk eklenen shell.php , ya da büyük-küçük harf oyunu yapan shell.PhP. Kontrolünüz büyük-küçük harfe duyarlıysa sonuncusu doğrudan geçer.
Content-Type Başlığı İstemciden Gelir#
Çok yaygın bir hata $_FILES['dosya']['type'] değerine güvenmektir. Bu alan sunucunun dosyayı incelemesiyle üretilmez; tarayıcının gönderdiği Content-Type başlığı olduğu gibi buraya kopyalanır. Saldırgan formu tarayıcıdan değil bir istemciyle gönderir ve başlığı istediği gibi yazar:
curl -X POST https://ornek.com/yukle.php \
-F "[email protected];type=image/jpeg"
Bu istekte dosyanın içeriği PHP kodudur, ancak $_FILES['dosya']['type'] değeri image/jpeg döner. İstemciden gelen hiçbir veriyi doğrulama kararında kullanmayın — bu kural yükleme güvenliğinin temel taşıdır.
Geçerli Bir Görselin İçine Gömülü Kod#
En sinsi yöntem budur. JPEG ve PNG biçimleri, dosya sonunda ya da yorum alanlarında tanımadıkları baytları görmezden gelir. Saldırgan gerçek bir fotoğrafın yorum bölümüne PHP kodu yerleştirir; dosya hem geçerli bir görseldir, hem de PHP olarak çalıştırıldığında kodu yürütür:
# Gerçek bir JPEG'in EXIF yorum alanına kod gömme
exiftool -Comment='<?php system($_GET["c"]); ?>' kedi.jpg
Bu dosya getimagesize() kontrolünden geçer, finfo ona image/jpeg der, tarayıcıda açıldığında normal bir kedi fotoğrafıdır. Tek eksik parça, saldırganın onu .php olarak çalıştırabilmesidir — ve o parça genellikle sizin yükleme dizininizin kod çalıştırmaya açık olmasıyla tamamlanır.
Buradaki asıl ders şudur: içerik doğrulaması gereklidir ama yeterli değildir. Yüklenen bir dosyanın hiçbir koşulda çalıştırılamaması ayrı bir katmandır ve asıl güvenceyi o verir.
Sunucu Tarafında İçerik Doğrulama Nasıl Yapılır?#
İlk katman, dosyanın gerçekte ne olduğunu sunucuda tespit etmektir. PHP'de doğru araç finfo sınıfıdır; dosyanın ilk baytlarındaki imzaya (sihirli bayt) bakarak tür belirler. Aşağıdaki örnek, izin verilen türü uzantının kaynağı olarak da kullanır — yani kullanıcının gönderdiği ad hiç dikkate alınmaz:
<?php
const IZINLI = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/webp' => 'webp',
'application/pdf' => 'pdf',
];
$f = $_FILES['dosya'] ?? null;
if (!$f || $f['error'] !== UPLOAD_ERR_OK) {
http_response_code(400); exit('Yükleme tamamlanamadı.');
}
if (!is_uploaded_file($f['tmp_name'])) {
http_response_code(400); exit('Geçersiz istek.');
}
if ($f['size'] > 5 * 1024 * 1024) {
http_response_code(413); exit('Dosya 5 MB sınırını aşıyor.');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($f['tmp_name']);
if (!array_key_exists($mime, IZINLI)) {
http_response_code(415); exit('Bu dosya türüne izin verilmiyor.');
}
$ad = bin2hex(random_bytes(16)) . '.' . IZINLI[$mime];
$hedef = '/var/uploads/' . $ad;
if (!move_uploaded_file($f['tmp_name'], $hedef)) {
http_response_code(500); exit('Dosya kaydedilemedi.');
}
chmod($hedef, 0644);
Dört ayrıntı burada bilinçli seçilmiştir. is_uploaded_file() dosyanın gerçekten bu istekle yüklendiğini doğrular; move_uploaded_file() aynı kontrolü tekrar yapar ve rastgele bir yerel yolun taşınmasını engeller. Boyut kontrolü PHP ayarlarına ek olarak uygulama seviyesinde de yapılır. chmod 0644 dosyayı çalıştırılabilir olmaktan çıkarır. Ve en önemlisi: hedef ad IZINLI dizisinden gelir, kullanıcının gönderdiği addan değil.
Kara Liste Değil, Beyaz Liste#
".php, .phtml ve .phar uzantılarını engelleyelim" yaklaşımı sürekli yenilenmesi gereken bir listeye mahkûmdur. .php7, .php8, .pht, .phps, .inc, .cgi, .pl, .py, .shtml — hepsi bir sunucu yapılandırmasında çalıştırılabilir. Bir tanesini unutmanız yeter. Beyaz liste ise tersini yapar: izin verilenler dışında her şey reddedilir, yeni bir tehlikeli uzantı çıktığında listenizi güncellemeniz gerekmez.
İki uzantıyı beyaz listeye alırken iki kez düşünün. SVG aslında bir XML belgesidir ve içine <script> etiketi konabilir; tarayıcıda doğrudan açıldığında sitenizin kendi kaynağında (origin) çalışan bir XSS'e dönüşür. HTML ve HTM de aynı sorunu taşır. Bu türlere gerçekten ihtiyacınız varsa ya özel bir SVG temizleyicisinden geçirin ya da yalnızca indirilecek ek olarak sunun, hiçbir zaman satır içi göstermeyin.
Dosya Adını Rastgeleleştirmek Neden Kritik?#
Kullanıcıdan gelen dosya adını kaydetmek üç ayrı sorunun kapısını aynı anda açar. Birincisi dizin geçişidir: ../../wp-config.php gibi bir ad, kontrolsüz birleştirildiğinde dosyayı bambaşka bir yere yazar. İkincisi üzerine yazmadır — saldırgan sizin var olan bir dosyanızın adını göndererek onu değiştirebilir. Üçüncüsü ise tahmin edilebilirliktir: yüklenen dosyanın adresini bilen biri, ona doğrudan istek atabilir.
Çözüm basittir ve üç sorunu birden kapatır: adı tamamen siz üretin. bin2hex(random_bytes(16)) 32 karakterlik, tahmin edilemeyen bir ad verir. Orijinal adı kullanıcıya göstermek istiyorsanız veritabanında bir sütunda saklayın; dosya sisteminde yeri yoktur.
Dizin yapısında da küçük bir alışkanlık işe yarar: yükleme dizininin altına tarih bazlı alt klasörler açmak yerine, dosya adının ilk iki karakterinden alt klasör üretin (/var/uploads/a7/a7f3...jpg). Tek dizinde yüz binlerce dosya biriktiğinde dosya sistemi performansı düşer; bu yöntem yükü dağıtır ve hiçbir ek riski yoktur.
Yükleme Dizininde PHP Yürütmeyi Nasıl Kapatırım?#
Bu, tek başına en yüksek getirili katmandır. Yüklenen dosya ne olursa olsun — içine kod gömülmüş bir JPEG, uzantısı atlatılmış bir .php — yürütme kapalıysa web sunucusu onu yalnızca veri olarak sunar.
Apache#
Yükleme dizinine bir .htaccess dosyası koyun. Aşağıdaki kural belirtilen uzantılara erişimi tümüyle reddeder, bu yüzden yorumlayıcının hangi modülle çalıştığından bağımsız olarak etkilidir:
# /var/www/html/uploads/.htaccess
<FilesMatch "\.(?i:ph(p[0-9]?|tml|ar|ps|t)|cgi|pl|py|sh|shtml|htaccess)$">
Require all denied
</FilesMatch>
Options -ExecCGI -Indexes
AddType text/plain .php .phtml .phar .php3 .php7 .php8 .pl .py .sh
İki tuzağa dikkat edin. Birincisi, internette çok yayılmış olan php_flag engine off satırı yalnızca mod_php ile çalışır; sunucunuz PHP-FPM kullanıyorsa bu satır siteyi 500 hatasına düşürür. İkincisi ve daha önemlisi: .htaccess uzantısını yüklenebilir türler arasında bırakırsanız, saldırgan yükleme dizinine kendi .htaccess dosyasını koyup bütün bu kuralları geçersiz kılabilir. Yukarıdaki kural .htaccess adını da kapsıyor, ancak asıl doğru çözüm ana yapılandırmada o dizin için AllowOverride None tanımlamaktır. .htaccess dosyalarının genel mantığı için htaccess dosyası oluşturma yazısına bakabilirsiniz.
Nginx#
Burada kritik bir bilgi var: Nginx .htaccess dosyalarını hiç okumaz. Apache için yazılmış bir korumayı Nginx sunucusuna kopyalayıp korunduğunuzu sanmak, gerçek hayatta çok sık görülen bir yanılgıdır. Nginx tarafında kural sunucu yapılandırmasına yazılır ve genel PHP bloğundan önce yer almalıdır, çünkü Nginx düzenli ifadeli konumları dosyadaki sıraya göre değerlendirir:
# Bu blok, aşağıdaki genel "location ~ \.php$" bloğundan ÖNCE gelmeli
location ~* ^/(uploads|files|medya)/.*\.(php|phtml|phar|php[0-9]|pl|py|cgi|sh)$ {
deny all;
access_log off;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include fastcgi_params;
}
try_files $uri =404; satırı isteğe bağlı görünse de değildir. Onsuz, /uploads/kedi.jpg/x.php biçimindeki bir istek location ~ \.php$ kuralıyla eşleşir ve PHP-FPM'e kedi.jpg dosyasını çalıştırması söylenebilir. Aynı korumanın kesin hâli PHP-FPM havuz dosyasındadır:
; /etc/php/8.3/fpm/pool.d/www.conf
security.limit_extensions = .php
php_admin_value[open_basedir] = /var/www/html:/var/uploads:/tmp
security.limit_extensions, FPM'in .php dışında hiçbir uzantıyı çalıştırmamasını garanti eder ve yukarıdaki yol tabanlı atlatmayı kökten keser. Nginx ile PHP-FPM ilişkisinin ayrıntıları için Nginx PHP-FPM yapılandırma yazısı iyi bir başlangıç noktasıdır.
Kurduğunuz Kuralı Doğrulayın#
Kuralı yazdıktan sonra mutlaka test edin. İçinde tek satır PHP olan bir dosyayı yükleme dizinine elle koyup tarayıcıdan çağırın:
echo '<?php echo "CALISIYOR"; ?>' > /var/www/html/uploads/test.php
curl -s https://ornek.com/uploads/test.php
rm /var/www/html/uploads/test.php
Çıktı CALISIYOR ise koruma yoktur. Beklenen sonuç 403 hatası ya da dosyanın ham metin olarak dönmesidir.
Dosyaları Web Kökü Dışında Tutmak#
En sağlam yaklaşım, yüklenen dosyaların web sunucusunun ulaşabileceği alanda hiç bulunmamasıdır. Dosyalar /var/uploads gibi web kökü dışındaki bir dizinde durur ve yalnızca bir PHP betiği üzerinden sunulur. Böylece yükleme dizinine doğrudan HTTP isteği atmak mümkün olmaz; dosyaya erişim tamamen sizin kontrolünüzdeki kodun kararına bağlanır.
<?php
const TIPLER = ['jpg' => 'image/jpeg', 'png' => 'image/png', 'pdf' => 'application/pdf'];
$ad = basename($_GET['f'] ?? '');
if (!preg_match('/^[a-f0-9]{32}\.(jpg|png|pdf)$/', $ad)) {
http_response_code(400); exit;
}
$yol = '/var/uploads/' . $ad;
if (!is_file($yol)) { http_response_code(404); exit; }
header('Content-Type: ' . TIPLER[pathinfo($ad, PATHINFO_EXTENSION)]);
header('Content-Disposition: attachment; filename="belge.pdf"');
header('X-Content-Type-Options: nosniff');
header('Content-Length: ' . filesize($yol));
readfile($yol);
Buradaki üç başlık da işlevseldir. Content-Type tarayıcıya dosyanın ne olduğunu siz söylersiniz. nosniff başlığı tarayıcının içeriğe bakıp kendi kararını vermesini engeller — bu olmadan bazı tarayıcılar text/plain olarak sunulan HTML'i yorumlayabilir. Content-Disposition: attachment ise dosyanın tarayıcıda açılmak yerine indirilmesini sağlar; belge ve arşiv türleri için varsayılan tercihiniz bu olmalıdır.
Bu tasarımın ek bir kazancı vardır: yetkilendirme artık mümkündür. Sadece giriş yapmış kullanıcının kendi faturasını indirebilmesini istiyorsanız, kontrol satırını bu betiğe eklemeniz yeterlidir. Dosyalar doğrudan erişilebilir bir dizinde dururken böyle bir kural yazmanın yolu yoktur.
Görseli Yeniden İşlemek: Gömülü Kodu Silen Adım#
Görsel kabul eden bir siteniz varsa, yüklenen dosyayı olduğu gibi saklamak yerine yeniden üretin. GD veya Imagick ile görüntüyü belleğe alıp tekrar diske yazdığınızda yalnızca piksel verisi korunur; EXIF yorumları, dosya sonuna eklenmiş baytlar ve gömülü kod parçalarının tamamı kaybolur.
<?php
$kaynak = match ($mime) {
'image/jpeg' => imagecreatefromjpeg($gecici),
'image/png' => imagecreatefrompng($gecici),
'image/webp' => imagecreatefromwebp($gecici),
default => false,
};
if ($kaynak === false) {
http_response_code(415); exit('Görsel çözümlenemedi.');
}
imagejpeg($kaynak, $hedef, 85);
imagedestroy($kaynak);
Yeniden işleme ayrıca konum bilgisi gibi kişisel EXIF verilerini de temizler; bu, kullanıcı fotoğrafı kabul eden sitelerde ayrı bir gizlilik kazancıdır. Boyut sınırı koymayı da unutmayın: piksel sayısı devasa bir görsel, açılırken sunucunun belleğini tüketerek hizmet dışı bırakma etkisi yaratabilir. getimagesize() ile genişlik ve yüksekliği okuyup makul bir üst sınır uygulamak yeterlidir. Imagick kullanıyorsanız policy.xml dosyasında kullanılmayan biçimleri kapatmak, geçmişte ciddi zafiyetler çıkmış olan bu kütüphanenin saldırı yüzeyini belirgin biçimde daraltır.
Katmanlı Savunma Kontrol Listesi#
Aşağıdaki tablo, bir yükleme uç noktasını devreye almadan önce geçilmesi gereken kontrolleri özetler. Sağ sütun, o katman tek başına kalırsa nasıl atlatıldığını gösterir — hiçbirinin yalnız yeterli olmamasının nedeni budur.
| Katman | Ne yapar | Tek başına kalırsa |
|---|---|---|
| Uzantı beyaz listesi | Bilinmeyen türleri eler | Sunucu yapılandırma hatasıyla atlatılır |
finfo ile MIME tespiti | Gerçek türü sunucuda belirler | Resme gömülü kod geçer |
| Boyut ve piksel sınırı | Kaynak tüketimini önler | Güvenlik katkısı yok |
| Adın yeniden üretilmesi | Geçiş ve üzerine yazmayı keser | Dosya yine çalıştırılabilir |
| Dizinde yürütmeyi kapatma | Kodun çalışmasını engeller | Yanlış sunucuda kural yazılırsa etkisiz |
| Web kökü dışında saklama | Doğrudan erişimi imkânsız kılar | Sunum betiği zayıfsa delinir |
| Görseli yeniden işleme | Gömülü içeriği siler | Görsel olmayan türleri kapsamaz |
| Yetki ve hız sınırı | Kimliksiz toplu yüklemeyi durdurur | Tek başına içerik denetlemez |
Son satır çoğu zaman gözden kaçar: yükleme uç noktanız oturum açmamış herkese açıksa, saldırgan istediği kadar deneme yapabilir. Mümkün olan her yerde yüklemeyi kimlik doğrulamasının arkasına alın ve IP başına dakikalık bir sınır koyun. Uygulama önünde bir web uygulama güvenlik duvarı çalışıyorsa, bilinen shell imzalarını taşıyan gövdeleri daha uygulamaya ulaşmadan eleyebilirsiniz.
WordPress ve Hazır Form Eklentilerinde Pratik Ayarlar#
Hazır bir sistem kullanıyorsanız kodu siz yazmıyorsunuz demektir, ama katmanların çoğunu yine de kurabilirsiniz. Önce mevcut durumu ölçün — yükleme dizininde çalıştırılabilir bir dosya zaten var mı:
find /var/www/html/wp-content/uploads -type f \
\( -name '*.ph*' -o -name '*.cgi' -o -name '.htaccess' \) \
-printf '%T+ %M %p\n' 2>/dev/null | sort
Bu komut boş dönmüyorsa temizlikten önce dosyaların içeriğini inceleyin; bulgunun ne anlama geldiği ve nasıl temizleneceği web shell tespiti yazısında ayrıntılı olarak anlatılıyor.
Ardından üç ayarı yapın. wp-config.php içinde dosya düzenleyiciyi ve panel üzerinden kod kurulumunu kapatın; ayrıntılar için wp-config güvenliği yazısına bakın:
define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', true);
İzin verilen türleri daraltın. WordPress'in varsayılan listesi geniştir ve çoğu site onun onda birini kullanır:
add_filter('upload_mimes', function (array $tipler): array {
return [
'jpg|jpeg' => 'image/jpeg',
'png' => 'image/png',
'webp' => 'image/webp',
'pdf' => 'application/pdf',
];
});
Son olarak dosya ve dizin izinlerini doğrulayın: dizinler 755, dosyalar 644 olmalı ve yüklenen hiçbir dosya çalıştırma bitine sahip olmamalıdır. Bu konunun tamamı WordPress dosya izinleri ve Linux dosya izinleri yazılarında ele alınıyor. Yükleme boyutu sınırlarını upload_max_filesize ve post_max_size ile hizalamayı da unutmayın; ikisi tutarsız olduğunda kullanıcı sessiz hatalarla karşılaşır — nedeni PHP upload_max_filesize yazısında açıklanıyor.
Sıkça Sorulan Sorular#
getimagesize kontrolü dosyanın güvenli olduğunu kanıtlar mı?#
Hayır. getimagesize() yalnızca dosyanın başında geçerli bir görsel başlığı bulunduğunu doğrular; o başlıktan sonra gelen baytlarla ilgilenmez. İçine PHP kodu gömülmüş gerçek bir JPEG bu kontrolden sorunsuz geçer. Yararlı bir ön eleme adımıdır, ancak tek başına güvenlik sağlamaz. Gerçek güvence, dosyanın yeniden işlenmesi ve yükleme dizininde kod yürütmenin kapatılmasıyla gelir.
Sadece uploads klasöründe PHP çalışmasını kapatsam yeterli olur mu?#
Tek bir önlem seçmek zorunda kalsanız en etkilisi budur, ancak yeterli değildir. Yüklenen bir SVG dosyası hâlâ tarayıcıda XSS çalıştırabilir, dev boyutlu bir görsel sunucunun belleğini tüketebilir, kullanıcı adıyla kaydedilen bir dosya mevcut dosyalarınızın üzerine yazabilir. Yürütmeyi kapatmak en ağır sonucu engeller; diğer katmanlar geri kalan riskleri kapatır.
Nginx kullanıyorum, .htaccess dosyası yazsam korunur muyum?#
Hayır. Nginx .htaccess dosyalarını okumaz, dizinde böyle bir dosya bulunması hiçbir şey değiştirmez. Kuralı sunucu yapılandırma dosyanıza yazmanız ve değişiklikten sonra yapılandırmayı yeniden yüklemeniz gerekir. Ayrıca kuralın genel PHP bloğundan önce yer alması önemlidir, çünkü Nginx düzenli ifadeli konumları dosyadaki sıraya göre değerlendirir.
Kullanıcıların SVG yüklemesine izin vermeli miyim?#
Gerçekten gerekmiyorsa hayır. SVG bir XML belgesidir ve içinde betik barındırabilir; tarayıcıda doğrudan görüntülendiğinde sitenizin kendi kaynağında çalışan bir XSS'e dönüşür. Logo yükleme gibi bir ihtiyaç varsa dosyayı özel bir SVG temizleyicisinden geçirin, betik ve olay özniteliklerini kaldırın veya dosyayı yalnızca indirilecek ek olarak sunun.
Yüklenen dosyaları antivirüs ile taramalı mıyım?#
Kullanıcıların birbirine dosya paylaştığı bir platform işletiyorsanız evet, faydalıdır. ClamAV gibi bir tarayıcıyı yükleme akışına eklemek, sitenizin bilinen zararlıların dağıtım noktasına dönüşmesini engeller. Ancak bunu web shell'e karşı bir savunma olarak görmeyin: özel yazılmış küçük bir arka kapı hiçbir imza veritabanında bulunmaz. Tarama tamamlayıcı bir katmandır, temel katmanların yerini almaz.
Dosyaları web kökü dışında saklamak performansı düşürür mü?#
Bir miktar düşürür, çünkü her istek PHP üzerinden geçer ve dosya doğrudan web sunucusu tarafından sunulmaz. Yoğun görsel trafiği olan sitelerde bunu dengelemenin yolu, yetki kontrolünü PHP'de yapıp dosyayı web sunucusuna devretmektir: Nginx'te X-Accel-Redirect, Apache'de X-Sendfile başlığı tam olarak bunun içindir. Böylece hem yetkilendirme korunur hem de dosya sunumu hızlı kalır.
Kapanış#
Güvenli dosya yükleme, tek bir kontrol satırının değil birbirini yedekleyen katmanların işidir. Uzantı ve MIME doğrulaması kaba eleme yapar, adın yeniden üretilmesi dosya sistemi oyunlarını keser, görselin yeniden işlenmesi gömülü içeriği siler; asıl güvenceyi ise yükleme dizininde kod yürütmeyi kapatmak ve dosyaları web kökü dışında tutmak verir.
Bu katmanları kurduktan sonra tek bir alışkanlık ekleyin: kuralı yazdıktan sonra test edin. Yükleme dizinine bir test dosyası koyup çağırmak otuz saniye sürer ve yapılandırmanın gerçekten çalıştığını gösteren tek kanıttır. Sunucu taşıdığınızda, web sunucusunu değiştirdiğinizde veya PHP sürümünü yükselttiğinizde bu testi tekrarlayın — koruma kuralları en çok bu geçişlerde sessizce devre dışı kalır.