Apakah berbahaya menjalankan gema tanpa tanda kutip?

11

Saya telah melihat beberapa topik yang serupa, tetapi mereka merujuk pada tidak mengutip variabel, yang saya tahu dapat menyebabkan hasil yang tidak diinginkan.

Saya melihat kode ini dan bertanya-tanya apakah akan mungkin untuk menyuntikkan sesuatu untuk dijalankan ketika baris kode ini dijalankan:

echo run after_bundle

Viktor Fonic
sumber
Saya mengalami ini ketika saya punya: target = "*** LIVE SERVER ***"; target gema: $ target; dan bajingan itu berkembang menjadi daftar folder ... 😬
Matt Parkins

Jawaban:

17

Untuk kasus khusus

echo run after_bundle

mengutip tidak diperlukan. Tidak ada kutipan yang diperlukan karena argumennya echoadalah string statis yang tidak mengandung ekspansi variabel atau penggantian perintah, dll. Mereka "hanya dua kata" (dan seperti yang ditunjukkan Stéphane , mereka juga dibuat dari set karakter portabel ).

"Bahaya" muncul ketika Anda berurusan dengan data variabel yang dapat diperluas atau ditafsirkan oleh shell. Dalam kasus seperti itu, harus diperhatikan bahwa shell melakukan hal yang benar dan hasilnya adalah apa yang dimaksudkan.

Dua pertanyaan berikut berisi informasi yang relevan tentang itu:


echokadang-kadang digunakan untuk "melindungi" perintah yang berpotensi berbahaya dalam jawaban di situs ini. Misalnya, saya dapat menunjukkan cara menghapus file atau memindahkan file ke tujuan baru menggunakan

echo rm "${name##*/}.txt"

atau

echo mv "$name" "/new_dir/$newname"

Ini akan menampilkan perintah pada terminal alih-alih menghapus atau mengganti nama file. Pengguna kemudian dapat memeriksa perintah, memutuskan bahwa mereka terlihat baik-baik saja, hapus echodan jalankan lagi.

Perintah Anda echo run after_bundledapat berupa instruksi kepada pengguna, atau mungkin kode "dikomentari" yang terlalu berbahaya untuk dijalankan tanpa mengetahui konsekuensinya.

Dengan menggunakan echoseperti ini, kita harus tahu apa yang dilakukan perintah yang dimodifikasi dan kita harus menjamin bahwa perintah yang dimodifikasi itu benar - benar aman (kemungkinan tidak akan jika itu berisi pengalihan, dan menggunakannya pada pipa tidak berfungsi, dll.)

Kusalananda
sumber
Menambahkan kutipan tidak cukup untuk mengetahui apa yang akan dilakukan shell - sama seperti Anda tidak dapat mengatakan bahwa echo rm "first file.txt" "second file.txt"dengan cara apa pun berbeda echo rm "first" "file.txt" "second" "file.txt", output dari keduanya sama. Jika Anda ingin menghasilkan perintah shell sebagai output, seseorang harus menggunakan printf '%q ' rm "first file.txt" "second file.txt"; echoatau sesuatu yang setara yang menghasilkan kembali kutipan sintaksis yang mengevaluasi ke argvberlalu.
Charles Duffy
@CharlesDuffy Saya sangat berharap tidak ada output debug-paste copy-paste dan menjalankannya di shell!
Kusalananda
1
Membuat perintah shell dan kemudian mem-pipe-nya ke shbukan pola yang tidak biasa, dan melihat orang bertanya "mengapa foobekerja ketika saya menjalankannya pada baris perintah, tetapi skrip ini yang memancarkan string yang tepat dengan echodi depan baris tidak? " terjadi sepanjang waktu di sini. Lebih penting lagi, hasil debugging tidak membantu jika menyembunyikan bug Anda - dan jika bug Anda terkait dengan mengutip, maka echotidak akan mengungkapkannya.
Charles Duffy
27

Hanya catatan tambahan di atas jawaban baik @ Kusalananda .

echo run after_bundle

baik-baik saja karena tidak ada karakter dalam 3 argumen¹ yang dilewatkan untuk echomengandung karakter yang khusus untuk shell.

Dan (poin tambahan yang ingin saya buat di sini) tidak ada sistem lokal di mana byte tersebut dapat diterjemahkan ke karakter yang khusus untuk shell.

Semua karakter itu dalam apa yang POSIX sebut sebagai set karakter portabel . Karakter-karakter tersebut harus ada dan dikodekan sama di semua set karakter pada sistem POSIX².

