Bagaimana cara saya secara eksplisit dan aman memaksakan penggunaan perintah bawaan di bash

19

Ada pertanyaan serupa yang berhubungan dengan skenario 'pembungkus', di mana Anda ingin mengganti misalnya cddengan perintah yang memanggil builtin cd.

Namun, mengingat shellshock et al dan mengetahui bahwa bash mengimpor fungsi dari lingkungan, saya telah melakukan beberapa tes dan saya tidak dapat menemukan cara untuk memanggil buildin dengan aman cddari skrip saya.

Pertimbangkan ini

cd() { echo "muahaha"; }
export -f cd

Setiap skrip yang dipanggil dalam lingkungan ini menggunakan cdakan rusak (pertimbangkan efek dari sesuatu seperti cd dir && rm -rf .).

Ada perintah untuk memeriksa jenis perintah (mudah dipanggil type) dan perintah untuk mengeksekusi versi builtin daripada fungsi ( builtindan command). Tapi, lihat dan lihat, ini bisa diganti menggunakan fungsi juga

builtin() { "$@"; }
command() { "$@"; }
type() { echo "$1 is a shell builtin"; }

Akan menghasilkan yang berikut:

$ type cd
cd is a shell builtin
$ cd x
muahaha
$ builtin cd x
muahaha
$ command cd x
muahaha

Apakah ada cara untuk memaksa bash secara aman untuk menggunakan perintah builtin, atau setidaknya mendeteksi bahwa suatu perintah bukanlah builtin, tanpa membersihkan seluruh lingkungan?

Saya menyadari jika seseorang mengendalikan lingkungan Anda, Anda mungkin masih bisa mengacaukannya, tetapi setidaknya untuk alias Anda punya opsi untuk tidak memanggil alias dengan menyisipkan \sebelumnya.

Falstro
sumber
Anda dapat menjalankan skrip Anda menggunakan envperintah sebelumnya, seperti ini:env -i <SCRIPT.sh>
Arkadiusz Drabczyk
Hanya jika envtidak didefinisikan ulang sebagai suatu fungsi juga. Ini menakutkan. Saya pertama kali berpikir bahwa karakter khusus akan membantu - memanggil dengan path lengkap yang mencakup /, menggunakan .sumber, dan sebagainya. Tapi itu juga bisa digunakan untuk nama fungsi! Anda dapat mendefinisikan kembali fungsi yang Anda inginkan, tetapi sulit untuk kembali memanggil perintah asli.
orion
1
Benar, tetapi jika Anda akan menjalankan skrip apa pun pada mesin yang dikompromikan, Anda tetap kacau. Atau, tulis skrip Anda #/bin/shjika ini bukan shell interaktif bawaan.
Arkadiusz Drabczyk

Jawaban:

16

