Postfix header_checks ve body_checks: Başlık Düzenleme, Dahili IP Gizleme ve İçerik Engelleme
header_checks bir antispam motoru değil, cleanup(8) içinde çalışan satır bazlı, durumsuz bir regexp filtresidir. Giden postada RFC1918 IP'lerini ve iç topolojiyi sızdırmadan mesajı temizlemenin, gelen postada tehlikeli ekleri protokol katmanında kesmenin doğru yolu.
EvilMail Team12 Temmuz 202613 dk okuma
Çoğu yönetici header_checks'i ilk kez spam'i durdurmak için açar, sonra da neden işe yaramadığına şaşırır. Yanlış kata bakıyorlar. header_checks ve body_checks bir antispam motoru değildir; cleanup(8) daemon'ı içinde çalışan, satır bazlı, durumsuz bir regexp filtresidir. İçeriği anlamaz, MIME'i çözmez, iki satır arasında bağlam tutmaz. Bu sınırları kabul ettiğiniz anda bu araç, başka hiçbir katmanın temiz yapamadığı iki işi mükemmel yapar: giden postadan dahili topolojiyi (RFC1918 IP'leri, LAN hostname'leri, iç
Postfix header_checks ve body_checks ile Başlık Düzenleme ve IP Gizleme — EvilMail Blog
Received
satırları) kazımak ve gelen postada tehlikeli ekleri, içerik tarayıcı daha devreye girmeden protokol katmanında kesmek.
Önce hangi check'in cleanup içinde hangi satırı gördüğünü netleştirelim; IP gizleme ve içerik engelleme bunun üzerine oturuyor.
header_checks aslında ne yapar (ve ne yapmaz)
Bir mesaj smtpd (ağdan) ya da pickup (yerel) üzerinden geldiğinde, kuyruğa yazılmadan önce cleanup(8) daemon'ından geçer. cleanup mesajı satır satır işler ve iki ayrı map'e danışır:
header_checks yalnızca başlık satırlarına bakar. Postfix katlanmış (folded) başlıkları önce mantıksal tek satıra birleştirir, regexp'i o birleşik satıra uygular. Yani 200 karakterlik bir Subject: üç fiziksel satıra bölünmüş olsa bile kuralınız tek satır görür.
body_checks gövdenin her fiziksel satırına ayrı ayrı bakar. MIME sınırlarını, Content-Transfer-Encoding'i, base64 kodlamasını görmez ve decode etmez. Base64 bir ekin ham satırlarını, göründükleri gibi, karakter karakter görür.
Durumsuz olması burada kritik. body_checks "şu satır bir eke ait" diye bilmez; her satır bağımsız değerlendirilir. Bu yüzden "şu attachment'ı engelle" mantığını base64 blob'unun ortasına değil, Content-Type / Content-Disposition başlığındaki filename= satırına kurarsınız — çünkü orada niyet düz metin olarak durur.
Bir kural eşleştiğinde döndürebileceğiniz eylemler:
WARN — sadece logla, hiçbir şey yapma (kural test etmek için ideal)
HOLD — mesajı hold kuyruğuna al, manuel inceleme bekle
REDIRECT / FILTER / DUNNO — sırasıyla başka alıcıya yönlendir, içerik filtresine ver, hiçbir kural eşleşmemiş gibi devam et
Map tipi olarak regexp: yerine pcre: kullanın; PCRE geri referans ($1, $2), non-greedy niceleyiciler ve daha zengin karakter sınıfları verir. Debian/Ubuntu'da pcre map tipi için postfix-pcre paketi gerekebilir; desteklenen tipleri postconf -m | grep pcre ile doğrulayın.
Konumlandırma: gelen mi giden mi, hangi cleanup instance?
En sık yapılan mimari hata: main.cf'e global bir header_checks koyup içine dahili IP gizleme kuralları yazmak. main.cf'teki global map her mesaja uygulanır — gelen, giden ve iç relay dahil. RFC1918 içeren Received satırlarını burada silerseniz, size gelen meşru postanın trace zincirini de bozarsınız.
Doğru desen, master.cf'te giden trafik için ayrı bir cleanup instance'ı tanımlamak ve onu yalnızca submission/relay servisine bağlamaktır:
text
# master.cf
submission inet n - n - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o cleanup_service_name=outbound-clean
outbound-clean unix n - n - 0 cleanup
-o header_checks=pcre:/etc/postfix/outbound_header_checks
Böylece 587'den (kimliği doğrulanmış kullanıcılar, yani sizin giden postanız) gelen her mesaj outbound-clean instance'ından geçer ve topoloji kazıma kurallarını yalnızca o görür. Port 25'ten gelen (dünyadan size) posta standart cleanup'ı kullanır ve Received zinciri sağlam kalır. Aktif servisleri şununla doğrulayın:
bash
postconf -M | grep -E 'cleanup|submission'
Ayrımı "kim gönderdi" (kimlik doğrulayan submission kullanıcısı, yani sizin postanız) üzerinden yaparsınız; port 25 trafiğini elle origin'e göre ayıklamaya çalışmaktan çok daha güvenilirdir.
Dahili IP ve topoloji sızıntısını kazımak
Somut problem şu. Kullanıcının MUA'sı ya da bir iç MTA giden mesaja şu satırları ekler:
text
Received: from workstation (unknown [10.0.3.14]) by internalhost.lan
X-Originating-IP: [192.168.1.50]
X-Mailer: Microsoft Outlook 16.0
X-MimeOLE: Produced By Microsoft MimeOLE
Bu satırlar dünyaya iç ağ topolojinizi duyurur. Bazı RBL ve heuristik puanlayıcılar giden mailde RFC1918 adresi görmeyi düşük seviyeli bir spam sinyali sayar; daha kötüsü, iç hostname'leriniz ve MUA sürümleriniz hedefli saldırı için keşif bilgisidir. outbound_header_checks map'i bunları kazır:
text
# /etc/postfix/outbound_header_checks (pcre)
# RFC1918 / CGNAT iceren ic Received satirlarini sil
/^Received: .*\b(10\.\d+\.\d+\.\d+|172\.(1[6-9]|2\d|3[01])\.\d+\.\d+|192\.168\.\d+\.\d+|100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.\d+\.\d+)\b/ IGNORE
# MUA ve topoloji parmak izlerini sil
/^X-Originating-IP:/ IGNORE
/^(X-Mailer|User-Agent|X-MimeOLE):/ IGNORE
# ic hostname'i genel relay adiyla degistir
/^Received: from internalhost\.lan.*/ REPLACE Received: from mail.evilmail.pro (mail.evilmail.pro [203.0.113.10])
Burada IGNORE ile REJECT/DISCARD'ı karıştırmayın. IGNORE yalnızca o başlık satırını sessizce siler, mesaj normal teslim edilir. REJECT mesajın tamamını reddeder. İç başlıkları temizlerken mesajı öldürmek istemezsiniz; IGNORE doğru araçtır.
Kritik uyarı: en dıştaki Received satırına, yani sizin kendi relay'inizin eklediğine dokunmayın. O satır teslim trace'inin ve döngü (loop/hop-count) tespitinin parçasıdır; silerseniz teşhis edilmesi zor teslim sorunları yaratırsınız. Kurallarınızı yalnızca iç ağdan gelen satırları hedefleyecek kadar dar tutun — yukarıdaki RFC1918 anchor'ı tam olarak bunu yapar.
Gelen tarafta içerik ve tehlikeli ek engelleme
Gelen tarafta amaç farklı: mesaj antivirüs/amavis/rspamd'a ulaşmadan önce, ucuz ve deterministik bir ön filtre çalıştırmak. cleanup her satırı sırayla tarar; kötü yazılmış, catastrophic-backtracking'e açık bir regex 100 bin satırlık bir mailde CPU'yu kilitleyebilir. O yüzden kurallar basit ve anchor'lı olmalı.
Tehlikeli ekleri filename= satırından yakalayın (bu bir başlık satırıdır, yani header_checks):
# /etc/postfix/body_checks (pcre)
/^TVqQAAMAAAAEAAAA/ REJECT MZ/PE calistirilabilir tespit edildi
Kesin karar veremediğiniz durumlarda REJECT yerine HOLD kullanın: mesaj hold kuyruğuna alınır, siz inceleyene kadar bekler.
bash
postqueue -p # hold dahil kuyrugu goster
postsuper -H ALL # tum hold mesajlarini serbest birak
postcat -q ABC123DEF # bir mesaji incele
postsuper -d ABC123DEF # sil
Tekrar altını çizelim: body_checks gövdeyi decode etmez. Base64/quoted-printable ham satırları görür. .zip içindeki .exe'yi, şifreli arşivi, polimorfik yükü göremez. Gerçek içerik taraması amavis/ClamAV/rspamd işidir. body_checks yalnızca en aptal, en yüksek hacimli çöpü protokol katmanında keser.
Test etme, debug ve tuzaklar
Hiçbir kuralı canlıya, önce postmap -q ile test etmeden almayın. Tek satır testi:
Katlanmış başlık yanılgısı. Postfix mantıksal başlığı birleştirip gösterir, ama Received doğası gereği çok bileşenlidir (from ... by ... with ... id ... for ...). ^Received: ile başlayıp .* ile devam eden dar bir anchor kullanın; başlığın ortasındaki bir kelimeye güvenip ^ olmadan yakalamaya çalışmayın.
REPLACE ile geçersiz başlık üretmek.REPLACE ile yeni satırı verirken başlık adını ve iki noktayı doğru yazmalısınız (Received: from ...). Adı bozarsanız RFC 5322'ye aykırı, alıcı sunucuların reddedebileceği bir başlık üretirsiniz.
reload unutmak. Map'i değiştirdikten sonra postfix reload gerekir. pcre map'i runtime'da okunur (derlenmiş bir dosya yoktur), ama cleanup süreçleri map yolunu servis başlarken çözer.
Log okuma. İş görüp görmediğini journalctl -u postfix ya da /var/log/mail.log içinde postfix/cleanup[pid]: satırlarında görürsünüz; WARN eyleminin logladığı metin burada çıkar.
En kritik sıralama sorusu: DKIM.** Postfix, `cleanup`'ın header/body check'lerini milter'ları (OpenDKIM dahil) çağırmadan **önce** uygular; yani mesajı önce kendisi yeniden yazar, sonra imzalayıcıya verir. `IGNORE` ile sildiğiniz satırlar imzaya hiç girmez, `REPLACE`/`PREPEND` ettikleriniz de son haliyle imzalanır — her iki durumda imza tutarlıdır. Tehlike ters kurulumda: imzayı bir şekilde rewrite'tan **önce** koyarsanız (yanlış milter zinciri ya da imzayı upstream bir hop'ta atmak), `header_checks` imzalı bir başlığı değiştirir ve imzayı kırar. Kural basit: **imza her zaman rewrite'tan sonra.
Uygulama kontrol listesi
Giden için master.cf'te ayrı bir outbound-clean cleanup instance'ı tanımla; submission'a -o cleanup_service_name= ile bağla.
Topoloji kazıma kurallarını asla global main.cf map'ine koyma — gelen postanın Received zincirini bozar.
İç başlıkları temizlerken IGNORE kullan, REJECT değil; mesajı öldürme.
En dıştaki (kendi relay'inin eklediği) Received satırına dokunma — trace ve döngü (loop) tespiti ona bağlı.
Gelen ek engellemede kuralı filename= satırına kur; base64 blob'una değil. body_checks'in decode etmediğini unutma.
pcre regex'lerini catastrophic backtracking açısından gözden geçir; anchor'lı ve basit tut.
Her kuralı postmap -q ile canlıya almadan test et; şüpheli durumlar için WARN ile başla, sonra REJECT/HOLD'a geç.
HOLD kuyruğunu postqueue -p ile izle; postsuper -H/-d ile yönet.
DKIM imzalamanın header rewrite'tan sonra çalıştığını doğrula; aksi halde imza kırılır.
Bu ayrımı doğru kurduğunuzda header_checks/body_checks size iki net kazanç verir: giden relay'iniz iç IP ve hostname sızdırmaz — evilmail.pro tarafında bu doğrudan itibar ve teslim edilebilirlik demek — ve gelen tarafta en bariz çöp, pahalı içerik motorlarınız daha uyanmadan düşer.