Filter yang diam-diam kosong
Tiga kegagalan publik satu malam: filter tak pernah cocok, pencarian meledak 500, dan 403 yang memantulkan input.
Ringkasan
Tiga bug publik: pencarian satu bintang bikin error 500, filter fasilitas selalu kosong, dan pesan error 403 ikut memantulkan input pengguna. Semuanya diperbaiki lewat sanitasi di batas input, pola LIKE yang cocok sama format data asli, plus pesan galat yang diamkan nilai mentah. Pelajarannya satu: jaga semuanya di tepi.
Seorang pengunjung mengetik satu kata di kolom pencarian modul daftar wisata dan menerima sebuah halaman 500. Bukan pada jam sibuk, bukan karena basis data down: satu karakter bintang di dalam istilah pencarian sudah cukup untuk mematahkan parser full-text di dalam basis data. Malah, ticket-tetangga menunjukkan gejala yang lebih tenang: filter fasilitas selalu mengembalikan nol baris untuk semua pengguna. Tanpa error. Tanpa log keras. Halaman hanya diam.
Tiga kegagalan ini sekelas karena semuanya berada di permukaan publik, dan ketiganya tidak berteriak. Dua yang pertama gagal dalam senyap, yang ketiga malah terlalu banyak bicara. Perbaikannya dijalankan dalam satu malam dan setiap perbaikan meninggalkan satu pelajaran yang bisa dibawa keluar dari modul ini.
Filter yang selalu kosong karena berjangkar
Pola pencarian fasilitas dibangun seperti ini: kutip, kata, tanda persen. Pola itu berjangkar di kolom, artinya hanya cocok bila nilai kolom memulai dengan kata itu. Masalahnya, kolom tidak menyimpan kata. Kolom menyimpan larik JSON berformat seperti daftar fasilitas berderet. Larik selalu dimulai dengan tanda kurung siku, bukan dengan kata; pattern berjangkar tidak pernah bisa cocok dengan bentuk data kanoniknya, sekecil apa pun kemungkinannya.
Perbaikannya mengubah jangkar menjadi substring: persen di kedua sisi. Aturan pelolosan wildcard tetap berlaku. Dokumen MySQL merumuskan dasarnya: "% matches any number of characters, even zero characters. _ matches exactly one character." [9], dan bila wildcard ingin dicari sebagai karakter biasa, "To test for literal instances of a wildcard character, precede it by the escape character." [9]. Semua input pengguna tetap di-escape sebelum diloloskan, sehingga tanda persen atau garis bawah dari ketikan pengunjung tidak bisa berpose sebagai wildcard.
Substring tidak gratis. Satu fasilitas bernama wifi-id-room akan ikut tersangkut saat pengguna menginginkan wifi. Kompromi ini ditulis di komentar kode bersama upgrade path-nya, tabel normalisasi fasilitas, yang memang bukan pilihan murah pada versi basis data yang dipakai. Keputusiannya eksplisit: filter publik yang sedikit terlalu bebas lebih baik daripada filter yang tidak pernah bekerja.
Sisi test juga disusun ulang. Test lama adalah test karakterisasi yang hadir sebelum perbaikan: ia mengunci perilaku yang salah dan membuatnya tetap hijau. Test karakterisasi yang dibangun dari kutu bukan kontrak. Test baru menanam larik JSON kanonik ke fixture, lalu menegaskan filter mengembalikan satu baris untuk fasilitas yang ada dan nol baris untuk yang tidak ada.
Pencarian yang meledak karena parser
Kegagalan kedua berurutan lebih keras. Istilah pencarian mentah dari URL masuk ke panggilan pencarian full-text tanpa disentuh. Parser full-text basis data punya bahasanya sendiri, dan karakter seperti bintang, plus, atau kurung bukan teks biasa bagi parser itu. Satu tanda bintang menghasilkan galat sintaks 1064, dan server membalas 500 pada endpoint GET yang seluruh dunia bisa memanggil.
Sanitasi dilakukan di batas kepercayaan, tepat di handler sebelum query turun. Karakter operator dihapus, spasi berlebih dirumahkan, dan panjang istilah dipangkas pada 100 karakter. Hasil kosong diperlakukan sebagai tanpa filter, bukan sebagai kegagalan, jadi permintaan yang isinya hanya operator pun memperoleh 200 dengan daftar tak tersaring. Kenapa menghapus karakter aman? Mode pencariannya adalah natural language. Dokumentasi MySQL merumuskannya: "A natural language search interprets the search string as a phrase in natural human language (a phrase in free text). There are no special operators, with the exception of double quote (") characters." [8]. Mode itu butuh operator yang dibuang, jadi membuangnya bukan mengubah arti pencarian, melainkan membuang sintaks yang oleh mode itu memang tidak dipakai. "Full-text searching is performed using MATCH() AGAINST() syntax." [8], dan syntax itulah yang menjadi garis pertahanan input.
Pesan yang memantulkan kembali jawabannya
Kegagalan ketiga paling senyap. Detail 403 pada endpoint ekspor memantulkan kembali nilai dari parameter yang diketik pembuat permintaan. Alurnya sederhana: service membungkus galat dengan nilai dari parameter, handler menuliskan galat sebagaimana adanya, dan penyerang memperoleh konfirmasi persis atas string yang dikirimnya. Untuk pengintaian, umpan balik seperti itu layak uang: OWASP merangkumnya sebagai "Error handling is a part of the overall security of an application." [7], karena "Unhandled errors can assist an attacker in this initial phase, which is very important for the rest of the attack." [7].
Perbaikannya bukan membungkam pesan, melainkan memindah isinya. Detail di body menjadi statis: keterangan kondisi, tanpa echo. Nilai mentah tetap tersimpan, tetapi di log server tempat pencatatan dimaksudkan untuk auditor, bukan untuk peminta. Test baru menegaskan nilai mentah tidak muncul di mana pun pada body respons.
Tiga perbaikan, satu prinsip di baliknya:penjagaan di tepi. Pola LIKE diuji terhadap format data yang sungguh disimpan, input publik disanitasi sebelum menyentuh parser khusus, dan jawaban untuk penyerang dirumuskan sebagai kondisi, bukan sebagai cermin. Ketiga lapis ini menutup kanal kegagalan yang satu per satu tidak tampak, tetapi bersama-sama membentuk kesan situs yang setengah mati.
Sumber
- OWASP Cheat Sheet Series, Error Handling (diakses 2026-10-12): "Error handling is a part of the overall security of an application."; "Unhandled errors can assist an attacker in this initial phase, which is very important for the rest of the attack."
- MySQL 5.7 Reference Manual, Full-Text Search Functions (snapshot Wayback, diakses 2026-10-12; halaman live diblokir): "Full-text searching is performed using MATCH() AGAINST() syntax."; "A natural language search interprets the search string as a phrase in natural human language (a phrase in free text). There are no special operators, with the exception of double quote (") characters."
- MySQL 5.7 Reference Manual, String Comparison Functions (snapshot Wayback, diakses 2026-10-12): "% matches any number of characters, even zero characters. _ matches exactly one character."; "To test for literal instances of a wildcard character, precede it by the escape character."
Sumber
- OWASP Cheat Sheet Series, Error Handling (diakses 2026-10-12): "Error handling is a part of the overall security of an application."; "Unhandled errors can assist an attacker in this initial phase, which is very important for the rest of the attack."
- MySQL 5.7 Reference Manual, Full-Text Search Functions (snapshot Wayback, diakses 2026-10-12; halaman live diblokir): "Full-text searching is performed using MATCH() AGAINST() syntax."; "A natural language search interprets the search string as a phrase in natural human language (a phrase in free text). There are no special operators, with the exception of double quote (") characters."
- MySQL 5.7 Reference Manual, String Comparison Functions (snapshot Wayback, diakses 2026-10-12): "% matches any number of characters, even zero characters. _ matches exactly one character."; "To test for literal instances of a wildcard character, precede it by the escape character."