Bagaimana saya bisa memastikan suatu variabel hanya berisi nama file yang valid?

8

Dengan skrip di bawah ini, bagaimana saya bisa memastikan bahwa argumen tersebut hanya berisi nama file yang valid di dalam /home/charlesingalls/dan bukan path ( ../home/carolineingalls/) atau wildcard, dll?

Saya hanya ingin skrip dapat menghapus satu file dari direktori hard-coded yang diberikan. Skrip ini akan berjalan sebagai pengguna istimewa.

#!/bin/bash

rm -f /home/charlesingalls/"$1"
Aaron Cicali
sumber
2
Jika Anda tidak ingin mendukung "foo / bar", periksa saja apakah itu tidak mengandung /. Wildcard tidak ditafsirkan dalam kutipan.
Random832
3
Jika Anda menghapus file , jangan gunakan -rdengan rm. rm -radalah untuk penghapusan secara rekursif dari suatu direktori dan semua file dan direktori di bawahnya. Ini hanya berguna saat menghapus direktori. Lebih umum, jangan pemujaan kargo. yaitu jangan hanya menyalin hal-hal yang terlihat berguna ke dalam baris perintah atau skrip Anda tanpa memahami apa yang mereka lakukan atau bagaimana mereka bekerja Dewa pesawat yang membawa kargo ajaib bisa marah dan menghapus semua file Anda.
Kasus
Poin bagus mengenai flag -r - Saya mengerti penggunaannya, hanya saja tidak berpikir jernih.
Aaron Cicali

Jawaban:

7

Jika Anda hanya ingin menghapus file di /home/charlesingalls(dan bukan file di subdirektori) maka mudah: cukup periksa bahwa argumennya tidak mengandung a /.

