cfexecute: jalankan proses sistem dan risikonya
Ketahui cara guna cfexecute untuk jalankan proses sistem dalam Lucee CFML, baca exitCode dan hasil, serta fahami risiko suntikan arahan dan cara kawalnya.

Kandungan
cfexecute ialah tag CFML yang menjalankan proses sistem luaran terus daripada pelayan, contohnya menjalankan /bin/date, /bin/echo atau skrip yang anda tulis sendiri. Ia berkuasa kerana ia menghubungkan aplikasi web anda dengan sistem operasi, tetapi kuasa itu datang dengan risiko yang besar: proses anak berjalan dengan keistimewaan pengguna pelayan, dan sebarang input pengguna yang masuk ke dalamnya boleh menjadi pintu kepada pelayan. Artikel ini menunjukkan cara menggunakannya dengan selamat dalam Lucee, apa maksud exitCode, dan kenapa kebanyakan tugas sebenarnya tidak memerlukannya langsung. Semua contoh kod di bawah telah dijalankan pada Lucee 7.1 dan lulus.
Apa itu cfexecute
cfexecute melaksanakan satu program di sisi pelayan dan menunggu atau tidak menunggu ia selesai, bergantung pada atribut timeout. Tag ini ialah jambatan antara CFML dan baris arahan sistem, jadi ia berguna untuk tugas seperti membaca tarikh sistem, memanggil alat baris arahan yang tiada padanan CFML, atau menjalankan skrip migrasi.
Perkara penting yang perlu difahami sejak awal: cfexecute menjalankan program itu secara terus, bukan melalui shell. Ini bermakna aksara shell seperti ;, | dan & tidak ditafsirkan oleh cfexecute sendiri. Ia hanya menjadi bahaya apabila anda sendiri membalut arahan itu dalam shell, contohnya name="/bin/sh" dengan arguments=["-c", "..."]. Fakta ini telah disahkan dalam ujian: menghantar a;b|c&d kepada /bin/echo memulangkan rentetan itu secara literal, bukan menjalankan tiga arahan.
Atribut utama cfexecute
Tag ini menerima beberapa atribut yang mengawal cara proses dijalankan dan ke mana outputnya pergi.
nameialah laluan penuh program yang hendak dijalankan, contohnya/bin/date. Atribut ini wajib. Pada Windows, sambungan fail seperti.exeperlu dinyatakan.argumentsialah hujah baris arahan yang dihantar kepada program. Ia boleh jadi satu rentetan, atau satu tatasusunan yang menjadi bentuk yang disyorkan kerana setiap elemen kekal sebagai satu hujah yang jelas.timeoutialah tempoh, dalam saat, utas menunggu proses selesai. Nilai lalai ialah0, yang bermakna tidak menyekat: proses dilancarkan dan halaman terus berjalan tanpa menunggu. Nilai positif menjadikannya mod menyekat sehingga tempoh itu.variablemenyimpan output standard program ke dalam pemboleh ubah. TanpavariableatauoutputFile, output dipaparkan terus pada halaman.outputFilemenyalurkan output standard ke satu fail pada cakera.errorVariabledanerrorFilemenangkap aliran ralat (stderr) program, sama ada ke pemboleh ubah atau ke fail.terminateOnTimeoutialah boolean yang membunuh proses anak apabila tempoh tamat. Lalainyafalse, jadi proses boleh terus hidup sebagai proses yatim selepas Lucee berhenti menunggu.resultmenyimpan satu struktur yang mengandungioutput,errordanexitCode, berguna untuk membaca kod keluar dalam satu langkah.
Apabila sesuatu proses melebihi timeout dalam mod menyekat, Lucee melontar pengecualian dengan mesej seperti timeout [1000 ms] expired. Oleh itu panggilan yang menyekat sebaiknya dibalut dengan try dan catch.
Menjalankan arahan pertama
Contoh paling ringkas ialah menjalankan /bin/date untuk membaca tarikh sistem dan menangkapnya dalam pemboleh ubah, kemudian menghantar output /bin/echo ke fail:
<cfexecute name="/bin/date"
arguments="+%Y-%m-%d"
variable="tarikh"
timeout="10">
</cfexecute>
<cfexecute name="/bin/echo"
arguments="Halo dari cfexecute"
outputFile="/tmp/keluaran-exec.txt"
timeout="10">
</cfexecute>
<cfoutput>Tarikh pelayan: #trim(tarikh)#</cfoutput>
Di sini timeout="10" memaksa Lucee menunggu sehingga sepuluh saat supaya variable="tarikh" benar-benar diisi. Tanpa tempoh yang positif, proses dilancarkan dan halaman terus berjalan, dan output yang sepatutnya ditangkap akan dibuang. Perhatikan juga outputFile menulis ke /tmp, di luar webroot, supaya fail itu tidak boleh dimuat turun terus oleh pelayar.
Membaca exitCode dan output
Setiap proses memulangkan kod keluar apabila tamat. Konvensyen pada Linux ialah 0 bermakna berjaya dan sebarang nilai bukan sifar bermakna gagal. Nilai biasa yang berguna: 1 ralat umum, 2 salah guna arahan, 126 tidak boleh dilaksana kerana keizinan, dan 127 arahan tidak dijumpai.
/bin/false ialah arahan kecil yang sentiasa gagal dengan kod 1, jadi ia sesuai untuk menunjukkan kod keluar bukan sifar:
<cfexecute name="/bin/date"
arguments="+%s"
variable="epoch"
timeout="10"
result="hasilTarikh">
</cfexecute>
<cfexecute name="/bin/false"
variable="keluar"
errorVariable="ralat"
timeout="10"
result="hasilGagal">
</cfexecute>
<cfoutput>
Tarikh exitCode: #hasilTarikh.exitCode#
false exitCode: #hasilGagal.exitCode#
</cfoutput>
Atribut result memulangkan struktur dengan kunci exitCode, output dan error, jadi anda boleh memeriksa kejayaan tanpa meneka. Dalam ujian, /bin/date memulangkan 0 dan /bin/false memulangkan 1. Satu amaran penting: proses yang gagal sambil menulis ke stderr boleh melontar pengecualian Error invoking external process, jadi bungkus panggilan yang mungkin gagal dalam try dan catch.
Suntikan arahan dan input pengguna
Peraturan paling penting dalam artikel ini: input pengguna tidak boleh masuk ke name mahupun arguments. Bayangkan arguments diisi terus daripada borang. Walaupun cfexecute tidak mentafsir aksara shell, bahayanya tetap wujud kerana program yang anda panggil mungkin menafsirkannya, atau anda mungkin membalut arahan itu dalam shell suatu hari nanti, dan pada ketika itu ; serta | menjadi hidup.
Pendekatan yang selamat ialah senarai putih: program dan set hujah ditentukan oleh pembangun, dan input pengguna hanya dipadankan dengan nilai yang dibenarkan, bukan disalurkan mentah:
<cfscript>
function inputSelamat(required string nilai) {
return reFind("[;|&`$]", nilai) == 0;
}
nilai = "laporan-tahunan";
serangan = "laporan; rm -rf /";
if (inputSelamat(serangan)) {
cfexecute(name="/bin/echo", arguments=[serangan], variable="hasil", timeout="10");
} else {
writeOutput("INPUT DITOLAK: " & serangan);
}
cfexecute(name="/bin/echo", arguments=["Laporan", nilai], variable="keluaran", timeout="10");
writeOutput(chr(10) & "HASIL: " & keluaran);
</cfscript>
Gunakan bentuk tatasusunan untuk arguments supaya setiap elemen kekal satu hujah dan tiada ruang untuk pemisahan yang kabur. Sebaik-baiknya peta input pengguna kepada nilai tetap dengan switch atau struct, contohnya pilihan "harian", "mingguan" dan "bulanan" masing-masing dipetakan kepada hujah yang ditulis tangan. Prinsip yang sama diajar dalam artikel keselamatan web XSS dan CSRF: anggap semua input tidak boleh dipercayai.
Jadual risiko dan kawalan
Kuasa cfexecute datang dengan lima risiko utama, dan setiap satu ada kawalannya:
| Risiko | Keterangan | Kawalan |
|---|---|---|
| Suntikan arahan | Input pengguna yang sampai ke name atau arguments boleh menjalankan arahan berbahaya, terutama bila dibalut shell | Jangan benarkan input pengguna ke name atau arguments; guna senarai putih dan bentuk tatasusunan |
| Masa jalan tanpa had | Proses yang tergantung memegang utas permintaan dan boleh memenatkan pelayan | Set timeout positif, terminateOnTimeout=true, bungkus dalam try dan catch |
| Penggunaan memori | Output yang sangat besar disimpan dalam ingatan dan membebankan pelayan | Alih output ke fail, jangan simpan semuanya dalam pemboleh ubah, hadkan bilangan proses serentak |
| Kebocoran output | Output atau ralat terpapar pada halaman atau tersimpan dalam log yang salah | Tentukan outputFile, variable dan errorVariable secara eksplisit, jangan biarkan output terpapar |
| Keistimewaan proses | Proses anak mewarisi keistimewaan pengguna pelayan, jadi ia boleh melakukan apa sahaja yang boleh dilakukan pelayan | Jangan jalankan Lucee sebagai root; jalankan sebagai pengguna tidak berkeistimewaan |
Had keistimewaan proses
cfexecute tidak mengurangkan keistimewaan. Proses anak berjalan sebagai pengguna sistem yang sama dengan proses Lucee atau Tomcat, jadi apa sahaja yang boleh dilakukan pengguna itu, proses anak juga boleh lakukannya. Kalau pelayan aplikasi anda berjalan sebagai root, satu kelemahan dalam aplikasi bermakna penyerang boleh menjalankan arahan sebagai root.
Peraturannya mudah: jangan sekali-kali jalankan Lucee sebagai root. Jalankan sebagai pengguna khusus dengan keizinan minimum, dan dalam persekitaran Docker gunakan pengguna bukan root di dalam bekas. Proses yang anda panggil juga akan berjalan sebagai pengguna bekas itu, jadi had keistimewaan dikenakan secara automatik. Baca deploy Lucee dengan Docker untuk cara menyediakan bekas yang tidak berjalan sebagai root.
cfexecute lawan cfthread
Pembaca yang biasa dengan CFML sering tertanya-tanya beza antara cfexecute dan cfthread, kerana kedua-duanya kelihatan seperti menjalankan kerja di luar permintaan biasa. Kedua-duanya berbeza pada dasarnya.
cfexecute melancarkan proses sistem luaran yang berasingan daripada JVM. Kerja itu berlaku sepenuhnya di luar proses Java, dan anda hanya boleh berkomunikasi melaluinya dengan output, stderr dan kod keluar. cfthread pula menjalankan blok CFML dalam utas JVM yang sama, jadi ia berkongsi skop aplikasi dan boleh memanggil fungsi CFML terus. Gunakan cfexecute untuk alat luaran, dan cfthread untuk kerja CFML yang mahu anda jalankan serentak tanpa menyekat permintaan. Perincian cfthread ada dalam artikel cfthread untuk konkurensi CFML.
Alternatif ProcessBuilder
Apabila anda memerlukan kawalan lebih halus, contohnya mengalirkan input ke proses atau membaca output secara progresif, anda boleh memanggil java.lang.ProcessBuilder terus melalui interop Java. Pendekatan ini memintas atribut tag dan memberi anda objek Process sebenar:
<cfscript>
pb = createObject("java", "java.lang.ProcessBuilder").init(["/bin/date", "+%Y-%m-%d"]);
proses = pb.start();
proses.waitFor();
kodKeluar = proses.exitValue();
pembaca = createObject("java", "java.io.BufferedReader")
.init(createObject("java", "java.io.InputStreamReader").init(proses.getInputStream()));
baris = pembaca.readLine();
writeOutput("EXITCODE=" & kodKeluar & " OUT=" & baris);
</cfscript>
ProcessBuilder lebih terkawal kerana anda memegang objek Process dan boleh memutuskan sendiri cara membaca aliran, menghantar input dan menunggu. Kosnya ialah kod lebih panjang dan anda bertanggungjawab menutup aliran sendiri. Gunakannya apabila cfexecute tidak cukup halus; untuk kebanyakan kes, cfexecute sudah memadai dan lebih mudah dibaca.
Kebanyakan tugas tidak memerlukan cfexecute
Sebelum menulis sebarang cfexecute, tanya diri anda sama ada kerja itu benar-benar memerlukan proses luaran. Banyak keperluan sudah ada laluan CFML atau Java yang lebih selamat. Untuk membaca dan menulis fail, guna cffile atau fungsi fileRead dan fileWrite. Untuk tugas berulang yang dijadualkan, guna cfschedule dan bukan gelung cfexecute dalam halaman.
Kalau kerja itu memanggil perkhidmatan lain, panggil HTTP API dengan cfhttp dan bukannya alat baris arahan. Kalau ia memerlukan logik yang kompleks, tulis dalam CFML atau pustaka Java secara terus. cfexecute ialah pilihan terakhir, bukan pilihan pertama, dan apabila ia benar-benar diperlukan, kekalkan senarai program yang dipanggil sekecil mungkin dan semua hujahnya tetap.
Soalan Lazim
Adakah cfexecute menjalankan arahan melalui shell?
Tidak. Ia menjalankan program secara terus tanpa shell, jadi ;, | dan & tidak ditafsirkan oleh cfexecute sendiri. Ia hanya menjadi bahaya apabila anda membalut arahan dalam /bin/sh -c atau cmd /c.
Bagaimana saya tahu proses berjaya atau gagal?
Periksa exitCode melalui atribut result. Nilai 0 bermakna berjaya dan bukan sifar bermakna gagal. /bin/false memulangkan 1 dan sesuai untuk menguji laluan kegagalan.
Apakah beza variable dengan outputFile?
variable menyimpan output standard ke dalam pemboleh ubah CFML, manakala outputFile menyalurkan output ke fail pada cakera. Gunakan variable untuk output pendek dan outputFile untuk output besar yang tidak mahu anda simpan dalam ingatan.
Bolehkah saya menghantar input pengguna terus ke arguments?
Jangan. Input pengguna tidak boleh masuk ke name atau arguments. Peta input kepada nilai tetap dengan senarai putih, dan hantar arguments sebagai tatasusunan.
Bila saya patut guna ProcessBuilder berbanding cfexecute?
Guna ProcessBuilder apabila anda perlu mengalirkan input ke proses, membaca output secara progresif, atau memegang objek Process untuk kawalan halus. Untuk kes mudah, cfexecute lebih pendek dan mencukupi.