WordPress Güvenlik Başlıkları Ekleme: functions.php ile HTTP Headers
Web sitenizi yayına aldıktan sonra en çok ihmal edilen konulardan biri HTTP güvenlik başlıklarıdır. Çoğu WordPress kurulumunda bu başlıklar ya hiç ayarlanmamış ya da eksik bırakılmıştır. Oysa bu başlıklar, XSS saldırılarından clickjacking’e, MIME sniffing’den zorla HTTP bağlantısına kadar pek çok saldırı vektörünü kapatır. Bu yazıda, güvenlik başlıklarını doğrudan functions.php üzerinden nasıl ekleyeceğinizi, her başlığın ne işe yaradığını ve gerçek dünya senaryolarında nasıl yapılandırmanız gerektiğini adım adım anlatacağım.
Neden .htaccess veya Nginx Değil de functions.php?
Güvenlik başlıklarını eklemek için birden fazla yöntem mevcut. Apache kullanıyorsanız .htaccess, Nginx kullanıyorsanız sunucu konfigürasyon dosyası ilk akla gelen seçeneklerdir. Peki neden functions.php yolunu tercih edelim?
Birkaç pratik sebebi var:
- Taşınabilirlik: Sitenizi başka bir sunucuya taşıdığınızda
.htaccessveya Nginx config dosyalarını unutabilirsiniz.functions.phptemanızla veya eklentinizle birlikte gelir. - Paylaşımlı hosting: Birçok paylaşımlı hosting sağlayıcısında
.htaccessveya Nginx konfigürasyonuna erişiminiz kısıtlı olabilir. - Merkezi yönetim: WordPress içinde her şeyi tek yerden yönetmek, özellikle birden fazla site bakıyorsanız çok daha kolaydır.
- Çakışma riski: Bazı hosting panelleri
.htaccessdosyasını otomatik düzenler ve eklediğiniz başlıkların üzerine yazabilir.
Ama şunu da söylemem gerekiyor: Bu yöntemin bir dezavantajı var. PHP ile eklenen başlıklar, PHP çalışmadan önce gönderilen statik dosyalara (resimler, CSS, JS) uygulanmaz. Yani tam kapsamlı bir güvenlik için functions.php ile sunucu tarafı konfigürasyonu birlikte kullanmak idealdir. Ama sadece WordPress sayfaları için dahi olsa bu yöntem, hiçbir şey yapmamaktan çok daha iyidir.
Temel Güvenlik Başlıkları ve Ne İşe Yararlar
Kodlara geçmeden önce hangi başlıkları ekleyeceğimizi ve bunların ne anlama geldiğini bilmek önemli.
X-Content-Type-Options
Tarayıcının, sunucunun bildirdiği MIME tipini yoksayıp içeriği “tahmin etmesini” (MIME sniffing) engeller. Örneğin bir saldırgan .jpg uzantılı ama aslında JavaScript olan bir dosya yükleyebilir. Bu başlık olmadan bazı tarayıcılar bu dosyayı JS olarak çalıştırabilir.
X-Frame-Options
Sitenizin bir içinde başka bir sitede gösterilmesini engeller. Bu, clickjacking saldırılarına karşı temel savunma mekanizmasıdır. Bir saldırgan, sitenizi görünmez bir iframe içine alıp kullanıcıları kandırarak farkında olmadan işlem yaptırabilir.
X-XSS-Protection
Eski nesil tarayıcılarda (özellikle IE) yerleşik XSS filtrelerini etkinleştirir. Modern tarayıcılar bu başlığı büyük ölçüde görmezden gelse de eski tarayıcılar için ekstra bir koruma katmanı sağlar.
Referrer-Policy
Kullanıcılar sitenizden başka bir siteye geçtiğinde hangi referrer bilgisinin iletileceğini kontrol eder. Özellikle admin URL’leri veya özel parametreler içeren sayfalardan çıkan trafiğin takip edilmesini önler.
Permissions-Policy (eski adıyla Feature-Policy)
Tarayıcının hangi özelliklerini (kamera, mikrofon, konum vs.) kullanabileceğinizi kısıtlar. Sitenizde ihtiyaç duymadığınız özellikler üçüncü parti scriptler tarafından kötüye kullanılamaz.
Strict-Transport-Security (HSTS)
Tarayıcıya “bu siteye bir daha asla HTTP ile bağlanma, her zaman HTTPS kullan” der. Bu başlığı yanlış yapılandırmak sitenizi erişilemez yapabilir, bu yüzden dikkatli kullanmak gerekiyor.
Content-Security-Policy (CSP)
En güçlü ama aynı zamanda en karmaşık başlık. Tarayıcıya hangi kaynaklardan içerik yükleyebileceğini söyler. Doğru yapılandırılmış bir CSP, XSS saldırılarının büyük çoğunluğunu engeller.
Temel Uygulama: Hepsini Bir Fonksiyonla Eklemek
İlk adım olarak en yaygın güvenlik başlıklarını ekleyen temel bir fonksiyon yazalım. Bu kodu functions.php dosyanıza ekleyin:
function add_security_headers() {
// MIME sniffing koruması
header( 'X-Content-Type-Options: nosniff' );
// Clickjacking koruması - sadece aynı origin iframe kullanabilir
header( 'X-Frame-Options: SAMEORIGIN' );
// XSS koruma (eski tarayıcılar için)
header( 'X-XSS-Protection: 1; mode=block' );
// Referrer politikası
header( 'Referrer-Policy: strict-origin-when-cross-origin' );
// İzin politikası - gereksiz tarayıcı özelliklerini kapat
header( 'Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()' );
}
add_action( 'send_headers', 'add_security_headers' );
send_headers action hook’u, WordPress başlıkları göndermeden hemen önce tetiklenir. Bu sayede başlıklarımız diğer WordPress başlıklarıyla birlikte gönderilir.
HSTS Başlığını Dikkatli Eklemek
HSTS başlığını eklemeden önce şunlardan emin olun:
- Sitenizde geçerli bir SSL sertifikası var
- SSL sertifikanızın süresi dolmayacak veya sürekli yenilenecek
- HTTP’den HTTPS’e yönlendirme düzgün çalışıyor
Eğer bu şartlar sağlanmıyorsa HSTS eklemeyin. Yanlış eklenen HSTS, sitenizi aylar boyunca erişilemez yapabilir çünkü tarayıcılar bu direktifi cache’ler.
function add_hsts_header() {
// Sadece HTTPS bağlantısında HSTS ekle
if ( is_ssl() ) {
// max-age: 1 yıl (saniye cinsinden)
// includeSubDomains: tüm subdomainler için geçerli
// preload: tarayıcı preload listesine eklenmek için (isteğe bağlı)
header( 'Strict-Transport-Security: max-age=31536000; includeSubDomains' );
}
}
add_action( 'send_headers', 'add_hsts_header' );
İpucu: Başlangıçta max-age değerini düşük tutun (örneğin max-age=300 yani 5 dakika). Her şeyin düzgün çalıştığını gördükten sonra 1 yıla çıkarın.
Content Security Policy: Gerçek Dünya Senaryosu
CSP, en güçlü ama yapılandırması en zor başlıktır. Özellikle Google Analytics, Facebook Pixel, reklam scriptleri gibi üçüncü parti kaynaklar kullanan sitelerde yanlış yapılandırılmış bir CSP sitenizi bozabilir.
WooCommerce kullanan bir e-ticaret sitesi için gerçekçi bir CSP örneği:
function add_csp_header() {
$csp_directives = array(
// Varsayılan kaynak - açıkça belirtilmeyenler için
"default-src 'self'",
// Script kaynakları
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google-analytics.com https://www.googletagmanager.com https://connect.facebook.net https://js.stripe.com",
// Style kaynakları
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
// Font kaynakları
"font-src 'self' https://fonts.gstatic.com",
// Resim kaynakları
"img-src 'self' data: https://www.google-analytics.com https://www.googletagmanager.com https://www.facebook.com",
// Frame kaynakları
"frame-src 'self' https://js.stripe.com https://www.youtube.com",
// Bağlantı kaynakları (fetch, XHR vs.)
"connect-src 'self' https://www.google-analytics.com https://stats.g.doubleclick.net",
);
$csp = implode( '; ', $csp_directives );
header( "Content-Security-Policy: {$csp}" );
}
add_action( 'send_headers', 'add_csp_header' );
Dikkat: 'unsafe-inline' ve 'unsafe-eval' güvenli değil, ama çoğu WordPress plugin’i bu direktiflere ihtiyaç duyar. Eğer mümkünse zamanla bunları kaldırıp nonce tabanlı CSP’ye geçmeyi hedefleyin.
CSP’yi Test Modunda Çalıştırmak
CSP’yi direkt olarak uygulamak yerine önce Content-Security-Policy-Report-Only başlığıyla test edin. Bu mod, politikayı uygulamaz ama ihlalleri raporlar:
function add_csp_report_only() {
// Sadece geliştirme/test ortamında kullan
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
$csp_directives = array(
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data:",
// İhlalleri bu URL'ye rapor et
"report-uri /wp-json/csp-violations/report",
);
$csp = implode( '; ', $csp_directives );
header( "Content-Security-Policy-Report-Only: {$csp}" );
}
}
add_action( 'send_headers', 'add_csp_report_only' );
Tarayıcı geliştirici araçlarının Konsol sekmesinde CSP ihlallerini görebilirsiniz. Bu ihlallere bakarak hangi kaynakları listeye eklemeniz gerektiğini anlarsınız.
Admin Paneli için Farklı Başlıklar
WordPress admin paneli, bazı başlıklarla uyumsuzluk yaşayabilir. Özellikle bazı plugin’ler iframe kullandığında X-Frame-Options: DENY her şeyi kırabilir. Bu yüzden admin paneli için başlıkları ayrı yapılandırmak mantıklı olabilir:
function add_conditional_security_headers() {
// Admin panelinde daha esnek başlıklar
if ( is_admin() ) {
header( 'X-Content-Type-Options: nosniff' );
header( 'X-XSS-Protection: 1; mode=block' );
// Admin panelinde SAMEORIGIN kullan, DENY değil
header( 'X-Frame-Options: SAMEORIGIN' );
} else {
// Frontend için daha katı başlıklar
header( 'X-Content-Type-Options: nosniff' );
header( 'X-XSS-Protection: 1; mode=block' );
header( 'X-Frame-Options: DENY' );
header( 'Referrer-Policy: strict-origin-when-cross-origin' );
header( 'Permissions-Policy: camera=(), microphone=(), geolocation=()' );
}
}
add_action( 'send_headers', 'add_conditional_security_headers' );
Login Sayfası için Ekstra Güvenlik
WordPress login sayfası (wp-login.php) saldırganların en çok hedef aldığı sayfadır. Bu sayfa için ekstra başlıklar ekleyelim:
function add_login_page_headers() {
// Sadece login sayfasında çalış
$is_login_page = ( isset( $GLOBALS['pagenow'] ) && $GLOBALS['pagenow'] === 'wp-login.php' );
if ( $is_login_page ) {
// Login sayfasını cache'leme
header( 'Cache-Control: no-store, no-cache, must-revalidate, max-age=0' );
header( 'Pragma: no-cache' );
// Login sayfası hiçbir iframe içinde görünmesin
header( 'X-Frame-Options: DENY' );
// Referrer bilgisini tamamen gizle
header( 'Referrer-Policy: no-referrer' );
// Strict CSP
$login_csp = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:";
header( "Content-Security-Policy: {$login_csp}" );
}
}
add_action( 'send_headers', 'add_login_page_headers' );
Tüm Başlıkları Birleştiren Kapsamlı Çözüm
Yukarıdaki tüm örnekleri tek, organize bir yapıda birleştirelim. Bu kodu functions.php dosyanıza veya özel bir plugin dosyasına ekleyebilirsiniz:
class WP_Security_Headers {
private $is_ssl;
private $is_admin;
private $is_login;
public function __construct() {
$this->is_ssl = is_ssl();
$this->is_admin = is_admin();
$this->is_login = ( isset( $GLOBALS['pagenow'] ) && $GLOBALS['pagenow'] === 'wp-login.php' );
add_action( 'send_headers', array( $this, 'send_security_headers' ) );
}
public function send_security_headers() {
$this->send_basic_headers();
if ( $this->is_ssl ) {
$this->send_hsts_header();
}
if ( $this->is_login ) {
$this->send_login_headers();
} elseif ( ! $this->is_admin ) {
$this->send_frontend_csp();
}
}
private function send_basic_headers() {
header( 'X-Content-Type-Options: nosniff' );
header( 'X-XSS-Protection: 1; mode=block' );
header( 'X-Frame-Options: SAMEORIGIN' );
header( 'Referrer-Policy: strict-origin-when-cross-origin' );
header( 'Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()' );
}
private function send_hsts_header() {
header( 'Strict-Transport-Security: max-age=31536000; includeSubDomains' );
}
private function send_frontend_csp() {
// Kendi ihtiyacınıza göre özelleştirin
$csp = "default-src 'self'; "
. "script-src 'self' 'unsafe-inline' https://www.google-analytics.com; "
. "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; "
. "font-src 'self' https://fonts.gstatic.com; "
. "img-src 'self' data: https:; "
. "connect-src 'self' https://www.google-analytics.com";
header( "Content-Security-Policy: {$csp}" );
}
private function send_login_headers() {
header( 'X-Frame-Options: DENY' );
header( 'Referrer-Policy: no-referrer' );
header( 'Cache-Control: no-store, no-cache, must-revalidate' );
}
}
new WP_Security_Headers();
Başlıkları Doğrulama ve Test Etme
Başlıklarınızı ekledikten sonra düzgün çalışıp çalışmadığını test etmeniz şart. Birkaç farklı yöntem var:
Tarayıcı geliştirici araçları: Chrome veya Firefox’ta F12’ye basın, Network sekmesine gidin, sitenizin ana isteğine tıklayın ve Response Headers bölümünü inceleyin.
Online araçlar:
- securityheaders.com: Sitenizi analiz edip her başlık için A’dan F’ye kadar not verir, eksiklikleri açıklar.
- observatory.mozilla.org: Mozilla’nın güvenlik tarama aracıdır, çok detaylı raporlar sunar.
Komut satırından test:
# curl ile başlıkları kontrol et
curl -I https://siteniz.com
# Sadece güvenlik başlıklarını filtrele
curl -I https://siteniz.com 2>/dev/null | grep -i -E "(x-content|x-frame|x-xss|strict-transport|content-security|referrer|permissions)"
# Verbose çıktı ile tüm başlıkları gör
curl -v https://siteniz.com 2>&1 | grep -i "< "
Sık Karşılaşılan Sorunlar ve Çözümleri
Güvenlik başlıklarını eklerken bazı şeylerin bozulduğunu görebilirsiniz. İşte en sık karşılaşılan sorunlar:
Google reCAPTCHA çalışmıyor: CSP’nize https://www.google.com ve https://www.gstatic.com adreslerini ekleyin.
YouTube veya Vimeo embed çalışmıyor: frame-src direktifine https://www.youtube.com ve https://player.vimeo.com ekleyin.
WooCommerce ödeme sayfası bozuldu: Stripe için https://js.stripe.com, PayPal için https://www.paypal.com ekleyin. frame-src ve script-src direktiflerini güncellemeniz gerekebilir.
Google Fonts yüklenmiyor: style-src direktifine https://fonts.googleapis.com, font-src direktifine de https://fonts.gstatic.com ekleyin.
Admin panelinde bazı sayfalar iframe hata veriyor: X-Frame-Options: SAMEORIGIN kullandığınızdan emin olun, DENY admin panelini kırabilir.
Inline JavaScript çalışmıyor: Eğer 'unsafe-inline' kullanmak istemiyorsanız script etiketlerinize nonce eklemek gerekir. Bu WordPress’te karmaşık bir süreçtir, başlangıç için 'unsafe-inline' kabul edilebilir.
Özel Başlık: WordPress’e Özgü Bilgileri Gizlemek
Güvenlik başlıklarına ek olarak, WordPress’in varsayılan olarak açığa çıkardığı bazı bilgileri de gizlemeniz faydalı olur:
function remove_wordpress_headers() {
// WordPress versiyonunu generator meta'dan kaldır
remove_action( 'wp_head', 'wp_generator' );
// X-Powered-By başlığını kaldır
header_remove( 'X-Powered-By' );
// Server başlığını değiştir (sadece PHP cgi/fpm modunda çalışır)
// header( 'Server: ' ); // Bazı sunucularda bu çalışmayabilir
// WordPress REST API'nin kullanıcı bilgilerini açığa çıkarmasını engelle
// (Bu functions.php'de ayrıca yapılmalı)
}
add_action( 'send_headers', 'remove_wordpress_headers' );
// wp-json/wp/v2/users endpoint'ini devre dışı bırak
function disable_user_enumeration( $endpoints ) {
if ( ! current_user_can( 'administrator' ) ) {
if ( isset( $endpoints['/wp/v2/users'] ) ) {
unset( $endpoints['/wp/v2/users'] );
}
if ( isset( $endpoints['/wp/v2/users/(?P<id>[d]+)'] ) ) {
unset( $endpoints['/wp/v2/users/(?P<id>[d]+)'] );
}
}
return $endpoints;
}
add_filter( 'rest_endpoints', 'disable_user_enumeration' );
Sonuç
Güvenlik başlıkları, sitenizi korumak için yapabileceğiniz en ucuz ve en hızlı iyileştirmelerden biridir. Birkaç dakikalık bir çalışmayla XSS, clickjacking, MIME sniffing gibi yaygın saldırı vektörlerini büyük ölçüde kapatabilirsiniz.
Başlangıç için önerim şu sırayı izleyin:
- Önce
X-Content-Type-Options,X-Frame-OptionsveX-XSS-Protectiongibi risksiz başlıklarla başlayın. - Sonra
Referrer-PolicyvePermissions-Policyekleyin. - HTTPS kullanıyorsanız
Strict-Transport-Securitybaşlığını küçük birmax-agedeğeriyle ekleyin ve test edin. - En son olarak CSP’yi
Report-Onlymodunda test edip yavaş yavaş sıkılaştırın.
Her değişikliği yaptıktan sonra sitenizi kapsamlı şekilde test edin. Özellikle ödeme sayfaları, form gönderimleri ve üçüncü parti entegrasyonlar dikkat gerektiren noktalardır. securityheaders.com veya observatory.mozilla.org gibi araçları düzenli olarak kullanarak güvenlik puanınızı takip edin.
Son olarak, bu başlıkları functions.php yerine bir mu-plugin (must-use plugin) içine almanızı tavsiye ederim. Bu şekilde tema değişikliklerinden etkilenmezler ve WordPress güncellemelerinde kaybolma riski taşımazlar. /wp-content/mu-plugins/security-headers.php yolu ideal bir konum olacaktır.