case "$1" in
  */*) echo 1>&2 "Refusing to remove a file in another directory"; exit 2;;
  *) rm -f /home/charlesingalls/"$1";;
esac

Ini berjalan rmbahkan jika argumennya adalah .atau ..atau kosong, tetapi dalam kasus itu rmakan gagal menghapus direktori.

Wildcard tidak relevan di sini karena tidak ada ekspansi wildcard yang dilakukan.

Ini aman bahkan di hadapan tautan simbolik: jika file tersebut adalah tautan simbolik, symlink (yang ada di dalam /home/charlesingalls) akan dihapus, dan target tautan itu tidak terpengaruh.

Perhatikan bahwa ini mengasumsikan bahwa /home/charlesingallstidak dapat dipindahkan atau diubah. Itu seharusnya ok jika direktori tersebut dikodekan dalam skrip, tetapi jika itu ditentukan dari variabel maka penentuan mungkin tidak lagi valid pada saat rmperintah dijalankan.

Berdasarkan informasi tambahan bahwa argumen tersebut adalah nama host virtual, Anda harus melakukan daftar putih daripada daftar hitam: periksa bahwa nama tersebut adalah nama host virtual yang masuk akal, bukan hanya melarang garis miring. Saya memeriksa apakah namanya dimulai dengan huruf kecil atau digit dan tidak mengandung karakter selain huruf kecil, digit, titik dan garis.

LC_CTYPE=C LC_COLLATE=C
case "$1" in
  *[!-.0-9a-z]*|[!0-9a-z]*) echo >&2 "Invalid host name"; exit 2;;
  *) rm -f /home/charlesingalls/"$1";;
esac
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Dalam hal ini, file adalah file konfigurasi server web yang telah dihasilkan oleh proses sebelumnya. Mereka semua memiliki tingkat kepentingan yang sama (masing-masing merupakan host virtual). Namun, direktori tempat mereka berada berbatasan dengan direktori serupa, dan mereka semua ada di bawah direktori yang dimiliki oleh server web. Skrip khusus ini dimaksudkan untuk memungkinkan penghapusan konfigurasi host virtual di bawah direktori tertentu saja.
Aaron Cicali
apakah pendekatan ini masuk akal bagi Anda asalkan use case? Saya menghargai pengalaman Anda dan akan mengakui pasti ada saatnya saya "membawa bazoka ke tembak-menembak" untuk mengotomatisasi sesuatu di linux.
Aaron Cicali
@AaronCicali Mengapa skrip dapat menghapus konfigurasi host mana pun dan bukan hanya milik entitas yang membuat permintaan asli? Mengapa nama host virtual tidak divalidasi terlebih dahulu (maka tidak akan mengandung karakter khusus)?
Gilles 'SANGAT berhenti menjadi jahat'
Entitas yang membuat permintaan asli adalah GUI yang memang memiliki izin untuk menghapus semua host virtual di folder itu. Nama host virtual divalidasi terlebih dahulu. Itu berasal dari daftar host virtual yang dibuat sebelumnya (nama domain). Saya percaya ini adalah tugas skrip ini untuk memastikan bahwa skrip ini seaman mungkin tanpa mengandalkan keamanan bagian lain dari aplikasi. Yaitu, itu hanya dapat menghapus file dari dalam direktori spesifik ini. Ini juga perlu melakukan beberapa pekerjaan pembersihan tambahan.
Aaron Cicali
@AaronCicali Oke, sebagai tambahan kewarasan, ini masuk akal. Dalam hal ini Anda harus memasukkan daftar putih: hanya menerima nama yang terlihat seperti nama host yang masuk akal. Jika Anda tidak mengizinkan subdomain, Anda mungkin akan melarang.
Gilles 'SO- stop being evil'
10

Jawaban ini mengasumsikan bahwa $1diizinkan untuk memasukkan subdirektori. Jika Anda tertarik pada kasus yang lebih sederhana di mana $1seharusnya menjadi nama direktori sederhana, maka lihat salah satu jawaban lainnya.


Wildcard tidak diperluas ketika dalam tanda kutip ganda. Karena $1dalam tanda kutip ganda, wildcard tidak menjadi masalah.

Keduanya ../dan symlink dapat mengaburkan lokasi sebenarnya dari suatu file. Yang ditunjukkan di bawah ini adalah tes untuk menentukan apakah file tersebut benar-benar, tidak hanya tampaknya, di bawah jalur yang kita inginkan.

Sistem yang lebih baru: menggunakan realpath

Adapun untuk mengetahui apakah file tersebut benar-benar jika file tersebut benar-benar di bawah /home/charlesingalls/atau tidak, Anda dapat menggunakan realpath:

realpath --relative-base=/home/charlesingalls/ "/home/charlesingalls/$1"  | grep -q '^/' && exit 1

Di atas berjalan exit 1jika file yang ditentukan oleh $1mana saja selain di bawah direktori /home/charlesingalls/. realpathmengkanonik seluruh jalur, menghilangkan symlink dan ../.

realpath adalah bagian dari GNU coreutils dan harus tersedia di sistem Linux apa pun.

realpathmembutuhkan GNU coreutils 8.15 (Jan 2012) atau lebih baik .

Contohnya

Untuk mendemonstrasikan bagaimana realpath mengikuti ../untuk menentukan lokasi sebenarnya dari suatu file (misalnya, -qopsi untuk grep dihilangkan sehingga output aktual dari grep terlihat):

$ touch /tmp/test
$ realpath --relative-base=$HOME "$HOME/../../tmp/test" | grep '^/' && echo FAIL
/tmp/test
FAIL

Untuk mendemonstrasikan cara mengikuti symlinks:

$ ln -s /tmp/test ~/test
$ realpath --relative-base=$HOME "$HOME/test" | grep '^/' && echo FAIL
/tmp/test
FAIL

Sistem yang lebih lama: menggunakan readlink -e

readlinkjuga mampu mengkononisasi jalur, mengikuti kedua symlinks dan ../:

readlink -e "$HOME/test" | grep -q "^$HOME" || exit 1

Menggunakan contoh file yang sama:

$ readlink -e "$HOME/../../tmp/test" | grep "$HOME" || echo FAIL
FAIL
$ readlink -e "$HOME/test" | grep "^$HOME" || echo FAIL
FAIL

Selain tersedia di sistem GNU yang lebih lama, versi readlinktersedia di BSD.

John1024
sumber
Ubuntu 14,04 saya coreutilstidak memilikirealpath
heemayl
1
Ini tampaknya lebih dari apa yang saya cari, tetapi sayangnya server Centos 6.5 saya tidak memiliki realpath. Saya tidak dalam posisi untuk menginstalnya. Googling muncul menyebutkan "readlink -f" superpeding realpath, tapi saya belum membuatnya bekerja.
Aaron Cicali
1
Subtitle menyebutkan -f("semua kecuali komponen terakhir harus ada") dan contoh-contohnya menggunakan -e("semua komponen harus ada"), yang agak membingungkan.
isanae
2
@AaronCicali Ini jelas bukan jawaban yang Anda butuhkan, karena itu salah besar. Anda tidak boleh menyelesaikan tautan simbolik . Target tautan simbolis dapat berubah antara waktu Anda memeriksanya dan waktu Anda menggunakannya. (Ini adalah kategori kesalahan desain yang terkenal ). Selain itu, tidak masuk akal untuk menyelesaikan symlink karena rmbertindak berdasarkan argumen itu sendiri, bukan targetnya.
Gilles 'SANGAT berhenti menjadi jahat'
1
Petunjuk: Gunakan grep -quntuk menjaga agar tidak grepmenghasilkan garis yang cocok. Anda masih mendapatkan status keluar begitu &&dan ||masih berfungsi persis seperti dulu.
CVn
2

Jika Anda ingin melarang path sepenuhnya, cara paling sederhana adalah dengan menguji apakah variabel berisi slash ( /). Dalam bash:

if [[ "$1" = */* ]] ; then...

