WordPress yönetim panelinde uzunca bir yazıyı düzenlediniz, "Güncelle"ye bastınız ve ekran bembeyaz bir Apache sayfasına döndü: Not Acceptable — An appropriate representation of the requested resource could not be found. Geri tuşuna bastığınızda yazınız duruyor ama kaydedilmemiş. Aynı içeriği bir kelime silerek tekrar kaydedince sorunsuz geçiyor. Ya da bir iletişim formunda "<" karakteri geçen bir mesaj gönderildiğinde 403 Forbidden alıyorsunuz, aynı form başka metinlerde çalışıyor.
Bu davranışın imzası nettir: hata rastgele değil, içeriğe bağlı. Sunucu kaynaklı bir arıza olsaydı her istekte tekrar ederdi; izin sorunu olsaydı sayfa hiç açılmazdı. İçeriğe göre değişen bir 403 veya 406, neredeyse her zaman bir web uygulama güvenlik duvarının — çoğu barındırma sunucusunda ModSecurity'nin — isteği bir saldırı imzasına benzetip düşürdüğü anlamına gelir.
İnternette bu sorunun karşılığı olarak yıllardır dolaşan tavsiye .htaccess dosyasına SecFilterEngine Off yazmaktır. Bu satır hem çalışmaz hem de çalıştığı senaryoda tehlikelidir; birazdan ikisinin de nedenini göreceğiz. Doğru yol üç adımdır: engellemeyi doğrulamak, günlükten tetiklenen kuralın ID'sini bulmak ve yalnızca o kuralı yalnızca ilgili yol için muaf tutmak. Paylaşımlı barındırmadaysanız üçüncü adımı sağlayıcınız yapar — bu yazının sonunda ondan tam olarak neyi isteyeceğinizi de bileceksiniz.
403 ile 406 Arasındaki Fark Neyi Anlatıyor?#
ModSecurity bir isteği düşürdüğünde döndüreceği HTTP durum kodu yapılandırmada belirlenir. SecDefaultAction satırındaki deny,status:403 ifadesi 403 Forbidden üretir; birçok cPanel kurulumunda ve bazı ticari kural setlerinde bu değer 406 Not Acceptable olarak ayarlanmıştır.
Bu ayrım tanı koyarken işinize yarar:
- 406 Not Acceptable, normal şartlarda içerik uzlaşması (content negotiation) hatasıdır ve sıradan bir web uygulamasının üretmesi çok nadirdir. Bir POST isteğine 406 dönüyorsa şüphelenilecek ilk şey WAF'tır.
- 403 Forbidden ise çok daha kalabalık bir kümedir. Dosya izinleri, dizin listeleme kapalıyken index dosyasının olmaması, IP engeli veya
.htaccesskuralları da 403 üretir. Bu ihtimalleri elemek için 403 Forbidden hatası çözümü yazısındaki kontrol listesini geçmek mantıklı bir başlangıçtır.
İki durumda da belirleyici olan şudur: hata yalnızca belirli girdilerde ortaya çıkıyor mu? Aynı sayfa boş formla açılıp dolu formla düşüyorsa, karar isteğin içeriğine bakılarak veriliyor demektir. Bu da tanım gereği bir kural motorunun işidir.
Engellemenin ModSecurity'den Geldiğini Nasıl Doğrularsınız?#
Tahmin yürütmek yerine tek bir kanıt satırı arayın. Apache'nin hata günlüğüne düşen ModSecurity kaydı şu biçimdedir:
[Sat Aug 15 11:42:07.113 2026] [security2:error] [pid 24817] [client 203.0.113.44:51122]
ModSecurity: Access denied with code 406 (phase 2).
Matched "Operator `Rx' with parameter `(?i)(?:[\"'`](?:;?\s*?(?:having|select|union)\b...'
against variable `ARGS:content' [file "/etc/apache2/conf.d/modsec_vendor_configs/.../REQUEST-942-APPLICATION-ATTACK-SQLI.conf"]
[line "45"] [id "942100"] [msg "SQL Injection Attack Detected via libinjection"]
[severity "CRITICAL"] [hostname "ornek.com"] [uri "/wp-admin/post.php"] [unique_id "aZ8vTxK9..."]
Bu tek satırda ihtiyacınız olan her şey var: engelleme kodu (406), tetiklenen kural (id "942100"), hangi değişkenin eşleştiği (ARGS:content), hangi URL (uri) ve günlükler arası eşleştirme için unique_id.
Günlüğün yeri ortamınıza göre değişir:
| Ortam | Hata günlüğü | Denetim günlüğü |
|---|---|---|
| cPanel / EasyApache 4 | /var/log/apache2/error_log | /var/log/apache2/modsec_audit.log |
| Debian / Ubuntu (Apache) | /var/log/apache2/error.log | /var/log/apache2/modsec_audit.log |
| AlmaLinux / RHEL (Apache) | /var/log/httpd/error_log | /var/log/httpd/modsec_audit.log |
| Nginx + ModSecurity v3 | /var/log/nginx/error.log | SecAuditLog ile tanımlanan yol |
| Plesk | /var/log/nginx/error.log | /var/www/vhosts/system/<domain>/logs/error_log |
Kök erişiminiz varsa son kayıtları şöyle süzebilirsiniz:
sudo grep "ModSecurity: Access denied" /var/log/apache2/error_log | tail -20
Belirli bir alan adı ve yol için:
sudo grep "ornek.com" /var/log/apache2/error_log \
| grep "wp-admin/post.php" \
| grep -o '\[id "[0-9]*"\]' | sort | uniq -c | sort -rn
Bu komut, o yolda en çok hangi kuralın tetiklendiğini sayarak listeler; muafiyeti hangi ID'ye yazacağınızı doğrudan gösterir. Kök erişiminiz yoksa cPanel'de Ölçümler → Hata Günlükleri ekranı ve cPanel hata kayıtları yazısında anlatılan raw log erişimi aynı satırlara ulaşmanın kullanıcı seviyesindeki yoludur.
Kanıtlı Bir Test İsteği Üretmek#
Hangi girdinin tetiklediğinden emin değilseniz kontrollü bir istek atın ve yanıt kodunu ölçün:
curl -s -o /dev/null -w "%{http_code}\n" \
"https://ornek.com/?test=UNION+SELECT+1,2,3"
Normal bir sayfa isteği 200 dönerken bu isteğin 403/406 dönmesi, sunucuda aktif bir kural motoru olduğunu kanıtlar. Testi kendi alan adınızda ve gerçek bir zafiyet aramadan, sadece "WAF ayakta mı" sorusunu yanıtlamak için kullanın.
SecFilterEngine Off Neden Çalışmaz ve Neden Tehlikelidir?#
SecFilterEngine Off satırı ModSecurity 1.x sözdizimidir. O sürüm yaklaşık yirmi yıl önce bırakıldı; bugün hiçbir sunucuda çalışmaz. Apache tanımadığı bir direktifle karşılaştığında yapılandırmayı reddeder ve .htaccess içindeyse o dizindeki tüm istekler 500 Internal Server Error döner:
Invalid command 'SecFilterEngine', perhaps misspelled or defined by a module not included in the server configuration
Yani forma ulaşamama sorununu, sitenin tamamına ulaşamama sorununa çevirirsiniz. Site birden komple çöktüyse ve az önce bu satırı eklediyseniz, çözüm satırı geri almaktır; 500 Internal Server Error çözümü yazısındaki adımlar bu tabloyu doğrulamanıza yardımcı olur.
Günümüzdeki doğru direktif SecRuleEngine Off'tur. Ancak bu da .htaccess içinde çoğu paylaşımlı sunucuda yasaktır; barındırma sağlayıcısı ModSecurity direktiflerine .htaccess bağlamında izin vermediğinde Apache "not allowed here" der ve yine 500 alırsınız.
Diyelim ki sunucu izin veriyor ve satır çalıştı. Bu sefer de asıl sorun başlar: o dizindeki bütün kurallar kapanır. SQL enjeksiyonu, uzaktan komut çalıştırma, dosya dahil etme ve XSS saldırıları için yazılmış kuralların tamamı devre dışı kalır. Tek bir eklentideki güncellenmemiş bir zafiyet, artık hiçbir katmana takılmadan sömürülebilir hale gelir. Paylaşımlı barındırmada bunun ikinci bir bedeli daha vardır: hesabınız üzerinden kötü amaçlı trafik çıkarsa sağlayıcı hesabı askıya alır.
Kısacası WAF'ı topluca kapatmak bir çözüm değil, sorunun yerini değiştirmektir. WAF'ın ne yaptığına dair genel çerçeveyi web uygulama güvenlik duvarı yazısında bulabilirsiniz; burada odak, tek bir kuralı hedef alarak çözmek.
Kural ID'leri Ne Anlatıyor?#
Engellemenin nedenini anlamak için ID'nin hangi aileye ait olduğunu bilmek yeterlidir. OWASP Core Rule Set (CRS) numaraları konuya göre gruplar:
| ID aralığı | Konu | Tipik yanlış alarm kaynağı |
|---|---|---|
911xxx | İzin verilmeyen HTTP yöntemi | WebDAV, özel API istemcileri |
913xxx | Tarayıcı/scanner imzası | Eski User-Agent gönderen istemciler |
920xxx | Protokol uyumu | Eksik Content-Type, tuhaf başlıklar |
930xxx | Yerel dosya dahil etme (LFI) | İçinde ../ geçen yol parametreleri |
931xxx | Uzak dosya dahil etme (RFI) | Parametrede tam URL taşıyan formlar |
932xxx | Uzaktan komut çalıştırma | Metinde cat, curl, ; gibi ifadeler |
933xxx | PHP enjeksiyonu | Kod örneği içeren blog yazıları |
941xxx | XSS | Sayfa oluşturucular, HTML içeren içerik |
942xxx | SQL enjeksiyonu | Kesme işareti, parantez, UNION kelimesi |
949110 | Gelen anomali eşiği aşıldı | Asıl engelleme burada olur |
200xxx | ModSecurity'nin kendi gövde kuralları | Büyük POST istekleri, dosya yükleme |
Son iki satır rehberlerde nadiren açıklanır ama pratikte en çok kafa karıştıran ikisidir.
949110 bir saldırı kuralı değildir. CRS varsayılan olarak "anomali puanlama" modunda çalışır: her eşleşen kural isteğe puan ekler, toplam puan eşiği (varsayılan 5) geçtiğinde 949110 devreye girip isteği düşürür. Bu yüzden hata günlüğünde bazen id "949110" görürsünüz. Muafiyeti bu ID'ye yazmayın — o zaman eşik kuralını iptal etmiş, yani WAF'ı fiilen kapatmış olursunuz. Puanı ekleyen asıl kuralları bulmanız gerekir; onlar aynı unique_id altındaki denetim günlüğünde listelenir:
sudo grep -A 60 "aZ8vTxK9" /var/log/apache2/modsec_audit.log | grep -o '\[id "[0-9]*"\]'
200002 ve komşuları da saldırı kuralı değildir. Bunlar ModSecurity'nin istek gövdesini ayrıştırırken uyguladığı limitlerdir. Uzun bir yazıyı kaydederken veya büyük bir CSV yüklerken aldığınız 403/406'nın sebebi çoğu zaman şu iki ayardır:
SecRequestBodyNoFilesLimit 131072
SecRequestBodyLimit 13107200
İlki, dosya içermeyen POST gövdesi için sınırdır ve varsayılan değeri 128 KB'dir — uzun bir blog yazısı bunu rahatlıkla aşar. Bu durumda çözüm kural muafiyeti değil, limiti yükseltmektir.
Sadece O Kuralı Muaf Tutmanın Doğru Sözdizimi#
Kuralı bulduktan sonra elinizde dört seçenek var; listede aşağı indikçe daha dar, dolayısıyla daha güvenli olurlar.
1. Kuralı yalnızca tek bir parametre için devre dışı bırakmak (en dar, tercih edilen):
SecRuleUpdateTargetById 942100 "!ARGS:content"
SecRuleUpdateTargetById 941100 "!ARGS:post_content"
Bu, 942100 kuralının content adlı parametreye bakmamasını söyler. Kural diğer tüm alanlarda çalışmaya devam eder.
2. Kuralı yalnızca belirli bir yol için kapatmak:
<LocationMatch "^/wp-admin/post\.php$">
SecRuleRemoveById 941100 942100
</LocationMatch>
3. Koşullu devre dışı bırakma (ctl eylemiyle):
SecRule REQUEST_URI "@beginsWith /wp-admin/" \
"id:1000100,phase:1,pass,nolog,ctl:ruleRemoveById=942100"
Kendi yazdığınız kurallara verdiğiniz ID'nin, sağlayıcının kural setleriyle çakışmaması gerekir; 1000000 üzeri bir aralık pratikte güvenlidir.
4. Kuralı tüm sunucuda kapatmak (son çare):
SecRuleRemoveById 942100
Bu satır kuralı her site, her yol için iptal eder. Yalnızca kuralın gerçekten kullanılamaz durumda olduğu ve daha dar bir kapsam tanımlanamadığı hallerde kullanın.
Muafiyet Nereye Yazılır?#
Sıralama kritiktir: SecRuleRemoveById ve SecRuleUpdateTargetById, hedefledikleri kural yüklendikten sonra okunmalıdır. Aksi hâlde sessizce hiçbir şey yapmazlar — muafiyeti eklediğiniz hâlde engellemenin sürmesinin en yaygın sebebi budur.
| Ortam | Dosya |
|---|---|
| OWASP CRS (kendi sunucunuz) | RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf |
| cPanel / EasyApache 4 | /etc/apache2/conf.d/modsec/modsec2.user.conf |
| cPanel, tek alan adı için | /etc/apache2/conf.d/userdata/std/2_4/<kullanici>/<domain>/modsec.conf |
| Nginx + ModSecurity v3 | modsecurity_rules direktifi veya kural dosyasının sonu |
cPanel'de alan adı bazlı dosyayı oluşturduktan sonra vhost içeriklerini yeniden üretmek gerekir:
sudo /scripts/ensure_vhost_includes --user=kullanici
sudo apachectl configtest
sudo systemctl restart httpd
Nginx tarafında kapsamı bir location bloğuna daraltabilirsiniz:
location /wp-admin/ {
modsecurity_rules '
SecRuleRemoveById 942100
';
}
Teşhis Ederken DetectionOnly Modunu Kullanın#
Birden fazla kuralın birlikte tetiklendiğinden şüpheleniyorsanız, hepsini tek seferde toplamanın yolu motoru geçici olarak yalnızca gözlem moduna almaktır:
SecRuleEngine DetectionOnly
Bu modda ModSecurity kuralları çalıştırır ve günlüğe yazar ama hiçbir isteği engellemez. Sorunlu işlemi bir kez tekrarlayıp günlükten tüm ID'leri toplayın, muafiyetleri yazın ve modu hemen On konumuna geri alın. Bu ayarı "geçici" diye açıp unutmak, SecRuleEngine Off yazmakla neredeyse aynı riski taşır — tek farkı olayların kayda geçmesidir.
Bir ayrımı da atlamayın: her engelleme yanlış alarm değildir. Günlükteki eşleşen veri gerçekten ' OR 1=1-- gibi bir yük ise ve istek tanımadığınız bir IP'den geliyorsa, WAF tam olarak işini yapmıştır. Muafiyet yazmadan önce eşleşen içeriğin sizin ürettiğiniz meşru bir veri olduğundan emin olun. Enjeksiyon yüklerinin nasıl göründüğünü SQL injection nedir yazısında görebilirsiniz.
Paylaşımlı Hostingde Sağlayıcıdan Ne İstemelisiniz?#
Kök erişiminiz yoksa yukarıdaki dosyaların hiçbirine yazamazsınız. Bu durumda destek talebinin kalitesi çözüm süresini belirler. "Sitem 406 veriyor, ModSecurity'yi kapatır mısınız" demek, çoğu zaman ya reddedilir ya da tüm hesapta WAF'ın kapatılmasıyla sonuçlanır — ikisini de istemiyorsunuz.
Bunun yerine şu dört bilgiyi verin ve şu üç şeyi isteyin:
- Tam zaman damgası (saat dilimiyle birlikte) ve isteği yaptığınız IP adresi.
- Tam URL ve HTTP yöntemi — örneğin
POST /wp-admin/post.php. - Elinizde varsa
unique_iddeğeri; yoksa tarayıcıda gördüğünüz hata sayfasının ekran görüntüsü. - Sorunun tekrarlanabilir olduğunu gösteren tarif: "içinde tırnak işareti geçen bir yazı kaydedince oluyor, geçmeyince olmuyor".
İstekleriniz:
- "Bu isteği hangi ModSecurity kural ID'sinin engellediğini paylaşır mısınız?"
- "Yalnızca
<alan adı>altındaki<yol>için, yalnızca o kural ID'sini muaf tutabilir misiniz? ModSecurity'nin tamamen kapatılmasını istemiyorum." - Gövde limiti şüphesi varsa: "
SecRequestBodyNoFilesLimitdeğeri nedir, bu hesap için yükseltilebilir mi?"
Sağlayıcının hangi kural setini kullandığını da sormakta fayda var. OWASP CRS, ticari bir sağlayıcının kural seti ve Imunify360 gibi bütünleşik çözümler muafiyeti farklı arayüzlerden yönetir; ID aralıkları ve devre dışı bırakma yöntemi de buna göre değişir. cPanel kullanan sunucularda yöneticinin WHM → Security Center → ModSecurity Tools → Hits List ekranından ilgili kaydı bulup tek tıkla kuralı devre dışı bırakabildiğini bilmek, talebinizin ne kadar kolay olduğunu göstermek açısından işe yarar.
Son olarak, .htaccess ile deneme yapacaksanız yazdığınız her ModSecurity direktifini modül yokluğuna karşı koruyun; bu, modül hiç yüklü değilse sitenin 500 vermesini engeller:
<IfModule security2_module>
SecRuleRemoveById 942100
</IfModule>
Bu koruma yalnızca "modül yok" durumunu kapsar; direktifin .htaccess bağlamında yasaklandığı sunucularda yine 500 alırsınız. .htaccess dosyasını düzenlerken izlenecek genel yöntem için .htaccess dosyası oluşturma yazısındaki uyarılar geçerlidir: değişiklikten önce yedek alın, tek satır ekleyip test edin.
Sıkça Sorulan Sorular#
ModSecurity'yi tamamen kapatmak güvenli mi?#
Değil. Motoru kapattığınızda SQL enjeksiyonu, uzaktan komut çalıştırma, dosya dahil etme ve XSS için yazılmış kuralların tamamı devre dışı kalır. Güncellenmemiş tek bir eklenti zafiyeti bile artık hiçbir katmana takılmadan sömürülebilir hale gelir. Paylaşımlı barındırmada ayrıca hesabınızdan kötü amaçlı trafik çıkması hâlinde askıya alınma riski doğar. Doğru yaklaşım tek kuralı, tek yol için muaf tutmaktır.
403 ile 406 arasındaki fark neden önemli?#
İkisi de ModSecurity engellemesi olabilir; hangi kodun döndüğü yalnızca sunucudaki SecDefaultAction ayarına bağlıdır. Fark tanı hızındadır: 406 sıradan uygulamalarda neredeyse hiç görülmeyen bir koddur, dolayısıyla doğrudan WAF'a işaret eder. 403 ise dosya izinleri, dizin ayarları ve IP engellerinden de gelebileceği için önce bu ihtimallerin elenmesi gerekir.
.htaccess ile ModSecurity kuralını kapatabilir miyim?#
Sunucu izin veriyorsa SecRuleRemoveById gibi direktifler .htaccess içinde çalışabilir. Ancak paylaşımlı barındırma sağlayıcılarının çoğu ModSecurity direktiflerine bu bağlamda izin vermez ve deneme 500 Internal Server Error ile sonuçlanır. SecFilterEngine Off ise hiçbir modern sunucuda çalışmaz; eski bir sürümün sözdizimidir ve tek etkisi dizini tamamen erişilemez hale getirmektir.
Kural muafiyeti ekledim ama hâlâ engelleniyorum, neden?#
En yaygın sebep sıralamadır: SecRuleRemoveById, hedeflediği kural yüklendikten sonra okunmalıdır. Muafiyeti kural setinden önce yüklenen bir dosyaya yazdıysanız satır sessizce etkisiz kalır. İkinci sebep yanlış ID'dir — anomali puanlama modunda günlükte 949110 görünse bile muafiyet puanı ekleyen asıl kurallara yazılmalıdır. Üçüncüsü Apache'nin yeniden başlatılmamış olmasıdır.
WordPress'te en sık hangi kurallar yanlış alarm veriyor?#
Pratikte üç grup öne çıkar: yazı içeriğinde tırnak, parantez veya UNION gibi kelimeler geçtiğinde SQL enjeksiyonu ailesi (942xxx), sayfa oluşturucuların gönderdiği HTML nedeniyle XSS ailesi (941xxx) ve kod örneği paylaşan yazılarda PHP/komut enjeksiyonu aileleri (932xxx, 933xxx). Bunların hemen tamamı /wp-admin/post.php ve /wp-admin/admin-ajax.php yollarında yoğunlaşır, bu yüzden muafiyeti bu iki yola daraltmak genellikle yeterlidir.
Dosya yüklerken aldığım 403 hatası da ModSecurity'den mi kaynaklanıyor olabilir?#
Evet, ama muhtemelen bir saldırı kuralından değil. 200xxx aralığındaki kurallar ModSecurity'nin istek gövdesi limitleridir. Dosya içermeyen POST gövdesi için varsayılan sınır olan SecRequestBodyNoFilesLimit 128 KB'dir ve uzun içerikler bunu aşar. Çözüm, kuralı muaf tutmak değil ilgili limiti yükseltmektir; kök erişiminiz yoksa bu değeri sağlayıcınızdan talep etmeniz gerekir.