WordPress web shell: uploads klasöründe bulma ve engelleme
WordPress web shell'lerini uploads klasöründe güvenli komutlarla bulun, Apache ve nginx'te PHP çalışmasını engelleyin, izinleri gözden geçirip giriş noktasını kapatın.
Diğer diller: English Deutsch Français Español Italiano Polski Português
uploads klasöründe bir WordPress web shell'inden şüpheleniyorsanız yapmanız gereken üç ayrı iş var. Dosyayı bulup delil olarak saklarsınız. wp-content/uploads içindeki PHP dosyalarının doğrudan isteklerle çalışmasını durdurursunuz. Dosyanın içeri girdiği yolu kapatırsınız. Her iş farklı bir sorunu çözer ve hiçbiri tek başına tam bir kurtarma sayılmaz.
Bu yazı her iş için gereken komutları ve sunucu ayarlarını veriyor; her birinin neyi çözmediğini de açıkça belirtiyor.
Web shell nedir ve wp-content/uploads neden en sevilen yer?
Web shell, saldırgana sunucunuzda uzaktan kumanda paneli sağlayan küçük bir betiktir, çoğunlukla PHP ile yazılır. Sunucunun yapılandırmasına bağlı olarak komut çalıştırmaya, veritabanı parolanızın bulunduğu wp-config.php dosyasını okumaya, veritabanını sorgulamaya ve yeni dosyalar yüklemeye izin verebilir.
Saldırganlar wp-content/uploads klasörünü sever, çünkü WordPress'in orada normalde yazma iznine ihtiyacı vardır. Medya dosyaları, form ekleri ve eklenti dışa aktarımları bu klasöre düşer ve içindeki dosyalar doğrudan URL ile sunulur. Oraya bir PHP dosyası girerse ve sunucu onu çalıştırırsa, internetteki herkes o dosyayı çağırabilir.
Web shell'ler wp-content/uploads'a nasıl girer?
Giriş yolları genellikle sıradandır:
- Her dosya türünü kabul eden bir yükleme formu. Örneğin ekleri uploads klasöründe saklayan bir iletişim formu veya iş başvurusu eklentisi.
- Bir eklentideki ya da temadaki rastgele dosya yükleme (arbitrary file upload) açığı.
- Yönetici işlevlerinin herkes tarafından çağrılabildiği bozuk erişim kontrolü (broken access control). Satıcı raporu olan Patchstack State of WordPress Security in 2026 bunu en çok istismar edilen kategori olarak adlandırıyor.
- Bir zayıflıkla birleşen çalınmış hesap. WordPress'in varsayılan dosya türü denetimleri normalde bir Editör'ün
.phpdosyası yüklemesini engeller. Çalınmış hesap, o rol bir eklentinin yükleme açığına da erişebiliyorsa ya da sitenin yapılandırmasıunfiltered_uploadyetkisini veriyorsa bir yükleme yoluna dönüşür. Tek siteli kurulumda Yönetici (Administrator) genellikle eklenti kurabilir ve panelin dosya editörlerini kullanabilir.DISALLOW_FILE_EDITbu editörleri kapatır;DISALLOW_FILE_MODSayrıca panelden eklenti ve tema kurulumunu ve güncellemesini de engeller. Multisite'ta bu yetkiler yalnızca Super Admin'lerdedir.
Shell'lere nadiren shell.php adı verilir. invoice.pdf.php gibi çift uzantılara, alternatif uzantılar olan .phtml ve .phar dosyalarına, önbellek veya küçük resim dosyalarını taklit eden adlara bakın. Bir .jpg içine gizlenmiş PHP kodu uyarı işaretidir, ama içinde PHP olması onu kendiliğinden çalıştırmaz. Dosya ancak sunucu onu PHP'ye devrederse ya da başka bir açık betik onu include ederse çalışır.
Risk eklentilerde toplanıyor. Patchstack, 2025'te WordPress ekosistemindeki yeni açıkların %91'ini eklentilere bağlıyor. Yoğun biçimde istismar edilen açıklar için kitlesel istismara kadar ağırlıklı medyan süreyi 5 saat olarak bildiriyor. Bu tempoya nasıl yetişeceğinizi savunmasız eklentiler rehberimizde anlattık.
WordPress web shell'i bulmak: uploads içindeki PHP dosyaları
Bu komutlar dosyalarda değişiklik yapmaz. SSH ile WordPress kök dizininden, yani wp-config.php dosyasının bulunduğu klasörden çalıştırın. Önce bariz uzantılarla başlayın:
find wp-content/uploads -type f \( -iname '*.php' -o -iname '*.phtml' -o -iname '*.phar' \) -ls
invoice.pdf.php .php ile bittiği için eşleşir. .php5 ya da image.php.jpg gibi varyantları yakalamak için deseni -iname '*.ph*' olarak genişletin; gürültünün artmasını bekleyin.
Sonra yakın zamanda değişen dosyaları ya da temiz olduğunu bildiğiniz bir yüklemeden daha yeni dosyaları listeleyin:
find wp-content/uploads -type f -mtime -7 -ls
find wp-content/uploads -type f -newer wp-content/uploads/2026/09/known-good.jpg -ls
Saldırganlar değişiklik zamanlarını sahteleyebilir, bu yüzden bunları yalnızca ipucu sayın. Görsel adlı dosyalardaki PHP kodunu bulmak için:
grep -rli --include='*.jpg' --include='*.jpeg' --include='*.png' --include='*.gif' --include='*.webp' --include='*.ico' '<?php' wp-content/uploads
grep dosya içeriğini okur ve bir dosyayı okumak erişim zamanını güncelleyebilir. Zaten bir ihlalden şüpheleniyorsanız önce hosting sağlayıcınızdan bir anlık görüntü (snapshot) isteyin.
Her eşleşme kötü amaçlı değildir. Bazı eklentiler yalnızca yorum satırı içeren bir index.php yer tutucusu bırakır; bazı önbellek ya da form eklentileri de kendi alt klasörlerine PHP yazar. Bir adayı açmadan önce meta verilerini bir sonraki bölümde anlatıldığı gibi kaydedin. Ardından less ya da xxd file | head -40 ile inceleyin. Dosyayı asla tarayıcıda açmayın, çünkü çalışabilir. Uzun kodlanmış diziler ile istek değişkenleriyle birlikte kullanılan eval, base64_decode veya assert güçlü uyarı işaretleridir.
Erişim günlüğünde uploads içindeki PHP dosyalarına yapılmış istekleri de arayın. Böyle bir dosyaya POST isteği nadiren meşrudur:
grep -E '"(GET|POST) /wp-content/uploads/[^ ]*\.(php|phtml|phar)' access.log
Bir tane buldunuz mu? Silmeden önce delilleri saklayın
Önce meta verileri kaydedin, sonra dosyayı ve günlükleri kopyalayın. Hepsini, sunucunuzun yayınladığı hiçbir belge kökünün (document root) içinde olmayan özel bir dizinde tutun. Bazı paylaşımlı hostlarda ev dizininin kendisi web üzerinden erişilebilir olabilir; bu yüzden belge kökünü kontrol panelinizden ya da vhost yapılandırmasından doğrulayın.
umask 077
mkdir -p ~/ir-evidence && chmod 700 ~/ir-evidence
f="wp-content/uploads/path/to/suspicious.php"
case_id="webshell-$(date -u +%Y%m%dT%H%M%SZ)-$RANDOM"
stat "$f" > ~/ir-evidence/"$case_id"-stat.txt
sha256sum "$f" > ~/ir-evidence/"$case_id"-sha256.txt
tar -czpf ~/ir-evidence/"$case_id"-file.tar.gz "$f"
tar -czpf ~/ir-evidence/"$case_id"-logs.tar.gz /path/to/access.log* /path/to/error.log*
Zaman damgası ve rastgele sonek, delil dosya adının yeniden kullanılma ihtimalini azaltır; yakalamayı yinelemeden önce mevcut dosya adlarını kontrol edin. Hostlar günlükleri döndürür, bu yüzden onları hemen kopyalayın. Sonra delilleri sunucu dışında güvenli bir yere indirin ve dosyanın yolunu ve eşleşen günlük satırlarını not edin.
wp-config.php içindeki veritabanı kimlik bilgilerini ele geçirilmiş olabilir sayın. Kişisel verilere erişilip erişilmediğini, bunların ifşa edilip edilmediğini, değiştirilip değiştirilmediğini, kaybolup kaybolmadığını ya da kullanılamaz hale gelip gelmediğini değerlendirin: çalışan bir shell ciddi bir uyarıdır, ama tek başına GDPR (AB Genel Veri Koruma Tüzüğü) anlamında bir kişisel veri ihlalini kanıtlamaz. 33. madde, kişisel veri ihlalinden haberdar olunduktan sonra gereksiz gecikmeden ve mümkünse 72 saat içinde denetim otoritesine bildirim yapılmasını ister; ihlalin kişilerin hak ve özgürlükleri açısından risk doğurma ihtimalinin düşük olduğu durumlar bunun dışındadır. 33(5) maddesi, bildirmediğiniz ihlaller dahil her ihlali belgelemenizi gerektirir. Ayrıntılar için 72 saat rehberimize bakın. Tam temizlik için kötü amaçlı yazılım temizleme planımızı izleyin.
uploads klasöründe PHP çalışmasını engelleyin (Apache ve nginx)
Meşru bir medya dosyasının PHP olarak çalışmasına hiçbir zaman gerek yoktur. Apache 2.4'te wp-content/uploads/.htaccess dosyasını oluşturun:
<FilesMatch "\.(?i:php|phtml|phar)$">
Require all denied
</FilesMatch>
Apache bu dosyayı yalnızca AllowOverride o dizin için izin veriyorsa dikkate alır; Require için bu All ya da en azından AuthConfig demektir. Override'lar kapalıysa hosting sağlayıcınızdan bloğu, bu sitenin uploads dizininin mutlak dosya sistemi yoluyla sınırlanmış bir <Directory> bölümüne koymasını isteyin. Bloğu kapsam belirtmeden sanal sunucuya (virtual host) koymayın; siteyi genelinde PHP'yi engelleyebilir.
nginx'te şunu server bloğuna ekleyin:
location ~* ^/wp-content/uploads/.*\.(php|phtml|phar)$ { deny all; }
nginx önce en uzun eşleşen önek location'ını bulur. Bu location ^~ kullanıyorsa, örneğin location ^~ /wp-content/uploads/, nginx regex location'larını hiç değerlendirmez ve kuralınız yok sayılır. Bu durumda deny kuralı o bloğun içine girmelidir. Aksi halde regex location'ları sırayla denetlenir ve ilk eşleşen kazanır; yani bu kural genel location ~ \.php$ bloğunuzdan önce gelmelidir. WordPress /blog/ gibi bir alt dizindeyse ya da özel bir uploads URL'si kullanılıyorsa yolu uyarlayın. Etkin yapılandırmayı kontrol edin, sonra yeniden yükleyin:
sudo nginx -T | grep -n 'location'
sudo nginx -t && sudo systemctl reload nginx
Yönetilen hostinglerde kuralı uygulamasını destek ekibinden isteyin.
Bu kurallar, sunucu yapılandırması uyguladığı sürece listelenen uzantılarla eşleşen doğrudan web isteklerini reddeder. Çalıştırılabilir her son eki kapsamazlar ve başka bir PHP betiğinin yüklenmiş bir dosyayı include etmesini engellemezler. Shell'leri silmezler, yüklemeleri de önlemezler. Hosting sağlayıcınıza hangi uzantıların PHP'ye eşlendiğini sorun ve gerekirse listeyi genişletin.
Kuralı bir test dosyasıyla doğrulayın
Yeni, rastgele bir dosya adı kullanın. noclobber ayarlıyken komut var olan bir dosyanın üzerine yazmayı reddeder ve yalnızca kendi oluşturduğu dosyayı siler:
t="wp-content/uploads/exec-test-$(date +%s)-$RANDOM.php"
if ( set -o noclobber; printf '%s\n' '<?php echo "marker-" . (40 + 2);' > "$t" ); then
curl -sS -i "https://example.com/$t"
rm -- "$t"
else
echo "Not created: $t already exists"
fi
Yanıt gövdesinde marker-42 varsa PHP dosyayı çalıştırmıştır, çünkü bu dize dosyanın kendisinde yazmıyor. 200 durum koduyla ham <?php satırını görüyorsanız PHP çalışmamış, ama sunucu kaynak kodu açığa çıkarıyordur. Bu, hosting sağlayıcınızı ilgilendiren ayrı bir sorundur. Tek başına 200 durumu hiçbir şeyi kanıtlamaz. 403 ise yalnızca onu bir CDN veya WAF değil, sizin kuralınız ürettiyse sayılır. Orijin sunucuya karşı curl --resolve example.com:443:ORIGIN_IP ile test edin ve orijin hata günlüğünde "access forbidden by rule" (nginx) ya da "client denied by server configuration" (Apache) ifadesini arayın.
WordPress dosya izinleri ve sahipliği: sıfırlamayın, gözden geçirin
İzinler ve sahiplik birlikte PHP sürecinin neyi değiştirebileceğini belirler. Tek başına sahiplik bunu çözmez, çünkü grup bitleri, "diğer" bitleri ve ACL'ler de yazma erişimi verir. Tüm sitede özyinelemeli (recursive) chmod çalıştırmayın. Yedekler gibi özel dosyaları herkes tarafından okunabilir yapabilir, bakım betiklerinin çalıştırma bitini de silebilir.
Önce dosyaların kime ait olduğunu ve PHP'nin hangi kullanıcıyla çalıştığını bulun:
stat -c '%U:%G %a %n' wp-config.php wp-content wp-content/uploads
ps -eo user,group,comm | grep -E 'php-fpm|php|httpd|apache2' | sort -u
Birçok paylaşımlı hostta PHP, kendi hesap kullanıcınızla çalışır. Bu kurulumda salt okunur mod bitleri sınırlı koruma sağlar: dosya sahibi, başka dosya sistemi ya da hosting kısıtları engellemiyorsa yazma erişimini genellikle geri verebilir.
Sonra PHP kullanıcısının gerçekten yazabildiği her şeyi ve herkesin yazabildiği (world-writable) yolları listeleyin. PHPUSER yerine bulduğunuz kullanıcıyı yazın:
sudo -u PHPUSER find . -writable ! -type l -print
find . -perm -o+w ! -type l -ls
İki listeyi ayrı ayrı değerlendirin: herkesin yazabildiği yolların genellikle düzeltilmesi gerekir; PHP'nin yazabildiği yollar ise güncelleme modelinize bağlıdır. Salt okunur bir dağıtımda PHP yazmaları normalde uploads ve gerekli çalışma zamanı klasörleriyle sınırlı olmalıdır; panelden ve otomatik güncellemeler için ek yazma erişimi gerekir. İncelediğiniz yolları tek tek, sembolik kaldırmayla düzeltin, örneğin chmod g-w,o-w path/to/file. Bit kaldırmak, mevcut daha sıkı bir modu hiçbir zaman gevşetmez. WordPress sertleştirme rehberi dizinler için 755'i, dosyalar için 644'ü olağan üst sınır sayar. wp-config.php dosyasını PHP'nin onu nasıl okuduğuna bağlı olarak 640 ya da 600 yapın ve sonra siteyi test edin. Asla 777 kullanmayın.
Kaçınılmaz bir sınır var. PHP'nin uploads klasörüne yazabilmesi gerekir; yani o kullanıcıyla çalışan savunmasız bir eklenti oraya yine yazabilir.
wp-config.php içindeki define( 'DISALLOW_FILE_EDIT', true ); panel dosya editörlerini kaldırır. DISALLOW_FILE_MODS daha da ileri gider, ama arka plandaki otomatik güncellemeleri de kapatır. Yalnızca başka bir yolla güncelleme yapıyorsanız kullanın.
Shell'i içeri alan açığı kapatın, sonra uploads'u izlemeye devam edin
Shell'i silmek nedeni çözmez. Giriş noktası açık kaldıkça yeniden bulaşma mümkündür.
Kaydettiğiniz stat çıktısını erişim günlüğüyle karşılaştırın. Dosya ortaya çıkmadan kısa süre önce POST alan formu ya da eklenti uç noktasını arayın. Sonra eklentiyi güncelleyin ya da yeniden yapılandırın: izin verilen dosya türlerini kısıtlayın ve eklenti destekliyorsa ekleri web kökünün dışında saklayın. Güvenli bir güncelleme ya da önlem yoksa eklentiyi silin. Devre dışı bırakmak güvenilir bir alternatif değildir, çünkü dosyaları diskte kalır ve bir kısmına hâlâ doğrudan erişilebilir. Ardından yönetici parolalarını, wp-config.php içindeki salt değerlerini ve veritabanı kimlik bilgilerini yenileyin.
Sonrasında izlemeye devam edin:
- Birleşik
findkomutunu günlük bir cron işinde çalıştırın ve çıktı varsa size e-posta gönderilsin. - Dosya bütünlüğü izleme ekleyin.
wp core verify-checksumskomutunu düzenli çalıştırın.
secaudit.eu'nun Security Audit & Compliance eklentisi izlemede yardımcı olur. Bir dosya bütünlüğü referansı tutar, değişiklikleri ve gizlenmiş (obfuscated) içeriği raporlar, kötü amaçlı yazılım imzalarını ve ele geçirilme göstergelerini denetler. Tespit eder ve raporlar; hiçbir şeyi kaldırmaz. Diğer uyarı işaretleri için WordPress sitenizin hacklendiğini gösteren 12 işaret yazısına bakın.
SSS
wp-content/uploads içindeki her PHP dosyası kötü amaçlı mıdır?
Hayır. Yer tutucu index.php dosyaları ve bazı eklenti klasörleri meşrudur. Meta verileri kaydedin, dosyayı çalıştırmadan inceleyin ve klasörün hangi eklentiye ait olduğunu kontrol edin.
Bir güvenlik eklentisi ya da WAF uploads kuralını gereksiz kılar mı?
Hayır. Bir güvenlik duvarı yeni bir saldırı desenini kaçırabilir. Sunucu kuralı ayrı bir katmandır ve bir yükleme içeri girse bile listelenen uzantılar için doğrudan istekleri reddeder.
Zaten bir shell bulduysam uploads'ta PHP'yi engellemek yeterli mi?
Hayır. Delilleri saklayın, siteyi temizleyin ve giriş noktasını kapatın. Shell, başka yerlere dosya yerleştirmek ya da veritabanınızı okumak için çoktan kullanılmış olabilir.
Bu makale genel bilgi niteliğindedir, hukuki danışmanlık değildir.
Kendi sitenizi kontrol edin
Security Audit & Compliance, secaudit.eu’nun WordPress eklentisidir: zararlı yazılımları, gizli spam’i, değiştirilmiş çekirdek dosyalarını ve açıklı kodu tarar; bulguları GDPR Md. 32, NIS2 Md. 21 ve OWASP Top 10 ile eşleştirir.
Eklentinin neleri kontrol ettiğini görün