Ini akan memblokir semua jalur, termasuk foo/bar. Anda bisa menguji .., tetapi itu akan meninggalkan kemungkinan symlink menunjuk ke direktori di luar jalur target.

Jika Anda hanya ingin mengizinkan penghapusan satu file, saya tidak berpikir Anda harus menggunakan rm -r.


Juga, tergantung pada apa yang Anda lakukan, Anda bisa menggunakan izin file sistem untuk hanya mengizinkan menghapus file yang bisa dihapus sendiri oleh pengguna. Sesuatu seperti ini:

su charlesingalls -c "rm /home/charlesingalls/'$1'"

Meskipun seperti yang dikomentari @Gilles, ini memiliki masalah kutipan: ia akan gagal jika $1berisi satu kutipan, jadi variabel harus diuji terlebih dahulu (dengan misalnya if [[ "$1" = *\'* ]] ; then fail...atau lebih dengan memasukkan daftar karakter yang masuk akal), atau nama file dilewatkan variabel lingkungan dengan mis

file="$1" su charlesingalls -c 'rm "/home/charlesingalls/$file"'
ilkkachu
sumber
Poin bagus tentang flag -r, yang seharusnya tidak ada di sana. Menghapus sekarang ...
Aaron Cicali
Perhatikan bahwa superintah Anda rusak karena penawaran salah. Anda menjalankan perintah dengan argumen yang diinterpolasi sebagai cuplikan shell. Misal jika argumennya $(touch foo)maka kode Anda berjalan touch foo.
Gilles 'SANGAT berhenti menjadi jahat'
@Gilles, sial, aku tahu pasti ada yang salah. kutip bersarang bukan teman favorit saya. Saya pikir versi saat ini berfungsi lebih baik, tanda kutip ganda akan mengevaluasi variabel di kulit terluar, dan tanda kutip tunggal membuatnya tidak dievaluasi di kulit dalam. Tidak ada garansi.
ilkkachu
@ilkkachu Versi itu gagal jika argumen berisi satu kutipan. (Tapi hanya dalam kasus itu, yang membuatnya jauh lebih mudah untuk divalidasi daripada ketika menggunakan tanda kutip ganda.) Tidak mungkin untuk secara langsung menginterpolasi string arbitrer. Anda perlu memijat string (mis. Ganti semua 'dengan '\'') atau meneruskannya melalui saluran lain seperti variabel lingkungan (yang akan saya lakukan di sini file_to_remove="$1" su -c 'rm "/home/charlesingalls/$file_to_remove"':). Ternyata nama file itu seharusnya nama host virtual, jadi menolak semua karakter khusus juga akan baik-baik saja di sini.
Gilles 'SANGAT berhenti menjadi jahat'