Sehingga baris perintah akan diinterpretasikan sama terlepas dari lokalnya.

Sekarang, jika kita mulai menggunakan karakter di luar set karakter portabel, itu adalah ide yang baik untuk mengutip mereka bahkan jika mereka tidak khusus untuk shell, karena di lokal lain, byte yang membentuknya dapat ditafsirkan sebagai karakter berbeda yang dapat menjadi khusus untuk shell. Perhatikan bahwa apakah Anda menggunakan echoatau perintah lain, masalahnya bukan pada echotetapi dengan bagaimana shell mengurai kodenya.

Misalnya dalam UTF-8:

echo voilà | iconv -f UTF-8 -t //TRANSLIT

Itu àdikodekan sebagai 0xc3 0xa0. Sekarang, jika Anda memiliki baris kode dalam skrip shell dan skrip shell dipanggil oleh pengguna yang menggunakan lokal yang charsetnya bukan UTF-8, dua byte itu bisa membuat karakter yang sangat berbeda.

Misalnya, dalam fr_FR.ISO8859-15lokal, lokal Prancis tipikal menggunakan charset byte tunggal standar yang mencakup bahasa Prancis (yang sama digunakan untuk sebagian besar bahasa Eropa barat termasuk bahasa Inggris), bahwa byte 0xc3 ditafsirkan sebagai Ãkarakter dan 0xa0 sebagai non- melanggar karakter ruang.

Dan pada beberapa sistem seperti NetBSD³, bahwa ruang tanpa putus dianggap sebagai karakter kosong ( isblank()di atasnya mengembalikan true, itu cocok dengan [[:blank:]]) dan kerang seperti bashkarenanya memperlakukannya sebagai pembatas token dalam sintaks mereka.

Itu berarti bahwa alih-alih menjalankan echodengan $'voil\xc3\xa0'sebagai argumen, mereka menjalankannya dengan $'voil\xc3'sebagai argumen, yang berarti tidak akan mencetak voilàdengan benar.

