SMTP Smuggling ve CRLF Enjeksiyonu: Postfix'i Byte Seviyesinde Sağlamlaştırmak
SMTP smuggling bir ayrıştırma hatası değil, kimlik doğrulama katmanının komple atlanmasıdır: kaçırılan ikinci mesaj dış rölenin SPF pass sonucunu miras alır ve p=reject bir domain adına kutuya düşer. DMARC panosunda değil, Postfix'in bare-newline reddinde çözülür. Postfix 3.6.4 üzerinde doğrulanmış sertleştirme reçetesi.
EvilMail Team27 Temmuz 202611 dk okuma
Aynı byte akışı iki farklı MTA'ya verildiğinde iki farklı "mesaj sonu" olarak yorumlanabiliyorsa, elinizde bir ayrıştırma tuhaflığı değil, kimlik doğrulama katmanının komple atlandığı bir açık var. Dış röle bir DATA gövdesini tek bir mesaj sanır; iç sunucu aynı gövdenin ortasında ikinci bir zarf görür. O ikinci, kaçak zarf güvenilir rölenin TCP oturumundan çıktığı için dış rölenin SPF pass sonucunu miras alır ve p=reject ilan etmiş bir domain adına sahte From ile kutuya düşer. DMARC panonuzda her şey yemyeşildir, kullanıcı yine de bankasının CEO'sundan gelmiş gibi görünen bir mesaj alır.
Özü tek cümle: SMTP smuggling ve CRLF enjeksiyonu bir spam ya da teslimat sorunu değil, byte seviyesinde bir protokol sınırı karışıklığı (boundary confusion) sorunudur. Çözüm de DMARC kaydında değil; Postfix'in <CR><LF>.<CR><LF> dizisini katı ayrıştırmasında, pipelining reddinde ve submission katmanındaki From=login hizalamasında yatar. Aşağıdaki her ayar bu sunucudaki Postfix 3.6.4 üzerinde postconf ile doğrulandı.
SMTP Smuggling ve CRLF Enjeksiyonuna Karşı Postfix Sertleştirme — EvilMail Blog
Aynı akış, iki farklı "mesaj sonu"
RFC 5321 mesaj sonunu tek bir dizi olarak tanımlar: <CR><LF>.<CR><LF>. Yani "yeni satır, tek başına nokta, yeni satır" — ve o yeni satırlar tam olarak carriage return + line feed olmalı. Sorun şu ki dünyadaki MTA'ların ciddi bir kısmı gevşek davranır. Unix geleneğinden gelen kod tabanları satır sonunu çıplak <LF> olarak da kabul eder, dolayısıyla <LF>.<LF> veya <CR>.<CR> gibi varyantları da geçerli bir "veri sonu" sayar.
Bu asimetri zararsız görünür, ta ki bir uçta katı, diğer uçta gevşek iki sunucuyu arka arkaya dizene kadar. Giden röle (örneğin büyük bir sağlayıcının outbound MTA'sı) mesajı katı yorumlar ve gövdenin içindeki <LF>.<LF> dizisini "mesaj sonu" saymaz — olduğu gibi, ham byte olarak iletir. Gelen MX ise gevşek yorumlar, aynı diziyi mesaj sonu görür ve ardından gelen satırları yeni bir SMTP komut dizisi olarak işlemeye başlar. Tek bir DATA gövdesinin içine ikinci bir tam SMTP oturumu sığdırılmış olur.
Bunu 2023'te Timo Longin (SEC Consult) sistematikleştirdi ve "SMTP Smuggling" adını verdi. GMX, Ionos ve Cisco Secure Email Gateway dahil büyük sağlayıcılar etkilendi. Kritik nokta teknik zarafeti değil, sonucu: kaçırılan mesaj güvenilir rölenin IP'sinden çıktığı için SPF pass alır, ve DKIM devrede değilse DMARC yalnızca SPF hizalamasına bakıp geçirir.
Saldırının anatomisi: bare LF ile ikinci mesajı içeri sokmak
Byte seviyesine inelim. Saldırgan meşru bir mesajın DATA gövdesinin içine, gövdeyi kapatıyormuş gibi görünen ama katı ayrıştırıcının kapanış saymadığı bir dizi koyar — çıplak LF ile biten bir nokta satırı, yani ...\r\n.\n. Sonrasına da kendi ikinci zarfını yazar: yeni bir MAIL FROM, RCPT TO, DATA ve spoof edilmiş From başlığı.
Katı giden sunucu \n.\n dizisini kapanış saymadığı için tüm bloğu — sahte zarf dahil — tek bir mesaj gövdesi olarak iletir. Gevşek gelen sunucu ise \n.\n'yi görünce birinci mesajı kuyruğa alır ve komut moduna döner; hemen ardından okuduğu MAIL FROM:<[email protected]> satırını gerçek bir zarf komutu sanar. Sonuç: iki teslimat, ikisi de aynı, güvenilir TCP bağlantısı üzerinden. İkinci mesajın spoof kalkanını deldiği yer tam burası — hizalama katmanı rölenin IP'sine bakar, "bu IP banka.com için SPF pass" der ve geçirir.
Hangi tarafın hangi varyantı kabul ettiği saldırının çekirdeğidir:
<CR><LF>.<CR><LF> — standart, herkesin kapanış saydığı dizi.
<LF>.<LF> — çıplak LF; Unix kökenli gevşek MTA'lar kapanış sayar, katı olanlar saymaz.
<CR>.<CR> — çıplak CR; bazı gateway'lerin zayıf noktası.
<CR><LF>.<CR> — karışık; asimetrinin en sık sömürülen biçimlerinden.
Postfix'i yamalamak: bare newline ve pipelining reddi
Savunmanın omurgası smtpd_forbid_bare_newline. Bu parametre Aralık 2023 sürümleriyle (3.8.4 / 3.7.9 / 3.6.13 / 3.5.23) backport edildi; dağıtımlar çoğu zaman üst sürüm etiketini (örneğin 3.6.4) sabit tutup yamayı paketin içine sokar. Kritik detay: backport sürümlerinde varsayılan `no` — yani elle açmazsanız korumanız yok. Bu sunucuda postconf smtpd_forbid_bare_newline çıktısı no döndü; kapalı. Postfix 3.9'da varsayılan açık geldi ve ayrıca normalize seçeneği eklendi (reddetmek yerine çıplak LF'yi CRLF'ye düzeltir).
`smtpd_forbid_bare_newline = yes` — DATA akışında çıplak LF içeren oturumu 550 ile reddeder. Reddetme kodu smtpd_forbid_bare_newline_reject_code ile ayarlanır, varsayılanı zaten 550 (bu sunucuda doğrulandı).
`_exclusions = $mynetworks` — iç legacy istemcileri (eski cron script'leri, bazı monitoring ajanları çıplak LF üretir) kırmamak için muaf tutar. Varsayılan zaten $mynetworks; dışarıdan gelen trafik istisnasız denetlenir.
`reject_unauth_pipelining` — smuggling saldırıları neredeyse her zaman komutları sunucunun PIPELINING ilan etmesini beklemeden art arda gönderir. 3.6.4'te henüz smtpd_forbid_unauth_pipelining parametresi yok (bu sunucuda unknown parameter uyarısı verdi); onun yerine smtpd_data_restrictions içinde reject_unauth_pipelining kullanılır. Postfix 3.9+ sistemlerde smtpd_forbid_unauth_pipelining = yes zaten varsayılandır ve DATA'dan önce de devrededir.
`strict_rfc821_envelopes = yes` — açısız/köşeli parantezsiz zarf adreslerini reddeder (MAIL FROM:foo@bar yerine MAIL FROM:<foo@bar> zorunlu). Gevşek zarf ayrıştırması smuggling varyantlarına ek yüzey açar. Varsayılanı no.
Uygulama ve doğrulama:
bash
postconf -e 'smtpd_forbid_bare_newline = yes'
postfix reload
postconf smtpd_forbid_bare_newline # canlı değeri oku: yes dönmeli
CRLF enjeksiyonu: web formundan mailer'a sızan satır
Konu değişir ama kök aynıdır: kontrol edilmeyen \r\n. Klasik senaryo bir iletişim formudur — kullanıcı Subject ya da From alanına Ödeme\r\nBcc: [email protected] yazar, uygulama bu değeri başlığa doğrudan koyar, ve tek bir alan sessizce ek bir Bcc başlığına dönüşür. PHP'nin mail() fonksiyonu bu hatanın anavatanıdır; nodemailer daha güvenli ama sanitize edilmemiş replyTo/subject değişkenleriyle aynı kapıyı açabilirsiniz.
Savunma tek satırlık bir kuralla özetlenir: başlığa giden kullanıcı girdisinde `\r` veya `\n` görürsen kabul et-ya-reddet — asla escape edip devam etme. evilmail.pro'nun src/lib/mailer.ts mailer'ında başlık değişkenleri şablona yerleştirilmeden önce şu kontrolden geçmeli:
javascript
function assertHeaderSafe(value, field) {
if (/[\r\n]/.test(value)) {
throw new Error(`Basliga satir sonu enjeksiyonu reddedildi: ${field}`);
}
return value;
}
// subject, replyTo, from display-name gibi TÜM baslik alanlarina uygula
Uygulama katmanı ilk savunma; Postfix cleanup daemon'ı ikincisidir. Gövdeyle başlık arasındaki sınırı normalize eder ve kaçak başlıkları header_checks ile eleyebilirsiniz. PCRE tabanlı iki kural, ikinci bir From ya da enjekte edilmiş Received satırını yakalar:
bash
# /etc/postfix/header_checks (pcre:)
/^From:.*\r?\nFrom:/ REJECT Coklu From basligi reddedildi
/^Received:.*\(unknown local injection\)/ REJECT Kacak Received basligi
main.cf'e header_checks = pcre:/etc/postfix/header_checks ekleyip postfix reload deyin. Bu, uygulama katmanı bir gün delinirse arkadaki emniyet ağıdır.
Smuggling'i dış MX'te bare-newline reddiyle kapatırsınız; ama kendi kimlik doğrulamalı kullanıcınızın spoof yapmasını (bir hesap ele geçirilip başka kullanıcının adına mail atılması) ayrı bir cephe olan 587/submission'da kapatırsınız. Anahtar smtpd_sender_login_maps: her SASL kullanıcısını kullanmasına izin verilen zarf/başlık adreslerine eşler.
reject_authenticated_sender_login_mismatch, kimlik doğrulamış kullanıcının sender_login_maps'te kendisine atanmadığı bir MAIL FROM/From vermesini engeller. [email protected] olarak giriş yapan biri [email protected] adına gönderemez. Anonim yolları da kapatmak isterseniz reject_sender_login_mismatch bilinen tüm sender'lar için login zorunlu kılar. Bu, smuggling ile hiç ilgisi olmayan ama aynı "sahte From" sonucunu üreten iç tehdidi kapatır.
SPF/DKIM/DMARC neden gerekli ama tek başına yetmez
Hizalama katmanının smuggling'e karşı neden kör olduğunu açıkça söylemek gerek: kaçırılan mesaj güvenilir rölenin IP'sinden çıkar, dolayısıyla SPF pass alır. DKIM imzası yoksa DMARC değerlendirmesi sadece SPF hizalamasına düşer ve mesajı geçirir. "p=reject taktık, güvendeyiz" yanılgısının tam olarak çöktüğü nokta budur.
Doğru kombinasyon üç ayaklı: gönderen domain DKIM ile imzalasın (smuggling saldırganı kaçırdığı gövdenin DKIM imzasını taşıyamaz — gövde ve başlık hash'i uyuşmaz, imza doğrulanmaz), DMARC katı hizalama ilan etsin, ve alıcı MX bare-newline'ı reddetsin. evilmail.pro için DNS:
~all (softfail) yerine -all (hardfail); adkim=s/aspf=s ile alt domain gevşekliğini kapatın. DMARC panelinde yeşil, "spoof edilemez" demek değildir — yalnızca "hizalama kurallarım tutarlı" demektir. Kaçırılan mesaj o kuralları teknik olarak sağladığı için geçer.
Doğrulama: raw socket ile smuggling testi
Test etmeden inanmayın. swaks bare LF gönderemez çünkü kütüphanesi satır sonlarını normalize eder; ham byte üretmek için printf kullanıp doğrudan sokete borulamanız gerekir. Kritik nokta: openssl s_client'e -crlf bayrağını vermeyin — o bayrak \n'leri \r\n'ye çevirir ve tam da test etmek istediğiniz çıplak LF'yi yok eder.
Sertleştirilmiş sunucuda beklenen: oturum 550 ile reddedilir ve maillog'da açık bir iz kalır. Sertleşmemiş sunucuda: iki ayrı teslimat, ikincisi spoof edilmiş From ile. Kontrol komutu:
bash
grep -E 'bare <LF>|reject_unauth_pipelining|forbid_bare_newline' /var/log/mail.log | tail
# beklenen: postfix/smtpd[...]: NOQUEUE: reject: ... bare <LF> received ... 550
Sertleştirme kontrol listesi
smtpd_forbid_bare_newline = yes — backport sürümlerde varsayılan no, elle açın; 3.9+'da varsayılan açık.