Kaybolan noktanın tuhaf vakası
(tjaart.substack.com)- Belge şablonlarıyla e-posta, SMS ve PDF üreten bir sistemde, yalnızca belirli bir müşteriye gönderilen e-postanın gövdesinde noktaların kaybolduğu bir sorun ortaya çıktı
- Şablonun kaynağında, önizlemede ve PDF’de noktalar vardı; ancak müşteri bazlı placeholder değerleri gerçek verilerle değiştirildikten sonra yalnızca belirli satır uzunluklarında sorun yeniden üretilebildi
- Özel bir SMTP istemcisi satır uzunluğunu sınırlandırırken noktayı yeni satırın ilk karakteri hâline getirdi ve SMTP’nin dot transparency kuralı nedeniyle sunucu bu noktayı kaldırdı
- Düzeltme, noktayla başlayıp ardından başka karakterlerin geldiği satırların başına istemcinin bir nokta daha eklemesi şeklinde yapıldı
- Aynı SMTP istemci kodunu paylaşan başka bir ekip yama uygulamayınca, bazı müşterilere aylık sigorta priminin $27.00 yerine $2700 gibi göründüğü e-postalar gönderildi ve hata hemen düzeltildi
Merkezi şablon sisteminde başlayan sorun
- Yaklaşık 7 yıl önce bir müşteri, farklı Microsoft Word şablonlarına dağılmış belgeleri tek bir sistemde birleştirmek istiyordu
- Eski yöntemde çalışanlar belgelerdeki placeholder alanlarını ad, soyad gibi bilgilerle elle değiştiriyordu; eski şartlar, önceki şirket logosu ve yanlış fontlar içeren şablonlar da dolaşımda kaldığı için yönetim zorlaşıyordu
- Yeni sistem, belge şablonlarını merkezi olarak yönetiyor ve bunlardan PDF belgeleri, SMS mesajları ve e-posta gövdeleri üretiyordu
- Yeni müşteri karşılama mesajları da iletim yöntemine göre farklı şablonlara sahip olabiliyordu
- E-posta sürümünde HTML tabloları ve temel stiller kullanılabiliyordu
- Postayla gönderilecek sürümde infografikler bulunabiliyordu
- SMS sürümünde yalnızca kısa bir karşılama mesajı yer alabiliyordu
Yalnızca belirli müşterilerde kaybolan nokta
- Sistem aylarca, hatta bir yıldan uzun süre çalıştıktan sonra bir yönetici, belirli bir müşteriye gönderilen e-postanın gövdesinde bir noktanın eksik olduğunu bildirdi
- Aynı e-posta başka müşterilere gönderildiğinde nokta kaybolmadığı için bunun basit bir şablon hatası olduğu düşünülmedi
- Şablonun kaynak kodunda ilgili nokta gerçekten vardı
- Prodüksiyon şablonu yerel ortama kopyalanıp e-posta gövdesi önizleme olarak oluşturulduğunda da nokta görünüyordu; aynı şablonla üretilen çıktı sürümünde de doğru şekilde gösteriliyordu
- Yerel ortamda e-postalar belirli bir porttaki localhost’a gönderiliyor, ardından SMTP4dev gibi sahte bir SMTP sunucusu ve Outlook ile kontrol ediliyordu
- İlk başta yerelde gönderilen e-posta Outlook’ta incelendiğinde de nokta düzgün görünüyordu
Müşteri verisinin oluşturduğu yeniden üretim koşulu
- Şablonlar, e-posta, PDF ve SMS üretirken müşterinin ad ve soyadı gibi placeholder değerlerini gerçek verilerle değiştiriyordu
- Aynı şablon kullanılsa bile, her müşteriye gönderilen e-posta gövdesinin uzunluğu farklı olabiliyordu
- Sorunlu müşteri için gerçekten kullanılan placeholder değerleri bulunup yerel ortamda aynı değerlerle e-posta tekrar gönderildiğinde, Outlook’ta noktanın kaybolduğu görüldü
- Sorun, ilgili müşteri e-postasının belirli gövde uzunluğu ve içeriğine bağlıydı
- İlk aşamada şablondaki nokta karakterinin encode edilip edilmediği ya da aslında başka bir karakter olup olmadığı kontrol edildi, ancak neden bu değildi
- Şablon içindeki noktanın yeri bir karakter kaydırılınca, Outlook’ta nokta yeniden görünmeye başladı ve satır konumu yeniden üretim için ipucu oldu
SMTP satır uzunluğu ve dot transparency
- Hata ayıklama sonucunda, e-postayı veritabanına kaydeden kodun placeholder’ları müşteri bilgileriyle değiştirmek dışında şablonu değiştirmediği görüldü
- Ardından inceleme, düzenli olarak gönderim hedeflerini alıp e-postaları yollayan cron işi koduna daraltıldı
- Cron işinin çağırdığı kodun bir bölümü önceki bir projeden alınmıştı ve içinde özel bir SMTP istemcisi implementasyonu bulunuyordu
- Bu kodda, e-posta gövdesindeki her satırın belirli bir karakter sayısını aşmaması için satırı bölen bir fonksiyon vardı
- Bu davranış SMTP spesifikasyonundaki satır uzunluğu sınırıyla ilgiliydi
- Metin satırının toplam azami uzunluğu
<CRLF>dahil 1000 octets idi - Şeffaflık için kopyalanan baştaki noktalar bu hesaba dahil edilmiyordu
- SMTP Service Extensions ile bu değer artırılabiliyordu
- Metin satırının toplam azami uzunluğu
- Sorunlu e-posta gövdesinde, satır uzunluğu sınırı nedeniyle nokta bir sonraki satırın ilk karakterine taşındı
- SMTP spesifikasyonunda mail data bitiş göstergesi, yalnızca bir noktadan oluşan bir satır olarak ele alınıyordu
- SMTP’nin dot transparency kuralı, noktayla başlayan gövde satırlarını ayrıca işliyordu
- SMTP istemcisi, posta metni satırını göndermeden önce ilk karakterin nokta olup olmadığını kontrol etmeli ve öyleyse satırın başına bir nokta daha eklemeliydi
- SMTP sunucusu, satır yalnızca tek bir noktadan oluşuyorsa bunu mail verisinin sonu olarak kabul eder
- İlk karakter nokta olup aynı satırda başka karakterler de varsa, sunucu ilk karakteri siler
- Özel SMTP istemcisi, satır noktayla başladığında bir nokta daha ekleme işlemini yapmıyordu; alıcı SMTP sunucusu ilk noktayı silince, gövdedeki gerçek nokta kayboluyordu
Düzeltme ve sonradan ortaya çıkan etki
- Düzeltme, satır noktayla başlayıp aynı satırda başka karakterler de olduğunda istemcinin bir nokta daha eklemesi şeklinde yapıldı
- Böyle gönderildiğinde, alıcı SMTP sunucusu ilk noktayı silse bile gövdede asıl nokta kalıyordu
- Sorunlu müşterinin aynı alıcı bilgileriyle özgün e-posta yerelde yeniden gönderildiğinde, nokta artık kaybolmuyordu
- Düzeltme dağıtıldıktan sonra, aynı SMTP istemci kodu önceki projeden alındığı için diğer ekiplere de bu hata bildirildi
- Birkaç ay sonra başka bir ekibin sistemi hâlâ yamalanmamış durumdaydı ve müşterilere yeni aylık sigorta primini bildiren önemli e-postalar gönderdi
- Bazı e-postalarda aylık sigorta primindeki ondalık ayırıcı noktası tam olarak satırın ilk karakter konumuna denk gelip kayboldu
- Sonuç olarak bazı müşteriler, yeni primin
$27.00yerine$2700olduğu izlenimini veren e-postalar aldı - Bu hata, e-posta gövdesindeki her satırın uzunluğuna bağlıydı ve yalnızca ad ile soyad uzunluğu tam denk gelen bazı müşterilerde ortaya çıkıyordu
- Neden zaten bilindiği için ilgili ekibin kodu hemen yamalandı
2 yorum
Başlıkta
periodkelimesini nokta değil, süre/dönem olarak yorumlamış gibi görünüyor hahaHacker News yorumları
Bu kodun bir kısmı SMTP istemcisini doğrudan uyguluyordu ve temel neden de oradaymış gibi görünüyor
Protokolü doğru uygulamak zordur; yazıda anlatılan hata türü de sık görülür
Doğru uygulanmış bir SMTP istemci kitaplığı olsaydı, giriş metnindeki noktanın konumundan bağımsız olarak SMTP kurallarına uygun şekilde kodlardı; şablon katmanının SMTP’yi düşünmesine gerek kalmazdı
Metni SMTP kurallarına uygun biçimde alıp kodlamak zor değil, ama en başta böyle bir işlemin gerektiğini bilmek gerekiyor
SQL injection, XSS gibi sayısız hata ve güvenlik açığı da aynı hatadan, yani string birleştirmeden doğuyor
SQL sorgularında değerleri sorgu şablonuna bağlama işi, tipsiz bir string alanında değil “SQL alanında” gerçekleşmeli;
SELECT * FROM foo WHERE foo.bar =+$userDatagibi serileştirilmiş SQL string’ini doğrudan oluşturmak yanlış bir yöntemHTML şablonları da benzer şekilde string gösterimi olarak değil belge ağacı düzeyinde ele alınırsa aptalca açıklar önlenebilir
E-postada noktaların kaybolmasını önlemek için SMTP hattının ortasına yapısız metin enjekte etmemek, çalışılan soyutlama düzeyine saygı göstermek gerekir
Bununla bağlantılı olarak langsec de bakmaya değer
“Yalnızca tek bir noktadan oluşan satırı” literal bir satır değil, kontrol dizisi olarak tanımladığı için böyle şeyler oluyor
SMTP gereksiz karmaşık tasarımın bir örneği; uygulama hataları da bu karmaşıklığı yansıtıyor
Kimseye bunu baştan yazmasını önermiyorum ama SMTP, tek başına bile doğru uygulanması zor olacak seviyede olmamalı
Geri kalanı ölü yük olduğu hâlde, hiç çağrılmayan fonksiyonlar yüzünden 20 bağımlılık daha geliyor; bir bakmışsınız kod tabanı birkaç MB şişmiş, kullandığınızı bile bilmediğiniz bir kitaplık için CVE uyarısı alıyorsunuz
Teknik kısmı başkaları zaten benden daha iyi ele almış, ama cümle sonundaki tek bir nokta kadar önemsiz görünen bir şeyin ne kadar önemli olabileceğini hatırlatan bir anekdot var
Çalıştığım Almanya’da işten ayrılırken yapılan işleri ve çalışan değerlendirmesini içeren bir referans mektubu olan “Zeugnis” istemek yaygındır; iş ararken de genelde istenen önemli bir belgedir
Elbette bir çalışan “bu kişi tembeldir, işe almayın” gibi bir ifadeyi kabul etmeyeceği için, övgü gibi gizlenmiş “Zeugnissprache” denen bir tür şifre ortaya çıkmış
Bu şifrelerden biri de son cümlede nokta yoksa “burada yazanların hepsini görmezden gelin, bu kişi berbattır” anlamına geldiği
Önceki işimden sonra Zeugnis’imi bir avukata incelettim; tüm değerlendirmeler olumlu olmasına rağmen, muhtemelen dikkatsizlikten son cümlede nokta eksikti
İnsan kaynakları çalışanlarının potansiyel rakipleriyle ortak kullanacakları gizli kodlar üretmek için bir motivasyonu yok; bunu yeni İK çalışanlarına öğretip aynı zamanda gizli tutmak da mantıklı değil
Bu tür efsaneler, İK’nın birinin baş belası olduğunu fazla açık yazamamasından doğuyor
Olumlu yazacak bir şey yoksa dakiklik gibi sıradan bir artıyı övmek gibi; bu, gizli şifreden çok İK dilindeki “bless their heart”a yakın
Dava açılırsa “Sayın hâkim, iyi bir şey söyledik! Her zaman zamanında gelirdi!” diyerek sıyrılacak alan bırakılmış oluyor
E-posta gönderen bir cron işinin neden kendi SMTP istemcisini uygulaması gerektiğini anlamıyorum
Basitçe mailutils’in
mailkomutu gibi bir program kullanılabilirTeslim edilebilirlik açısından da soket üzerinde iskelet hâlinde bir SMTP etkileşimini baştan yazmak en baştan zorlama
Günümüzde herhangi bir mail exchange ana makinesine doğrudan bağlanıp e-posta gönderemezsiniz; genellikle ISP’nin sağladığı belirli bir SMTP relay ana makinesine bağlanmanız gerekir
Bunun için TLS bağlantısı, kimlik doğrulama vb. her şeyi uygulamak gerekir
Biraz ironik olan, cron’un kendisinin zaten e-posta göndermeyi bilmesi
Cron işinin çıktısı sahibine e-posta olarak gönderilir; bazı cron’lar crontab’daki
MAILTOdeğişkeni gibi şeylerle alıcı adresini değiştirebilirNedeni şu: “o şeyi” kullanmak isteyen kullanıcı bir SMTP sunucu adresi girebilir, ama programın sendmail gibi gönderici MTA’nın doğru yapılandırıldığına güvenmesinin bir yolu yok
Şirketlerde sistem e-postası gönderemeyen çok sayıda sunucu bulunuyor ve bu durum çoğu zaman program yazarının ya da kullanıcının kontrolü dışında oluyor
E-posta yapılandırması DNS, şifreleme, politika gibi ön koşullar gerektiren uzmanlık alanı; bir cüzdan uygulamasına yalnızca e-posta göndertmek için fazla ağır
“Sistem yöneticinize başvurun” büyük ya da küçük ölçekte çoğu zaman gerçekçi bir seçenek değil
İyi bir neden değil ama gerçek neden bu
Çoğu dilde, standart kitaplıkta olmasa bile kullanılabilir çok sayıda SMTP istemci kitaplığı var
Burada iki büyük kötü alışkanlık görünüyor
İlki, birçok kişinin işaret ettiği gibi standartları baştan savma uygulamamak gerektiği
Kendiniz uygulamanız gerekiyorsa gereken dikkat ve özeni göstermeli ya da hazır bir kütüphane kullanmalısınız
İkincisi, bağımlılıkları vendor etmemeniz gerektiği
Kullandığınız kütüphaneleri düzenli olarak, zamanında güncellemelisiniz; “yalnızca ihtiyaç olduğunda” değil
Güncellemeleri geciktirmek ya da tamamen kaçınmak, upstream’de zaten düzeltilmiş hataların bile ancak sorun kendilerinin karşısına çıktığında güncellenmesi gerektiğini düşünenler için büyük problemlere dönüşmesine yol açabilir
O zaman uygulamanın kararlılığı upstream değişikliklerine açık hâle gelir ve bu değişiklikler kodu bozabilir
Düzeltmeleri hemen alamayabilirsiniz ama istenmeyen kararsızlık ve ek riskleri içeri almaktansa, ihtiyaç duyduğum bir düzeltme nedeniyle değişiklik yaptığımı bilmenin daha iyi olduğunu düşünüyorum
“Bozuk değilse tamir etme” tarafındayım
Günümüzde birçok kişi terminalle doğrudan etkileşime girerek temel protokolleri öğrenmiyor gibi
SMTP sanki başlangıçta bu tür elle etkileşim düşünülerek tasarlanmış; bir süre gerçekten böyle kullanmış biri olarak, mesajı bitiren “tek noktalı bir satır” zihnime kalıcı biçimde kazındı
Bununla ilgili escape işlemleri de birçok programcıya yabancı bir kavram gibi görünüyor
Yukarıdaki durumu görüp “Peki yalnızca nokta içeren bir satırı olan e-posta göndermek istersem ne olacak?” diye sormayan çok kişi var; ama başka büyük bir grup da bu soruyu son derece mantıklı ve anlaşılır bulacaktır
Dot stuffing gerekiyor
SMTP https://www.rfc-editor.org/rfc/rfc5321#section-4.5.2
POP3’te de var https://www.rfc-editor.org/rfc/rfc1939#page-8
Bir ağ protokolü implementasyonunda hata ayıkladığım deneyimi hatırlattı. Daha spesifik olarak, eskiler için AppleTalk NBP idi
Her şeyi kodlamıştım ama paketlerim reddediliyor, yani sessizce drop ediliyor; gerçek Apple implementasyonunun paketleri ise geçiyordu
İyi paketlerle kötü paketleri bilgisayar ekranında yan yana açıp bayt bayt karşılaştırdım ama sorunu bulamadım; checksum dâhil baştan sona tamamen aynıydılar
İşten çıkma vakti gelince, sonra bakmak için o aptal şeylerin çıktısını almaya karar verdim; çıktı alır almaz hatayı gördüm
Benim sürümüm iki sayfaydı, düzgün implementasyon bir sayfaydı
Veriyi göndermeden önce buffer’ı düzgün boşaltmamıştım. mbuf dediğin böyle bir şey
Hâlâ aklıma geldikçe gülüyorum
“We are happy to welcome you to our family.” satırı satır uzunluğu sınırının yakınına bile gelmiyor
Başka bir şey olmuş gibi; örneğin tamamı aslında bir HTML MIME eki olabilir
Şöyle,
... lots of text ...ardındanWe are happy to welcome you to our family.gelmiş olabilirAma HTML’i gelişigüzel satırlara bölerseniz etiketleri kırarsınız
Şirketin o küçük sorunun hiç farkına varmadan geçip gitmesi muhtemeldir
quoted-printable, CRLF dâhil en fazla 78 karakter sınırına sahip olmalı; ancak e-posta istemcileri genelde toleranslıdır
Dot stuffing’i ilk kez duyan biri, e-posta dünyasında başka ne tür korkular olduğunu inanmakta zorlanacaktır
Header folding, local part’ın tırnaklanması, IPv6 literal’ları gibi şeyler var
Önceki bir projeden ödünç alınmış SMTP istemci kodu olduğunu okur okumaz, sonucun diğer ekibin bu hatayı hâlâ patch etmediği şeklinde çıkacağını sandım