Ia mendapat lebih buruk dengan set karakter Cina seperti BIG5, BIG5-HKSCS, GB18030, GBK yang memiliki banyak karakter yang encoding berisi encoding sama dengan |, `, \(untuk nama yang terburuk) (juga bahwa SJIS menggelikan, alias Microsoft Kanji, kecuali bahwa itu ¥bukan \, tapi masih diperlakukan \oleh sebagian besar alat karena dikodekan sebagai 0x5c di sana).

Misalnya, jika di zh_CN.gb18030lokal Cina, Anda menulis skrip seperti:

echo  reboot

Skrip itu akan menampilkan 詜 rebootdalam lokal menggunakan GB18030 atau GBK, 唰 rebootdi lokal menggunakan BIG5 atau BIG5-HKSCS, tetapi di lokal C menggunakan ASCII atau lokal menggunakan ISO8859-15 atau UTF-8, akan menyebabkan rebootdijalankan karena pengkodean GB18030 dari adalah 0xd4 0x7c dan 0x7c adalah pengkodean |dalam ASCII sehingga kami akhirnya menjalankan:

 echo �| reboot

(yang mewakili byte 0xd4 diberikan di lokal). Contoh menggunakan yang kurang berbahaya unamealih-alih reboot:

$ echo $'echo \u8a5c uname' | iconv -t gb18030 > myscript
$ LC_ALL=zh_CN.gb18030 bash ./myscript | sed -n l
\324| uname$
$ LC_ALL=C bash ./myscript | sed -n l
Linux$

( unameDijalankan).

Jadi saran saya adalah mengutip semua string yang berisi karakter di luar set karakter portabel.

Namun perhatikan bahwa karena penyandian \dan `ditemukan dalam penyandian beberapa karakter tersebut, lebih baik untuk tidak menggunakan \atau "..."atau $'...'(di dalamnya mana `dan / atau \masih istimewa), tetapi '...'alih - alih mengutip karakter di luar rangkaian karakter portabel.

Saya tidak mengetahui adanya sistem yang memiliki lokal di mana charset memiliki karakter apa pun (selain 'tentu saja itu sendiri) yang penyandiannya berisi penyandian ', jadi itu '...'pasti yang paling aman.

Perhatikan bahwa beberapa shell juga mendukung $'\uXXXX'notasi untuk mengekspresikan karakter berdasarkan titik kode Unicode mereka. Dalam cangkang suka zshdan bash, karakter dimasukkan disandikan dalam charset lokal (meskipun dapat menyebabkan perilaku yang tidak terduga jika charset itu tidak memiliki karakter itu). Itu memungkinkan Anda menghindari memasukkan karakter non-ASCII dalam kode shell Anda.

Di atas:

echo 'voilà' | iconv -f UTF-8 -t //TRANSLIT
echo '詜 reboot'

Atau:

echo $'voil\u00e0'
echo $'\u8a5c reboot'

(dengan peringatan itu bisa mematahkan skrip ketika dijalankan di lokal yang tidak memiliki karakter tersebut).

Atau lebih baik, karena \juga khusus untuk echo(atau setidaknya beberapa echo implementasi, setidaknya yang sesuai Unix):

printf '%s\n' 'voilà' | iconv -f UTF-8 -t //TRANSLIT
printf '%s\n' '詜 reboot'

(catatan yang \juga khusus dalam argumen pertama printf, jadi karakter non-ASCII juga lebih baik dihindari di sana jika mereka mungkin mengandung pengkodean \).

Perhatikan bahwa Anda juga bisa melakukan:

'echo' 'voilà' | 'iconv' '-f' 'UTF-8' '-t' '//TRANSLIT'

(Itu akan berlebihan tetapi bisa memberi Anda ketenangan pikiran jika Anda tidak yakin karakter mana yang ada di set karakter portabel)

Juga pastikan untuk tidak pernah menggunakan `...`bentuk substitusi perintah kuno (yang memperkenalkan pemrosesan backslash tingkat lain), tetapi gunakan $(...)sebagai gantinya.


¹ teknis, echojuga diberikan sebagai argumen ke echoutilitas (untuk menceritakannya bagaimana itu dipanggil), itu argv[0]dan argcadalah 3, meskipun dalam kebanyakan kerang saat ini echoadalah builtin, sehingga exec()dari /bin/echofile dengan daftar 3 argumen disimulasikan oleh kulit. Juga umum untuk mempertimbangkan daftar argumen sebagai mulai dengan yang kedua ( argv[1]untuk argv[argc - 1]) karena itulah yang sebagian besar ditindaklanjuti oleh perintah.

² pengecualian untuk yang menjadi menggelikan ja_JP.SJISlokal sistem FreeBSD yang charset tidak memiliki \atau ~karakter!

³ perhatikan bahwa walaupun banyak sistem (FreeBSD, Solaris, bukan yang GNU) menganggap U + 00A0 sebagai [[:blank:]]di dalam UTF-8 lokal, sedikit yang dilakukan di lokal lain seperti yang menggunakan ISO8859-15, mungkin untuk menghindari masalah seperti ini.

Stéphane Chazelas
sumber
Dalam paragraf pertama Anda, Anda memberi tahu kami "... dari karakter dalam 3 argumen yang diteruskan ke echo...", saya hanya menghitung 2 argumen yang diteruskan ke perintah echo, argumen yang dapat saya hitung adalah rundan after_bundle, ingin menjelaskan bagaimana Anda dihitung dan sampai 3 argumen?
Ferrybig
1
@ ViktorFonic, lihat sunting tentang jumlah argumen (dan bahwa masalah utama bukan dengan echo). Lihat (exec -a foo /bin/echo --help)pada sistem GNU dan dengan shell GNU untuk cara meneruskan argumen pertama yang sewenang-wenang ke /bin/echoutilitas.
Stéphane Chazelas
@Ferrybig Lihat hasil edit Stephane, catatan kaki 1. Argumen untuk perintah dalam gaya C yang biasa adalah array argumen, dengan argv [0] adalah nama yang dapat dieksekusi itu sendiri. Agak mirip dengan $0dan parameter posisi dalam shell.
Sergiy Kolodyazhnyy
Ada 373 penyandian iconvyang ESCdikonversi menjadi '. Coba (sebagai contoh):printf '\x1b'|iconv -f utf8 -t IBM-937|xxd
NotAnUnixNazi
Ada 173 encoding di mana beberapa codepoint (selain ESC) dikonversi menjadi a '. Coba printf '\u2804' | iconv -f utf8 -t BRF | xxd. Ada pengkodean di mana ada banyak codepoint yang menjadi '. Sekitar 8695 codepoint di UCS-4 menjadi '. Coba printf '\U627' | iconv -cf utf-8 -t UCS-4. Beberapa (37) pengkodean mengkonversi karakter 0x127 ke a '. Cobaprintf '\U127' | iconv -cf utf8 -t UCS2 |xxd
NotAnUnixNazi