Kasus Aneh Titik yang Menghilang
(tjaart.substack.com)- Dalam sistem yang membuat email, SMS, dan PDF dari templat dokumen, muncul masalah di mana titik menghilang hanya pada isi email yang dikirim ke pelanggan tertentu
- Titik ada di sumber templat, pratinjau, dan PDF, tetapi masalah hanya dapat direproduksi pada panjang baris tertentu setelah nilai placeholder tiap pelanggan diganti dengan data nyata
- Klien SMTP kustom membatasi panjang baris dan mendorong titik ke karakter pertama di baris baru, lalu server menghapus titik itu karena aturan dot transparency di SMTP
- Perbaikannya dilakukan dengan membuat klien menambahkan satu titik lagi di depan baris yang dimulai dengan titik dan diikuti karakter lain
- Karena tim lain yang memakai kode klien SMTP yang sama tidak memasang patch, email yang menampilkan premi bulanan $27.00 sebagai $2700 sempat terkirim ke sebagian pelanggan, dan bug itu segera diperbaiki
Masalah yang bermula dari sistem templat terpusat
- Sekitar 7 tahun lalu, seorang klien ingin menggabungkan dokumen yang tersebar di berbagai templat Microsoft Word ke dalam satu sistem
- Cara lama mengharuskan staf mengganti placeholder di dokumen secara manual dengan nama depan, nama belakang, dan lain-lain, sehingga templat dengan syarat lama, logo perusahaan lama, dan font yang salah ikut beredar dan sulit dikelola
- Sistem baru dirancang untuk mengelola templat dokumen secara terpusat, lalu menggunakannya untuk membuat dokumen PDF, pesan teks, dan isi email
- Pesan sambutan pelanggan baru juga bisa memiliki templat berbeda tergantung cara pengirimannya
- Versi email dapat menggunakan tabel HTML dan gaya dasar
- Versi untuk pengiriman pos dapat memuat infografik
- Versi pesan teks hanya dapat berisi sapaan sambutan singkat
Titik yang hilang hanya untuk pelanggan tertentu
- Setelah sistem berjalan beberapa bulan, atau bahkan lebih dari setahun, seorang administrator melaporkan bahwa satu titik hilang dari isi email yang dikirim ke pelanggan tertentu
- Saat email yang sama dikirim ke pelanggan lain, titiknya tidak hilang, sehingga sulit dianggap sebagai kesalahan templat biasa
- Di kode sumber templat, titik tersebut memang benar-benar ada
- Saat menyalin templat produksi ke lingkungan lokal dan membuat pratinjau isi email, titik itu juga terlihat, dan pada versi keluaran yang dibuat dari templat yang sama pun tampil normal
- Lingkungan lokal mengirim email ke localhost pada port tertentu, lalu email diperiksa dengan server SMTP palsu seperti SMTP4dev dan Outlook
- Saat email pertama kali dikirim dari lokal dan diperiksa di Outlook, titik itu juga tampil normal
Kondisi reproduksi yang dibentuk oleh data per pelanggan
- Saat membuat email, PDF, dan pesan teks, templat mengganti nilai placeholder seperti nama depan dan nama belakang pelanggan dengan data nyata
- Karena itu, meskipun templatenya sama, panjang isi email yang dikirim ke tiap pelanggan bisa berbeda
- Setelah nilai placeholder yang benar-benar dipakai untuk pelanggan bermasalah ditemukan dan email dikirim ulang di lingkungan lokal dengan nilai yang sama, gejala titik menghilang bisa dikonfirmasi di Outlook
- Masalah ini bergantung pada panjang dan isi spesifik dari badan email pelanggan tersebut
- Awalnya sempat diperiksa apakah karakter titik di templat ter-encode atau ternyata bukan titik biasa, tetapi itu bukan penyebabnya
- Ketika posisi titik digeser satu karakter di dalam templat, titik itu kembali terlihat di Outlook, sehingga posisi baris menjadi petunjuk reproduksi
Panjang baris SMTP dan dot transparency
- Hasil debugging menunjukkan bahwa kode yang menyimpan email ke database tidak mengubah templat selain mengganti placeholder dengan data pelanggan
- Setelah itu, ruang lingkup penyelidikan menyempit ke kode pekerjaan cron yang secara berkala mengambil target pengiriman dan mengirim email
- Sebagian kode yang dipanggil pekerjaan cron diambil dari proyek sebelumnya, dan di dalamnya ada implementasi klien SMTP kustom
- Kode tersebut memiliki fungsi untuk memecah baris agar setiap baris pada isi email tidak melebihi jumlah karakter tertentu
- Perilaku ini berkaitan dengan batas panjang baris dalam spesifikasi SMTP
- Panjang total maksimum satu baris teks adalah 1000 octets termasuk
<CRLF> - Titik awal yang digandakan untuk transparansi tidak dihitung dalam perhitungan ini
- Nilai ini dapat diperbesar oleh SMTP Service Extensions
- Panjang total maksimum satu baris teks adalah 1000 octets termasuk
- Pada isi email yang bermasalah, batas panjang baris membuat titik berpindah menjadi karakter pertama di baris berikutnya
- Dalam spesifikasi SMTP, penanda akhir data mail diperlakukan sebagai satu baris yang hanya berisi satu titik
- Aturan dot transparency di SMTP menangani secara khusus baris isi yang dimulai dengan titik
- Klien SMTP harus memeriksa sebelum mengirim setiap baris teks mail apakah karakter pertamanya adalah titik, dan jika ya, harus menambahkan satu titik lagi di awal baris
- Server SMTP akan menganggap baris yang hanya berisi satu titik sebagai akhir data mail
- Jika karakter pertama adalah titik dan ada karakter lain pada baris yang sama, server akan menghapus karakter pertama itu
- Klien SMTP kustom tidak melakukan penambahan satu titik lagi ketika sebuah baris dimulai dengan titik, sehingga server SMTP penerima menghapus titik pertama dan titik asli di isi email pun hilang
Perbaikan dan dampak yang baru terlihat belakangan
- Perbaikannya adalah membuat klien menambahkan satu titik lagi saat sebuah baris dimulai dengan titik dan masih memiliki karakter lain pada baris yang sama
- Dengan cara ini, meskipun server SMTP penerima menghapus titik pertama, titik asli tetap tersisa di isi email
- Saat email asli dikirim ulang secara lokal dengan informasi penerima yang sama seperti pelanggan bermasalah, titik itu tidak lagi hilang
- Setelah perbaikan dirilis, tim lain juga diberi tahu tentang bug tersebut karena mereka memakai kode klien SMTP yang sama dari proyek sebelumnya
- Beberapa bulan kemudian, sistem milik tim lain ternyata masih belum dipatch dan mengirim banyak email penting yang memberi tahu pelanggan tentang premi bulanan baru mereka
- Pada sebagian email, titik pemisah desimal pada premi bulanan tepat berada di posisi karakter pertama sebuah baris lalu menghilang
- Akibatnya, sebagian pelanggan menerima email yang membuat premi baru mereka tampak seperti
$2700, bukan$27.00 - Bug ini bergantung pada panjang setiap baris isi email, dan hanya terjadi pada sebagian pelanggan yang panjang nama depan dan nama belakangnya tepat cocok dengan kondisi tersebut
- Karena penyebabnya sudah diketahui, kode tim tersebut langsung dipatch
2 komentar
Sepertinya di judul,
perioddipahami sebagai periode waktu, bukan tanda titik, hahaKomentar Hacker News
Kode ini tampaknya mengimplementasikan sebagian klien SMTP secara langsung, dan akar masalahnya sepertinya ada di situ
Mengimplementasikan protokol dengan tepat itu sulit, dan bug seperti yang dibahas di artikel cukup sering terjadi
Jika memakai library klien SMTP yang diimplementasikan dengan benar, teks masukan akan dienkode sesuai spesifikasi SMTP tanpa peduli posisi titik di dalam teks, dan lapisan templat tidak perlu memikirkan SMTP
Menerima teks lalu mengodekannya agar sesuai dengan spesifikasi SMTP tidaklah sulit, tetapi orang harus tahu sejak awal bahwa pemrosesan itu memang diperlukan
Banyak bug dan kerentanan keamanan seperti SQL injection dan XSS juga lahir dari kesalahan yang sama, yaitu menyambung-nyambung string
Dalam kueri SQL, pengikatan nilai ke templat kueri harus terjadi di “ruang SQL”, bukan di ruang string tanpa tipe; membuat string SQL terserialisasi secara langsung seperti
SELECT * FROM foo WHERE foo.bar =+$userDataadalah cara yang salahTemplat HTML juga sama: jika ditangani pada level pohon dokumen alih-alih sebagai representasi string, banyak kerentanan bodoh bisa dihindari
Untuk menghindari titik yang hilang dalam email, jangan menyuntikkan teks tak berstruktur di tengah pipeline SMTP; hormati level abstraksi yang sedang dipakai
Terkait ini, langsec juga layak dilihat
Karena “baris yang hanya berisi satu titik” didefinisikan bukan sebagai baris literal, melainkan sebagai urutan kontrol, hal seperti ini bisa terjadi
SMTP adalah contoh desain yang rumit tanpa perlu, dan bug implementasinya mencerminkan kerumitan itu
Bukan berarti saya menyarankan untuk mengimplementasikannya sendiri, tetapi SMTP juga seharusnya tidak sampai sesulit itu untuk diimplementasikan dengan benar sendirian
Sisanya cuma beban mati, tetapi karena ada fungsi yang bahkan tidak dipanggil, ia tetap menarik 20 dependensi tambahan, dan tahu-tahu codebase membengkak beberapa MB serta Anda menerima peringatan CVE dari library yang bahkan tidak sadar sedang dipakai
Sisi teknisnya sudah dibahas lebih baik oleh orang lain, tetapi ini mengingatkan saya pada anekdot tentang betapa pentingnya hal kecil seperti satu titik di akhir kalimat
Di Jerman tempat saya bekerja, umum untuk meminta “Zeugnis”, yaitu surat rekomendasi saat keluar kerja yang berisi tugas yang dijalankan dan penilaian terhadap karyawan, dan ini dokumen penting yang biasanya diminta saat melamar kerja
Tentu saja tidak mungkin seorang karyawan mau menerima kalimat seperti “orang ini malas, jangan dipekerjakan”, jadi muncul semacam kode yang disebut “Zeugnissprache”, disamarkan seperti pujian
Salah satu kodenya konon jika kalimat terakhir tidak diakhiri titik, artinya “abaikan semua yang tertulis di sini, orang ini buruk sekali”
Setelah pekerjaan saya sebelumnya, saya meminta pengacara meninjau Zeugnis saya, dan meskipun semua penilaiannya positif, entah karena ceroboh atau bukan, kalimat terakhirnya memang tidak punya titik
Staf HR tidak punya insentif untuk membuat kode rahasia bersama para pesaing potensial mereka, dan tidak masuk akal juga membayangkan itu bisa diajarkan ke staf HR baru sambil tetap dirahasiakan
Legenda seperti ini muncul karena pihak HR tidak bisa menulis terlalu blak-blakan bahwa seseorang itu bermasalah
Jika tidak ada hal positif yang bisa ditulis, mereka akhirnya memuji kelebihan biasa seperti ketepatan waktu; ini lebih mirip gaya bahasa HR versi “bless their heart” daripada kode rahasia
Kalau sampai digugat, mereka masih punya celah untuk berkata, “Yang Mulia, itu pujian! Dia memang selalu datang tepat waktu!”
Saya tidak paham kenapa sebuah pekerjaan cron yang mengirim email harus mengimplementasikan klien SMTP sendiri
Tinggal pakai program seperti
maildari mailutilsBahkan dari sudut pandang deliverability, membuat sendiri interaksi SMTP minimal di atas socket itu sudah sulit sejak awal
Sekarang Anda tidak bisa begitu saja terhubung langsung ke sembarang host penukar email untuk mengirim surat; biasanya harus terhubung ke host relay SMTP tertentu yang disediakan ISP
Itu berarti harus mengimplementasikan juga koneksi TLS, autentikasi, dan seterusnya
Sedikit ironisnya, cron sendiri sebenarnya sudah bisa mengirim email
Output dari pekerjaan cron dikirim lewat email kepada pemiliknya, dan di beberapa cron alamat penerimanya bisa diubah lewat hal seperti variabel
MAILTOdi crontabAlasannya, meski pengguna “barang itu” mungkin bisa memasukkan alamat server SMTP, program tersebut tidak punya cara untuk percaya bahwa MTA pengirim seperti sendmail sudah dikonfigurasi dengan benar
Di perusahaan besar ada banyak server yang memang tidak bisa mengirim email sistem, dan kondisinya kadang berada di luar kendali pembuat program maupun pengguna
Konfigurasi email adalah bidang khusus dengan prasyarat seperti DNS, enkripsi, dan kebijakan, jadi itu terlalu berat jika tujuan satu-satunya cuma ingin sesuatu seperti aplikasi dompet bisa mengirim email
“Hubungi administrator sistem” sering kali bukan pilihan yang realistis, baik di skala besar maupun kecil
Bukan alasan yang bagus, tetapi itulah alasan yang sebenarnya
Di hampir semua bahasa, sekalipun tidak ada di pustaka standar, ada banyak library klien SMTP yang cukup bagus untuk dipakai
Ada dua kebiasaan buruk besar yang terlihat di sini
Yang pertama, seperti sudah ditunjukkan banyak orang, adalah jangan mengimplementasikan standar secara asal-asalan
Jika memang harus mengimplementasikannya sendiri, lakukan dengan perhatian dan ketelitian yang diperlukan, atau gunakan library yang sudah ada
Yang kedua adalah jangan melakukan vendoring dependensi
Library yang digunakan harus diperbarui secara berkala dan tepat waktu, bukan hanya “saat diperlukan”
Jika pembaruan ditunda atau malah dihindari, bug yang sudah diperbaiki di upstream bisa menjadi masalah besar bagi orang-orang yang merasa cukup memperbarui hanya ketika masalahnya terlihat langsung
Dalam kasus itu, stabilitas aplikasi terekspos pada perubahan upstream, dan perubahan itu bisa merusak kode
Memang kita mungkin tidak langsung mendapatkan perbaikan, tetapi menurut saya lebih baik tahu bahwa saya melakukan perubahan karena perbaikan yang saya butuhkan daripada memasukkan ketidakstabilan dan risiko tambahan yang tidak diinginkan
Saya cenderung ke prinsip “kalau tidak rusak, jangan diperbaiki”
Belakangan ini, tampaknya banyak orang tidak mempelajari protokol dasar dengan berinteraksi langsung lewat terminal
SMTP tampaknya memang dirancang dengan mempertimbangkan interaksi manual seperti itu, dan sebagai orang yang pernah benar-benar memakainya seperti itu untuk sementara waktu, “satu baris berisi satu titik” untuk mengakhiri pesan terasa terpatri permanen dalam ingatan
Terkait hal itu, penanganan escape juga tampak seperti konsep yang asing bagi banyak programmer
Banyak orang melihat situasi di atas tanpa bertanya, “Kalau ingin mengirim email yang punya baris berisi satu titik saja, bagaimana?” tetapi ada juga kelompok besar lain yang akan merasa pertanyaan itu sangat logis dan mudah dipahami
Dot stuffing memang diperlukan
SMTP https://www.rfc-editor.org/rfc/rfc5321#section-4.5.2
Ada juga di POP3 https://www.rfc-editor.org/rfc/rfc1939#page-8
Ini mengingatkan saya pada pengalaman men-debug implementasi protokol jaringan. Tepatnya AppleTalk NBP untuk orang-orang lama
Saya sudah mengodekan semuanya, tetapi paket saya ditolak, atau lebih tepatnya dibuang diam-diam, sementara paket dari implementasi Apple yang asli lolos
Saya menaruh paket yang benar dan yang salah di layar komputer dan membandingkannya byte demi byte, tetapi tidak bisa menemukan masalahnya, dan dari awal sampai akhir semuanya sama persis termasuk checksum
Saat waktunya pulang dan saya memutuskan untuk mencetak hal-hal bodoh itu agar dilihat nanti, begitu dicetak kesalahannya langsung terlihat
Versi saya dua halaman, sementara implementasi yang benar hanya satu halaman
Ternyata saya tidak mengosongkan buffer dengan benar sebelum mengirim data. Begitulah
mbufSampai sekarang pun kalau dipikirkan masih lucu
Baris “We are happy to welcome you to our family.” sama sekali tidak mendekati batas panjang baris
Sepertinya ada hal lain yang terjadi, misalnya keseluruhannya sebenarnya berupa lampiran HTML MIME
Bisa jadi bentuknya seperti
... lots of text ...lalu diikutiWe are happy to welcome you to our family.Tetapi kalau HTML dipotong menjadi baris secara sembarangan, tag-nya akan rusak
Sangat mungkin perusahaan itu sendiri lewat begitu saja tanpa pernah menyadari masalah kecil tersebut
Quoted-printable seharusnya memiliki batas maksimum 78 karakter termasuk CRLF, tetapi klien email umumnya cukup toleran
Jika seseorang baru pertama kali mendengar dot stuffing, mungkin sulit percaya ada kengerian lain apa saja di dunia email
Ada pelipatan header, penanganan tanda kutip di local part, hal-hal seperti IPv6 literal, dan sebagainya
Begitu membaca bagian bahwa itu adalah kode klien SMTP yang dipinjam dari proyek sebelumnya, saya langsung menduga akhirnya akan berujung pada tim lain yang ternyata belum menambal bug ini