cfquery dan cfqueryparam: Mencegah Suntikan SQL dalam CFML
Tutorial keselamatan CFML: cegah sql injection dengan cfqueryparam dan queryExecute. Contoh cfquery rosak vs betul, jenis cfsqltype, dan amalan terbaik Lucee.

Kandungan
- cfquery dan cfqueryparam: Mencegah Suntikan SQL dalam CFML
- Apa Itu Suntikan SQL dan Kenapa Ia Bahaya
- Kod Rosak: Mencantum Nilai Secara Terus
- Penyelesaian Gaya Tag: cfqueryparam
- Penyelesaian Gaya Skrip: queryExecute dengan Parameter
- Memilih Jenis Data dengan cfsqltype
- Senarai Nilai, NULL dan Kes Khas
- Apa yang Tidak Boleh Diparameterisasi
- Amalan Terbaik Ringkas
- Soalan Lazim (FAQ)
- Sumber
cfquery dan cfqueryparam: Mencegah Suntikan SQL dalam CFML
Suntikan SQL berlaku apabila input pengguna dicampur terus ke dalam rentetan pertanyaan SQL, membenarkan penyerang menukar struktur pertanyaan untuk mencuri, mengubah atau memadam data. Dalam CFML, penyelesaiannya ialah mengikat setiap nilai dinamik sebagai parameter, menggunakan tag cfqueryparam dalam gaya tag atau parameter bernama dalam queryExecute() untuk gaya skrip. Artikel ini menunjukkan kod yang rosak berbanding kod yang betul, supaya anda dapat melihat dengan jelas apa yang berubah dan kenapa.
Apa Itu Suntikan SQL dan Kenapa Ia Bahaya
Suntikan SQL ialah teknik serangan di mana penyerang menghantar data yang direka khas supaya aplikasi membinanya menjadi arahan SQL yang tidak sepatutnya wujud. Ia antara kelemahan web paling lama dan paling merbahaya, dan dalam OWASP Top 10 terkini (2025) ia dikategorikan di bawah A05 Injection. Garis panduan OWASP juga menegaskan bahawa suntikan SQL paling baik dicegah melalui pertanyaan berparameter, dan Cheat Sheet mereka menyenaraikan cfqueryparam sebagai cara terbina dalam untuk bahasa ColdFusion. Kesan serangan yang berjaya boleh jadi serius: pangkalan data dibaca sepenuhnya, kata laluan dicuri, rekod diubah, atau dalam kes tertentu penyerang mendapat jalan ke sistem operasi pelayan.
Punca utama masalah ini ialah kepercayaan buta terhadap input. Apabila aplikasi menyusun SQL dengan cara mencantum rentetan, tiada sempadan yang membezakan bahagian arahan dengan bahagian data. Parameterisasi menyelesaikan masalah ini pada akarnya, iaitu dengan menghantar data secara berasingan daripada struktur SQL supaya pangkalan data tidak mungkin tersilap mentafsir data sebagai arahan.
Kod Rosak: Mencantum Nilai Secara Terus
Contoh berikut kelihatan tidak berbahaya tetapi sangat rapuh:
<cfquery name="q" datasource="mydb">
SELECT * FROM users WHERE username = '#url.username#'
</cfquery>
Jika penyerang menghantar nilai ' OR '1'='1 melalui parameter URL, pertanyaan sebenar yang sampai ke pangkalan data menjadi:
SELECT * FROM users WHERE username = '' OR '1'='1'
Keadaan '1'='1' sentiasa benar, jadi pertanyaan ini memulangkan semua pengguna dan bukannya seorang. Versi yang lebih ganas boleh menambah ; DROP TABLE users; atau UNION SELECT untuk membaca jadual lain. Masalah yang sama wujud dalam gaya skrip jika anda masih mencantum rentetan:
var sql = "SELECT * FROM users WHERE username = '#url.username#'";
var q = queryExecute(sql, {}, { datasource = "mydb" });
Dalam kedua-dua kes, punca kerosakan adalah sama: nilai pengguna menjadi sebahagian daripada sintaks SQL.
Penyelesaian Gaya Tag: cfqueryparam
cfqueryparam ialah tag yang bersarang di dalam cfquery dan tertanam dalam pernyataan SQL. Fungsinya ialah mengesahkan jenis data parameter dan mengikat nilainya supaya pangkalan data melayannya sebagai data, bukan arahan. Dokumentasi Lucee menyatakan tag ini meningkatkan keselamatan dengan mencegah suntikan SQL, menyediakan pengesahan nilai, dan membolehkan caching pertanyaan pangkalan data.
Versi selamat bagi contoh di atas:
<cfquery name="q" datasource="mydb">
SELECT * FROM users WHERE username = <cfqueryparam value="#url.username#" cfsqltype="cf_sql_varchar">
</cfquery>
Sekarang nilai url.username dihantar sebagai parameter terikat. Jika penyerang menghantar ' OR '1'='1, pangkalan data akan mencari pengguna yang nama penggunanya betul-betul sama dengan rentetan itu, dan tidak menemui apa-apa. Struktur pertanyaan tidak lagi boleh diubah oleh input.
Penyelesaian Gaya Skrip: queryExecute dengan Parameter
queryExecute() ialah padanan cfquery dalam bentuk fungsi, dan ia menggunakan kod asas yang sama. Untuk parameterisasi, hantar struktur params di mana setiap kunci sepadan dengan penanda bernama dalam SQL yang ditulis dengan titik bertindih di hadapannya:
var q = queryExecute(
sql = "SELECT * FROM users WHERE username = :username",
params = { username = { value = url.username, cfsqltype = "cf_sql_varchar" } },
options = { datasource = "mydb" }
);
Anda juga boleh menghantar tatasusunan dan menggunakan tanda soal sebagai penanda, tetapi struktur dengan nama kunci lebih mudah dibaca apabila pertanyaan mempunyai banyak parameter. Nilai dalam params boleh membawa kunci tambahan seperti list, null dan separator, iaitu padanan atribut pada tag cfqueryparam.
Memilih Jenis Data dengan cfsqltype
Menyatakan jenis data yang betul bukan sahaja menguatkan keselamatan, malah membantu Lucee melakukan penukaran jenis yang tepat antara CFML dan pangkalan data. Dokumentasi Lucee menyenaraikan jenis yang boleh digunakan sama ada dengan awalan CF_SQL_ atau tanpanya. Jadual berikut menunjukkan jenis yang paling lazim:
| Jenis data | Kegunaan | Contoh nilai |
|---|---|---|
| varchar | Rentetan teks (jenis lalai) | "thalhah" |
| integer | Nombor bulat | 42 |
| bigint | Integer besar | 9007199254740993 |
| decimal / double / money | Nombor perpuluhan atau kewangan | 19.90 |
| boolean / bit | Nilai benar atau salah | true |
| date / time / timestamp | Tarikh dan masa | createDate(2026,10,16) |
| binary / blob | Data binari | fileReadAsBinary(...) |
Menggunakan cf_sql_integer untuk parameter angka bermakna rentetan seperti abc akan ditolak sebelum sampai ke pangkalan data. Ini ialah satu lapisan pengesahan percuma yang anda dapat hanya dengan memilih jenis yang betul.
Senarai Nilai, NULL dan Kes Khas
Keperluan pertanyaan seperti operator IN dan nilai NULL ada cara parameterisasinya sendiri. Untuk IN, gunakan atribut list dengan nilai senarai atau tatasusunan:
<cfquery name="q" datasource="mydb">
SELECT id, name FROM products
WHERE id IN (<cfqueryparam value="#form.ids#" cfsqltype="cf_sql_integer" list="true">)
</cfquery>
Untuk menghantar nilai NULL sebenar, gunakan atribut null="true" supaya atribut value diabaikan. Ini penting kerana kebanyakan pangkalan data memerlukan sintaks IS NULL, bukan = NULL, untuk perbandingan. Atribut maxLength pula menambah pengesahan panjang supaya Lucee melempar pengecualian jika nilai melebihi had yang anda tetapkan.
Apa yang Tidak Boleh Diparameterisasi
Parameterisasi tidak meliputi semuanya. Nama jadual dan nama lajur secara amnya tidak boleh diikat sebagai parameter, kerana ia adalah struktur pertanyaan, bukan data. Jika anda perlu membina SQL dinamik yang memilih jadual atau lajur berdasarkan input, sahkan nilai itu terhadap senarai putih sebelum mencantumkannya:
var allowedColumns = { "name": true, "email": true, "created": true };
if (!structKeyExists(allowedColumns, form.sortBy)) {
form.sortBy = "name";
}
Prinsip asasnya mudah: apa-apa yang boleh diikat, ikatlah dengan parameter. Apa-apa yang tidak boleh diikat, sahkan dengan senarai putih yang ketat. Untuk memahami skop seperti url dan form yang muncul dalam contoh di atas, rujuk memahami scope CFML.
Amalan Terbaik Ringkas
Jadikan perkara berikut sebagai tabiat dalam setiap pertanyaan yang menyentuh input pengguna:
- Sentiasa gunakan
cfqueryparamatau parameterqueryExecute, tanpa pengecualian untuk nilai dinamik. - Nyatakan
cfsqltypeyang sepadan dengan jenis lajur pangkalan data. - Jangan sekali-kali membina SQL dengan mencantum rentetan untuk nilai pengguna.
- Gunakan
list="true"untuk operatorINdannull="true"untuk nilai kosong yang bermaksud NULL. - Sahkan nama jadual dan lajur dinamik dengan senarai putih.
Parameterisasi juga memberi kesan sampingan yang bagus: pertanyaan menjadi lebih pantas kerana pelan pelaksanaan boleh digunakan semula, dan data ditaip dengan betul tanpa penukaran manual. Dokumentasi Lucee turut menyenaraikan caching pertanyaan pangkalan data sebagai satu lagi faedah cfqueryparam. Untuk menyediakan sambungan pangkalan data yang dirujuk oleh atribut datasource, baca konfigurasi datasource. Jika anda masih keliru antara dua gaya penulisan, perbezaan gaya tag dan skrip menerangkan bila setiap satu lebih sesuai. Suntikan SQL hanyalah satu daripada beberapa ancaman aplikasi web, jadi lengkapkan pemahaman anda dengan panduan keselamatan aplikasi web CFML yang merangkumi XSS dan CSRF.
Soalan Lazim (FAQ)
Adakah cfqueryparam cukup untuk mencegah semua suntikan SQL?
Untuk nilai data yang boleh diikat, ya. Nama jadual dan lajur lazimnya tidak boleh diparameterisasi, jadi sahkan nilai itu dengan senarai putih sebelum mencantumkannya ke dalam SQL.
Apakah beza cfqueryparam dan queryExecute dengan params?
Kedua-duanya mencapai perkara yang sama, iaitu parameterisasi. cfqueryparam ialah cara gaya tag di dalam cfquery, manakala queryExecute dengan struktur params ialah cara gaya skrip, dan kedua-duanya menggunakan kod asas yang sama dalam Lucee.
Apakah jenis data yang perlu saya guna dalam cfqueryparam?
Gunakan jenis yang sepadan dengan lajur pangkalan data, contohnya cf_sql_integer untuk integer dan cf_sql_varchar untuk teks. Nilai boleh ditulis dengan atau tanpa awalan CF_SQL_.
Bolehkah saya menghantar senarai nilai untuk operator IN?
Boleh. Gunakan list="true" dengan nilai senarai atau tatasusunan, dan Lucee akan menaip setiap elemen mengikut jenis yang anda nyatakan.
Adakah parameterisasi menjadikan pertanyaan saya lebih perlahan?
Tidak. Ia biasanya menjadikan pertanyaan lebih pantas kerana pelan pelaksanaan boleh digunakan semula, selain menambah pengesahan jenis secara percuma.