Apakah mungkin menggunakan `find -exec sh -c` dengan aman?

29

Saya mencoba menggunakan findke echo 0dalam beberapa file, tetapi ternyata ini hanya berfungsi dengan sh -c:

find /proc/sys/net/ipv6 -name accept_ra -exec sh -c 'echo 0 > {}' \;

Tetapi menggunakan sh -cdengan find -execmembuat saya merasa sangat tidak nyaman karena saya curiga mengutip masalah. Saya sedikit mengotak-atik itu dan tampaknya kecurigaan saya dibenarkan:

  • Setup pengujian saya:

    martin@dogmeat ~ % cd findtest 
    martin@dogmeat ~/findtest % echo one > file\ with\ spaces
    martin@dogmeat ~/findtest % echo two > file\ with\ \'single\ quotes\'
    martin@dogmeat ~/findtest % echo three > file\ with\ \"double\ quotes\"
    martin@dogmeat ~/findtest % ll
    insgesamt 12K
    -rw-rw-r-- 1 martin martin 6 Sep 17 12:01 file with "double quotes"
    -rw-rw-r-- 1 martin martin 4 Sep 17 12:01 file with 'single quotes'
    -rw-rw-r-- 1 martin martin 4 Sep 17 12:01 file with spaces
  • Penggunaan find -exectanpa sh -ctampaknya berfungsi tanpa masalah - tidak perlu mengutip di sini:

    martin@dogmeat ~ % find findtest -type f -exec cat {} \;
    one
    two
    three
  • Tetapi ketika saya menggunakan sh -c {}tampaknya membutuhkan semacam kutipan:

    martin@dogmeat ~ % LANG=C find findtest -type f -exec sh -c 'cat {}' \;
    cat: findtest/file: No such file or directory
    cat: with: No such file or directory
    cat: spaces: No such file or directory
    cat: findtest/file: No such file or directory
    cat: with: No such file or directory
    cat: single quotes: No such file or directory
    cat: findtest/file: No such file or directory
    cat: with: No such file or directory
    cat: double quotes: No such file or directory
  • Kutipan ganda berfungsi selama tidak ada nama file yang mengandung tanda kutip ganda:

    martin@dogmeat ~ % LANG=C find findtest -type f -exec sh -c 'cat "{}"' \;
    one
    two
    cat: findtest/file with double: No such file or directory
    cat: quotes: No such file or directory
  • Kutipan tunggal berfungsi selama tidak ada nama file yang mengandung tanda kutip tunggal:

    martin@dogmeat ~ % LANG=C find findtest -type f -exec sh -c "cat '{}'" \;
    one
    cat: findtest/file with single: No such file or directory
    cat: quotes: No such file or directory
    three

Saya belum menemukan solusi yang berfungsi dalam semua kasus. Apakah ada sesuatu yang saya abaikan, atau gunakan sh -cpada find -execdasarnya tidak aman?

Martin von Wittich
sumber

Jawaban:

40

Jangan pernah menyematkan {}kode shell! Itu menciptakan kerentanan injeksi perintah. Perhatikan bahwa untuk cat "{}", itu tidak hanya tentang "karakter, \, `, $juga masalah (menganggap misalnya file bernama ./$(reboot)/accept_ra).

(Ngomong-ngomong, beberapa findimplementasi tidak akan membiarkan Anda melakukan itu, dan POSIX membiarkan perilaku tidak ditentukan ketika {}tidak sendirian dalam argumen untuk find)

Di sini, Anda ingin meneruskan nama file sebagai argumen terpisah sh(bukan dalam argumen kode ), dan shskrip inline ( argumen kode ) untuk merujuknya menggunakan parameter posisi:

find . -name accept_ra -exec sh -c 'echo 0 > "$1"' sh {} \;

Atau, untuk menghindari menjalankan satu shper file:

find . -name accept_ra -exec sh -c 'for file do
  echo 0 > "$file"; done' sh {} +

Hal yang sama berlaku untuk xargs -I{}atau zsh's zargs -I{}. Jangan menulis:

<list.txt xargs -I {} sh -c 'cmd> {}'

Yang akan menjadi kerentanan injeksi perintah dengan cara yang sama seperti di findatas, tetapi:

<list.txt xargs sh -c 'for file do cmd > "$file"; done' sh

Yang juga memiliki manfaat menghindari menjalankan satu shper file dan kesalahan ketika list.txttidak mengandung file apa pun.

Dengan zsh's zargs, Anda mungkin ingin menggunakan fungsi daripada memohon sh -c:

do-it() cmd > $1
zargs ./*.txt -- do-it

Perhatikan bahwa dalam semua contoh di atas, yang kedua di shatas masuk ke skrip inline $0. Anda harus menggunakan sesuatu yang relevan di sana (seperti shatau find-sh), tidak hal-hal seperti _, -, --atau string kosong, sebagai nilai dalam $0digunakan untuk pesan kesalahan shell:

$ find . -name accept_ra -exec sh -c 'echo 0 > "$1"' inline-sh {} \;
inline-sh: ./accept_ra: Permission denied

GNU parallelbekerja secara berbeda. Dengan itu, Anda tidak ingin menggunakan sh -ckarena parallelsudah menjalankan shell dan mencoba untuk mengganti {}dengan argumen yang dikutip dalam sintaksis yang tepat untuk shell .

<list.txt PARALLEL_SHELL=sh parallel 'cmd > {}'
Stéphane Chazelas
sumber
thats di kedua shtampaknya menjadi semacam placeholder, ia bekerja juga jika diganti dengan _misalnya - sangat berguna jika Anda ingin menyebutnya internal bash: find /tmp -name 'fil*' -exec bash -c 'printf "%q\n" "$1"' _ {} \;. Tetapi apakah ada yang tahu di mana ini didokumentasikan?
Florian Fida
1
@FlorianFida Argumen pertama ke shell menjadi $0(biasanya nama shell. Anda harus melewatkannya dalam skenario ini sehingga tidak memakan salah satu argumen posisi normal Anda. Dokumentasi untuk -cmenyebutkan ini.
Etan Reisner
2
@EtanReisner in-ulm.de/
Gilles 'SO- stop being evil'
1
@ php, itu tidak di argv[0]sini, itu hanya $0naskahnya. Halaman Sven tidak akurat di sini, rtidak akan membuat shell masuk ke mode terbatas sejauh yang bisa dikatakan dan zshtidak akan mengubah mode berdasarkan $0. (exec -a rksh ksh -c 'cd /')akan menjalankan terbatas ksh, tetapi tidak ksh -c 'cd /' rksh).
Stéphane Chazelas
1
Stéphane, jika Anda tidak keberatan, apakah ada jawaban Anda yang menjelaskan "mengapa tidak menanamkan {} dalam kode shell"? Saya telah memeriksa semua jawaban Anda di bawah menemukan tetapi tidak dapat menemukan meskipun saya dapat bersumpah saya melihat beberapa hal yang telah Anda tulis tentang subjek ini tetapi tidak dapat mengingat apakah itu adalah jawaban, komentar dalam obrolan atau di situs lain seperti compunix ... Jika / ketika Anda punya waktu, maukah Anda memperluas bagian "Never embed {}" atau apakah Anda pikir kami harus memiliki Q khusus misalnya "Implikasi keamanan penggunaan finddengan -exec sh -cdan penyematan {}dalam kode shell" ?
don_crissti