Neden “şimdi”?
Saldırı yüzeyleri büyüdü: eklenti tedarik zinciri, bot ağları, zayıf parola/2FA eksikliği, istek seli (rate limit), XML-RPC kötüye kullanımı, REST uçlarının yanlış konfigürasyonu… Ölçü birimi tek: ihlal sonrası değil, ihlal öncesi hazırlık. Bu rehber; WordPress (WP) projelerini CSP, SRI, güvenlik başlıkları, WAF ve en az ayrıcalık ilkeleriyle güçlendirmenin pratik yol haritasıdır.
1) Mimari & Güncelleme Disiplini
- Çekirdek/eklenti/tema güncellemelerini minör sürümler için otomatik, majörler için kademeli/staging.
- PHP 8.2+ / 8.3 kullan; EOL olmayan sürüm şart.
- Eklenti/tema diyet: Kullanılmayanı kaldır; benzer işi yapan birden fazla eklentiyi tutma.
- Tedarik zinciri: Yayıncı itibarı, commit aktivitesi, changelog düzenliliği, güvenlik duyuruları.
2) Kimlik & Erişim: Parola değil, Passkey/2FA
- Passkeys (WebAuthn) ve/veya 2FA zorunlu: Özellikle
administratoriçin. - Uygulama Parolaları (Application Passwords) yalnızca gerektiğinde ve kısıtlı süreyle.
- En az ayrıcalık: Editör, Shop Manager vs. rolleri gerçekten ihtiyacı kadar yetkili.
- IP/Ülke bazlı WAF kuralları:
wp-login.phpvexmlrpc.phpiçin hız limiti / ülke zorluğu.
3) wp-config.php Sertleştirme
// HTTPS
define('FORCE_SSL_ADMIN', true);
// Dosya düzenlemeyi kapat (Panel > Görünüm > Tema düzenleyici vb.)
define('DISALLOW_FILE_EDIT', true);
// (İsteğe bağlı) Panelden eklenti/tema yükleme/güncellemeyi kapat
// define('DISALLOW_FILE_MODS', true);
// Çekirdek minör otomatik güncellemeler açık kalsın
define('WP_AUTO_UPDATE_CORE', 'minor');
// Ortam etiketi (günlükleme/diagnostics için)
define('WP_ENVIRONMENT_TYPE', 'production');
// Güçlü AUTH KEYS & SALTS (her projede benzersiz!)
/*
define('AUTH_KEY','...'); // hepsi benzersiz olacak
...
*/
// Oturum/cookie ömrü ve güvenlik ayarlarını ihtiyaca göre sıkılaştır
Not: SALTS anahtarlarını mutlaka yenile; depoya koyma.
wp-config.phpmümkünse 600 izinleriyle korunmalı.
4) Sunucu Katmanı: Güvenlik Başlıkları (Headers) + HSTS + CSP
Önerilen başlıklar
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadX-Content-Type-Options: nosniffX-Frame-Options: SAMEORIGIN(veya CSPframe-ancestors)Referrer-Policy: strict-origin-when-cross-originPermissions-Policy: camera=(), microphone=(), geolocation=()Cross-Origin-Opener-Policy: same-originCross-Origin-Resource-Policy: same-origin
CSP (Content-Security-Policy)
CSP, XSS’i etkin şekilde bastırır. Önce “Report-Only” yayınla, raporları topla, sonra enforce et.
Başlangıç şablonu (sert, ama gerçek hayata uyarlayın):
Content-Security-Policy:
default-src 'self';
script-src 'self' https://www.googletagmanager.com https://www.google-analytics.com 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline' https:;
img-src 'self' data: https:;
font-src 'self' data: https:;
connect-src 'self' https:;
frame-src https:;
frame-ancestors 'self';
base-uri 'self';
Gerçek kaynaklarınıza göre daraltın (CDN, ödeme iframe’leri, haritalar…). Önce Report-Only ile sahada test!
5) SRI (Subresource Integrity)
CDN’den çektiğiniz JS/CSS için SRI hash’leri kullanın.
// functions.php
wp_enqueue_script('alpine-cdn', 'https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js', [], null, true);
add_filter('script_loader_tag', function($tag, $handle, $src){
if ($handle === 'alpine-cdn') {
$integrity = 'sha384-XXXXXXXX'; // gerçek hash
return sprintf('<script src="%s" integrity="%s" crossorigin="anonymous" defer></script>',
esc_url($src), esc_attr($integrity));
}
return $tag;
}, 10, 3);
Hash’i yayına çıkmadan üretip sabitleyin; dosya sürümü değişirse hash’i de güncelleyin.
6) Uygulama Katmanı: REST, XML-RPC, Nonce, Sanitization
XML-RPC’yi kapat veya sınırla:
# .htaccess
<Files "xmlrpc.php">
Require all denied
</Files>
REST API’yi sınırlamak (kamuya kapatıp oturum açmışlara izin vermek) – Gutenberg/headless ihtiyacınıza göre uyarlayın:
add_filter('rest_authentication_errors', function($result){
if (!empty($result)) return $result;
if (is_user_logged_in()) return $result;
return new WP_Error('rest_forbidden', 'REST API sadece oturumla kullanılabilir.', ['status' => 401]);
});
Kullanıcı enumerasyonunu engelle:
add_action('template_redirect', function(){
if (is_author()) { wp_redirect(home_url(), 301); exit; }
});
Nonce + veri hijyeni: Form/endpoint’lerde nonce doğrula, girdileri sanitize_* ile temizle, çıktılayanı esc_* ile kaçıştır; SQL sorgularında $wpdb->prepare() kullan.
7) Dosya Sistemi & İzinler
- Sahiplik/izinler: Klasörler
755, dosyalar644,wp-config.php600. - Uploads’ta PHP çalışmasın:
# wp-content/uploads/.htaccess
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
.git,.env, yedek.zipgibi hassas dosyaları public dizinde tutma.
8) WAF/CDN Katmanı (Cloudflare vb.)
wp-login.phpvexmlrpc.phpiçin Rate Limiting.- Bot/Challenge kuralları (ülke/ASN/kaynak hesaplamalı).
- Cache By-Pass:
/wp-admin/,preview=true,?s=aramaları. - Turnstile / hCaptcha gibi kullanıcı dostu korumalar.
9) İzleme, Loglama, Olay Müdahalesi
- Giriş, yetki değişimi, eklenti/tema değişiklikleri için denetim günlükleri.
- 404/5xx ani sıçramalarını uyarı eşiği ile yakala.
- 3-2-1 yedek: 3 kopya, 2 farklı ortam, 1’i off-site; geri yükleme provası yap.
10) Hızlı Sorun Giderme (403/500/Bozulma Senaryoları)
- 403: WAF/ModSecurity/izinler;
wflogs, güvenlik eklentisi kilitleri;.htaccessson değişiklikleri. - Beyaz sayfa:
WP_DEBUG_LOGile hata kaydı; sorunlu eklentiyipluginsklasör adını değiştirerek izole et. - Admin-Ajax 0 döndürme: nonce/permission check; CORS, cache veya minify çakışması.
Sunucuya Göre Örnek Konfigürasyonlar
Apache (.htaccess) – Örnek paket
# Temel korumalar
<FilesMatch "^(wp-config\.php|\.htaccess)$">
Require all denied
</FilesMatch>
# XML-RPC kapat
<Files "xmlrpc.php">
Require all denied
</Files>
# Uploads'ta PHP yok
<Directory "/path/to/wp-content/uploads">
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
</Directory>
# Güvenlik başlıkları (CSP'yi önce Report-Only yayına alın)
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Header always set Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline' https:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; font-src 'self' data: https:; connect-src 'self' https:; frame-ancestors 'self'; frame-src https:; base-uri 'self'"
Nginx – Örnek paket
# XML-RPC kapat
location = /xmlrpc.php { return 403; }
# Uploads'ta PHP engeli
location ~* ^/wp-content/uploads/.*\.php$ { deny all; }
# Güvenlik başlıkları
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# add_header Content-Security-Policy-Report-Only "default-src 'self'; ..." always;
IIS (Windows Server) – web.config (özet)
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="X-Content-Type-Options" value="nosniff" />
<add name="X-Frame-Options" value="SAMEORIGIN" />
<add name="Referrer-Policy" value="strict-origin-when-cross-origin" />
<add name="Permissions-Policy" value="camera=(), microphone=(), geolocation=()" />
<add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains; preload" />
<!-- CSP'yi önce Report-Only deneyip sonra Content-Security-Policy'ye alın -->
<!-- <add name="Content-Security-Policy-Report-Only" value="default-src 'self'; ..." /> -->
</customHeaders>
</httpProtocol>
<!-- xmlrpc.php engeli -->
<security>
<requestFiltering>
<fileExtensions>
<add fileExtension=".php" allowed="true" />
</fileExtensions>
<hiddenSegments>
<add segment="xmlrpc.php" />
</hiddenSegments>
</requestFiltering>
</security>
<!-- uploads'ta php çalıştırmayı engellemek için ayrı site/config kuralı önerilir -->
</system.webServer>
</configuration>
functions.php ile başlık eklemek (sunucu yerine uygulamada)
add_action('send_headers', function () {
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('Referrer-Policy: strict-origin-when-cross-origin');
header('Permissions-Policy: camera=(), microphone=(), geolocation=()');
header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
// CSP'yi burada yönetmek zor; sunucuda vermek daha sağlıklı.
});