Olivier D hampir benar, tetapi Anda harus mengatur POSIXLY_CORRECT=1sebelum menjalankan unset. POSIX memiliki gagasan tentang Built-in Khusus , dan bash mendukung ini . unsetadalah salah satu builtin tersebut. Mencari SPECIAL_BUILTINdi builtins/*.cdalam sumber bash untuk daftar, itu termasuk set, unset, export, evaldan source.

$ unset() { echo muahaha-unset; }
$ unset unset
muahaha-unset
$ POSIXLY_CORRECT=1
$ unset unset

Nakal yang unsetsekarang telah dihapus dari lingkungan, jika Anda unset command, type, builtinmaka Anda harus bisa melanjutkan, tetapi unset POSIXLY_CORRECTjika Anda mengandalkan pada perilaku non-POSIX atau fitur pesta canggih.

Ini tidak membahas alias, jadi Anda harus menggunakannya \unsetuntuk memastikan itu bekerja di shell interaktif (atau selalu, dalam kasus expand_aliasesberlaku).

Untuk paranoid, ini harus memperbaiki semuanya, saya pikir:

POSIXLY_CORRECT=1
\unset -f help read unset
\unset POSIXLY_CORRECT
re='^([a-z:.\[]+):' # =~ is troublesome to escape
while \read cmd; do 
    [[ "$cmd" =~ $re ]] && \unset -f ${BASH_REMATCH[1]}; 
done < <( \help -s "*" )

( while, do, doneDan [[dicadangkan kata-kata dan tidak perlu tindakan pencegahan.) Catatan kami menggunakan unset -funtuk memastikan fungsi unset, meskipun variabel dan fungsi berbagi ruang nama yang sama itu mungkin bagi keduanya untuk eksis secara bersamaan (terima kasih kepada Etan Reisner) dalam hal unset-ing dua kali juga akan melakukan trik. Anda dapat menandai fungsi readonly, bash tidak mencegah Anda membatalkan fungsi readonly hingga dan termasuk bash-4.2, bash-4.3 memang mencegah Anda tetapi masih menghormati builtin khusus ketika POSIXLY_CORRECTdiatur.

Readonly POSIXLY_CORRECTbukan masalah nyata, ini bukan boolean atau flag kehadirannya memungkinkan mode POSIX, jadi jika itu ada sebagai readonly Anda dapat mengandalkan fitur POSIX, bahkan jika nilainya kosong atau 0. Anda hanya perlu hapus fungsi bermasalah dengan cara yang berbeda dari yang di atas, mungkin dengan beberapa cut-and-paste:

\help -s "*" | while IFS=": " read cmd junk; do echo \\unset -f $cmd; done

(dan abaikan kesalahan apa pun) atau terlibat dalam beberapa scriptobatics lainnya .


Catatan lain:

  • functionadalah kata yang dilindungi undang-undang, ini bisa alias tetapi tidak diganti dengan fungsi. (Aliasing functionagak sulit karena \function tidak dapat diterima sebagai cara untuk melewatinya)
  • [[, ]]adalah kata-kata yang dicadangkan, mereka dapat alias (yang akan diabaikan) tetapi tidak diganti dengan fungsi (meskipun fungsi dapat dinamai demikian)
  • (( bukan nama yang valid untuk suatu fungsi, atau alias
mr.spuratic
sumber
Menarik, terima kasih! Sepertinya aneh bagi saya bahwa bash akan default untuk tidak melindungi unsetet al dari ditimpa.
falstro
Ini perlu -fargumen untuk unsetbukan? Kalau tidak, jika kedua variabel dan fungsi bernama sama dengan builtin didefinisikan maka unsetakan memilih variabel untuk tidak disetel terlebih dahulu. Ini juga gagal jika salah satu fungsi yang readonlytidak jelas diatur bukan? (Meskipun itu dapat dideteksi dan dapat membuat kesalahan fatal.) Juga ini merindukan [builtin sepertinya.
Etan Reisner
1
Saya tidak berpikir \unset -f unsetmasuk akal, mengingat itu akan dilakukan dalam loop baca. Jika unsetmerupakan fungsi yang \unsetbiasanya Anda selesaikan alih-alih builtin, tetapi Anda dapat menggunakannya \unsetdengan aman selama mode POSIX berlaku. Secara eksplisit menyebut \unset -ftentang [, .dan :mungkin ide yang baik juga, karena tidak termasuk regex Anda mereka. Saya juga akan menambahkan -fke \unsetdalam loop, dan akan \unset POSIXLY_CORRECTsetelah loop, daripada sebelumnya. \unalias -a(Setelah \unset -f unalias) juga memungkinkan seseorang untuk dengan aman melepaskan pelarian pada perintah berikutnya.
Adrian Günter
1
@ AdrianGünter Coba:, [[ ${POSIXLY_CORRECT?non-POSIX mode shell is untrusted}x ]]ini akan menyebabkan skrip non-interaktif keluar dengan kesalahan yang ditunjukkan jika variabel tidak disetel, atau melanjutkan jika disetel (kosong, atau nilai apa pun).
mr.spuratic
1
"Anda harus menggunakan \unset" ... Harus dicatat bahwa mengutip karakter apa pun akan berfungsi untuk menghindari ekspansi alias, bukan hanya yang pertama. un"se"takan bekerja dengan baik, abeit kurang mudah dibaca. "Kata pertama dari setiap perintah sederhana, jika tidak dikutip, diperiksa untuk melihat apakah ada alias." - Bash Ref
Robin A. Meade
6

Saya menyadari jika seseorang mengendalikan lingkungan Anda, Anda mungkin memang sedang kacau

Ya itu. Jika Anda menjalankan skrip di lingkungan yang tidak dikenal, segala hal bisa salah, mulai dengan LD_PRELOADmenyebabkan proses shell mengeksekusi kode arbitrer sebelum bahkan membaca skrip Anda. Mencoba melindungi dari lingkungan yang bermusuhan dari dalam skrip itu sia-sia.

Sudo telah membersihkan lingkungan dengan menghapus apa pun yang tampak seperti definisi fungsi bash selama lebih dari satu dekade. Sejak Shellshock , lingkungan lain yang menjalankan skrip shell di lingkungan yang tidak sepenuhnya tepercaya telah mengikuti.

Anda tidak dapat menjalankan skrip dengan aman di lingkungan yang telah ditetapkan oleh entitas yang tidak dipercaya. Jadi mengkhawatirkan definisi fungsi tidak produktif. Sanitasi lingkungan Anda, dan dengan melakukannya, variabel yang akan ditafsirkan oleh bash sebagai definisi fungsi.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
1
Saya sepenuhnya tidak setuju dengan ini. Keamanan bukanlah proposisi biner yang berpusat di sekitar "disanitasi" atau "tidak disanitasi". Ini tentang menghilangkan peluang untuk eksploitasi. Dan sayangnya, kompleksitas sistem modern berarti ada banyak peluang eksploitasi yang perlu dikunci dengan benar. Banyak peluang eksploitasi berpusat di sekitar serangan hulu yang tampaknya tidak berbahaya, seperti mengubah variabel PATH, mendefinisikan ulang fungsi bawaan, dll. Menawarkan satu orang atau program peluang untuk melakukan keduanya, dan seluruh sistem Anda rentan.
Dejay Clayton
@ DjayClayton Saya sepenuhnya setuju dengan komentar Anda, kecuali untuk kalimat pertama, karena saya tidak melihat bagaimana hal itu bertentangan dengan jawaban saya. Menghapus peluang eksploitasi membantu, tetapi tidak menghapus hanya setengahnya di mana perubahan sepele dalam serangan memungkinkannya untuk tetap bekerja.
Gilles 'SANGAT berhenti menjadi jahat'
Maksud saya adalah Anda tidak bisa hanya "membersihkan lingkungan Anda" dan kemudian berhenti mengkhawatirkan berbagai vektor serangan. Terkadang membayar untuk "melindungi terhadap lingkungan yang bermusuhan dari dalam skrip." Bahkan jika Anda memecahkan masalah "definisi fungsi", Anda masih harus khawatir tentang hal-hal seperti memiliki PATH di mana seseorang dapat menempatkan skrip khusus bernama "cat" atau "ls" ke direktori, dan kemudian skrip apa pun yang memanggil "cat" "atau" ls "bukannya" / bin / cat "dan" / bin / ls "secara efektif menjalankan exploit.
Dejay Clayton
@ DjayClayton Jelas membersihkan lingkungan tidak membantu dengan vektor serangan yang tidak terkait dengan lingkungan. (Misalnya skrip yang memanggil /bin/catchroot bisa memanggil apa saja.) Membersihkan lingkungan memang membuat Anda waras PATH(tentu saja dengan asumsi Anda melakukannya dengan benar). Saya masih tidak mengerti maksud Anda.
Gilles 'SANGAT berhenti menjadi jahat'
Mempertimbangkan bahwa PATH adalah salah satu peluang terbesar untuk eksploitasi, dan bahwa banyak praktik PATH yang dipertanyakan didokumentasikan sebagai anjuran bahkan di blog keamanan, dan juga mempertimbangkan bahwa kadang-kadang aplikasi yang terinstal mengatur ulang PATH untuk memastikan bahwa aplikasi seperti itu berfungsi, saya tidak dapat melihat bagaimana pengkodean defensif dalam sebuah skrip akan menjadi ide yang buruk. Apa yang Anda anggap sebagai PATH waras sebenarnya rentan terhadap eksploitasi yang belum Anda temui.
Dejay Clayton
1

Anda dapat menggunakan unset -funtuk menghapus fungsi builtin, perintah dan ketik.

odc
sumber
2
maaf, unset() { true; }urus itu.
falstro
1
Sial! Dan daftar fungsi yang didefinisikan juga bergantung pada builtin. Saya menyerah!
odc