Apa yang dilakukan env x = '() {:;}; perintah bash lakukan dan mengapa itu tidak aman?

237

Tampaknya ada kerentanan (CVE-2014-6271) di bash: Bash dibuat khusus untuk serangan kode variabel lingkungan injeksi

Saya mencoba mencari tahu apa yang terjadi, tetapi saya tidak sepenuhnya yakin saya memahaminya. Bagaimana bisa echodieksekusi seperti dalam tanda kutip tunggal?

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
vulnerable
this is a test

EDIT 1 : Sistem yang ditambal terlihat seperti ini:

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
this is a test

EDIT 2 : Ada kerentanan / tambalan terkait: CVE-2014-7169 yang menggunakan tes yang sedikit berbeda:

$ env 'x=() { :;}; echo vulnerable' 'BASH_FUNC_x()=() { :;}; echo vulnerable' bash -c "echo test"

keluaran yang belum ditonton :

vulnerable
bash: BASH_FUNC_x(): line 0: syntax error near unexpected token `)'
bash: BASH_FUNC_x(): line 0: `BASH_FUNC_x() () { :;}; echo vulnerable'
bash: error importing function definition for `BASH_FUNC_x'
test

sebagian keluaran (versi awal) yang ditambal :

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
bash: error importing function definition for `BASH_FUNC_x()'
test

output yang ditambal hingga dan termasuk CVE-2014-7169:

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `BASH_FUNC_x'
test

EDIT 3 : cerita berlanjut dengan:

jippie
sumber
Bukan gema yang dieksekusi. itu definisi fungsi x. Jika fungsi yang didefinisikan dalam x melakukan beberapa pekerjaan licik licik, tidak ada cara bash dapat memeriksa nilai kembali jika fungsi x adalah nyata. Perhatikan bahwa fungsi ini kosong dalam kode pengujian. Nilai balik yang tidak dicentang dapat menyebabkan injeksi skrip. Injeksi script mengarah ke eskalasi hak istimewa dan eskalasi hak istimewa mengarah ke akses root. Patch menonaktifkan penciptaan x sebagai fungsi
eyoung100
26
eyoung100, tidak ada gema yang semakin dieksekusi. Anda dapat melihatnya dieksekusi karena kata tersebut vulnerablemuncul di output. Masalah utama adalah bahwa bash mem-parsing dan mengeksekusi kode setelah definisi fungsi juga. Lihat /bin/idbagian dari seclists.org/oss-sec/2014/q3/650 untuk contoh lainnya.
Mikel
4
Hanya komentar sampingan yang cepat. Red Hat telah menyarankan bahwa patch yang telah dirilis hanya sebagian patch dan meninggalkan sistem yang masih berisiko.
Peter
2
@ eyoung100 perbedaannya adalah bahwa kode di dalam fungsi hanya dijalankan ketika variabel lingkungan secara eksplisit dipanggil. Kode setelah definisi fungsi dijalankan setiap kali proses Bash baru dimulai.
David Farrell
1
Lihat stackoverflow.com/questions/26022248/… untuk rincian tambahan
Barmar

Jawaban:

204

bash menyimpan definisi fungsi yang diekspor sebagai variabel lingkungan. Fungsi yang diekspor terlihat seperti ini:

$ foo() { bar; }
$ export -f foo
$ env | grep -A1 foo
foo=() {  bar
}

Yaitu, variabel lingkungan foomemiliki konten literal:

() {  bar
}

Ketika instance baru dari bash diluncurkan, ia mencari variabel lingkungan yang dibuat khusus ini, dan menafsirkannya sebagai definisi fungsi. Anda bahkan dapat menulisnya sendiri, dan melihat bahwa itu masih berfungsi:

$ export foo='() { echo "Inside function"; }'
$ bash -c 'foo'
Inside function

Sayangnya, penguraian definisi fungsi dari string (variabel lingkungan) dapat memiliki efek yang lebih luas daripada yang dimaksudkan. Dalam versi yang tidak ditonton, ia juga mengartikan perintah sewenang-wenang yang terjadi setelah penghentian definisi fungsi. Hal ini disebabkan oleh kurangnya kendala dalam penentuan string seperti fungsi yang dapat diterima di lingkungan. Sebagai contoh:

$ export foo='() { echo "Inside function" ; }; echo "Executed echo"'
$ bash -c 'foo'
Executed echo
Inside function

Perhatikan bahwa gema di luar definisi fungsi telah secara tak terduga dieksekusi selama bash startup. Definisi fungsi hanyalah langkah untuk mendapatkan evaluasi dan mengeksploitasi terjadi, definisi fungsi itu sendiri dan variabel lingkungan yang digunakan sewenang-wenang. Shell melihat variabel lingkungan, melihat foo, yang sepertinya memenuhi batasan yang diketahui tentang seperti apa definisi fungsi, dan mengevaluasi baris, tanpa sengaja juga menjalankan gema (yang bisa berupa perintah apa pun, berbahaya atau tidak).

Ini dianggap tidak aman karena variabel biasanya tidak diizinkan atau diharapkan, sendiri, untuk secara langsung menyebabkan permohonan kode arbitrer yang terkandung di dalamnya. Mungkin program Anda menetapkan variabel lingkungan dari input pengguna yang tidak terpercaya. Akan sangat tak terduga bahwa variabel lingkungan tersebut dapat dimanipulasi sedemikian rupa sehingga pengguna dapat menjalankan perintah sewenang-wenang tanpa maksud eksplisit Anda untuk melakukannya menggunakan variabel lingkungan itu untuk alasan yang dinyatakan dalam kode.

Berikut adalah contoh serangan yang layak. Anda menjalankan server web yang menjalankan shell yang rentan, di suatu tempat, sebagai bagian dari masa pakainya. Server web ini meneruskan variabel lingkungan ke skrip bash, misalnya, jika Anda menggunakan CGI, informasi tentang permintaan HTTP sering dimasukkan sebagai variabel lingkungan dari server web. Misalnya, HTTP_USER_AGENTmungkin disetel ke konten agen pengguna Anda. Ini berarti bahwa jika Anda menipu agen pengguna Anda menjadi sesuatu seperti '() {:; }; echo foo ', ketika skrip shell berjalan, echo fooakan dieksekusi. Sekali lagi, echo foobisa berupa apa saja, berbahaya atau tidak.

Chris Down
sumber
3
Bisakah ini memengaruhi shell mirip Bash lainnya, seperti Zsh?
Amelio Vazquez-Reina
3
@ user815423426 Tidak, zsh tidak memiliki fitur ini. Ksh memilikinya tetapi diimplementasikan secara berbeda, saya pikir fungsi hanya dapat dikirim dalam keadaan yang sangat sempit, hanya jika garpu bercabang, bukan melalui lingkungan.
Gilles
20
@ user815423426 rc adalah shell lain yang melewati fungsi di lingkungan, tetapi dengan variabel dengan nama diawali dengan "fn_" dan mereka hanya ditafsirkan ketika dipanggil.
Stéphane Chazelas
18
@ StéphaneChazelas - terima kasih telah melaporkan bug.
Deer Hunter
13
@gnclmorais Maksud Anda Anda menjalankan export bar='() { echo "bar" ; }'; zsh -c bardan tampilannya barbukan zsh:1: command not found: bar? Apakah Anda yakin Anda tidak membingungkan shell yang Anda gunakan dengan shell yang Anda gunakan untuk mengatur tes?
Gilles
85

Ini dapat membantu untuk menunjukkan lebih lanjut apa yang sedang terjadi:

$ export dummy='() { echo "hi"; }; echo "pwned"'
$ bash
pwned
$

Jika Anda menjalankan shell yang rentan, maka ketika Anda memulai subkulit baru (di sini, cukup dengan menggunakan pernyataan bash), Anda akan melihat bahwa kode arbitrer ( echo "pwned") segera dieksekusi sebagai bagian dari inisiasinya. Rupanya, shell melihat bahwa variabel lingkungan (dummy) berisi definisi fungsi, dan mengevaluasi definisi untuk mendefinisikan fungsi itu di lingkungannya (perhatikan bahwa ia tidak menjalankan fungsi: yang akan mencetak 'hi'.)

Sayangnya, ini tidak hanya mengevaluasi definisi fungsi, tetapi juga mengevaluasi seluruh teks dari nilai variabel lingkungan, termasuk pernyataan yang mungkin berbahaya yang mengikuti definisi fungsi. Perhatikan bahwa tanpa definisi fungsi awal, variabel lingkungan tidak akan dievaluasi, itu hanya akan ditambahkan ke lingkungan sebagai string teks. Seperti yang ditunjukkan Chris Down, ini adalah mekanisme spesifik untuk mengimplementasikan impor fungsi shell yang diekspor.

Kita dapat melihat fungsi yang telah didefinisikan di shell baru (dan telah ditandai sebagai diekspor ke sana), dan kita dapat menjalankannya. Selain itu, dummy belum diimpor sebagai variabel teks:

$ declare -f
dummy ()
{
    echo "hi"
}
declare -fx dummy
$ dummy
hi
$echo $dummy
$

Baik penciptaan fungsi ini, maupun apa pun yang akan dilakukannya seandainya dijalankan, adalah bagian dari exploit - itu hanya kendaraan yang dengannya exploit dieksekusi. Intinya adalah bahwa jika penyerang dapat menyediakan kode berbahaya, didahului oleh definisi fungsi minimal dan tidak penting, dalam string teks yang dimasukkan ke dalam variabel lingkungan yang diekspor, maka itu akan dieksekusi ketika subkulit dimulai, yang merupakan peristiwa umum dalam banyak skrip. Selanjutnya, itu akan dieksekusi dengan hak istimewa skrip.

sdenham
sumber
17
Sementara jawaban yang diterima benar-benar mengatakan ini jika Anda membacanya dengan cermat, saya menemukan jawaban ini lebih jelas dan lebih membantu dalam memahami bahwa itu adalah evaluasi definisi (daripada menjalankan fungsi itu sendiri) yang menjadi masalah.
natevw
1
mengapa contoh ini memiliki exportperintah sedangkan yang lain punya env? Saya berpikir envsedang digunakan untuk mendefinisikan variabel lingkungan yang akan dipanggil ketika bash shell lain diluncurkan. lalu bagaimana ini bekerja denganexport
Haris
Hingga saat ini belum ada jawaban yang diterima. Saya mungkin akan menunggu beberapa hari lagi sebelum menerimanya. Kelemahan dari jawaban ini adalah bahwa ia tidak memecah perintah asli, juga tidak membahas cara mendapatkan dari perintah asli dalam pertanyaan ke perintah dalam jawaban ini, menunjukkan mereka identik. Selain itu penjelasan yang bagus.
jippie
@ralph - definisi ekspor envdan exportlingkungan sehingga tersedia dalam subkulit. Masalahnya sebenarnya adalah bagaimana definisi yang diekspor ini diimpor ke lingkungan subkulit, dan khususnya dalam mekanisme yang mengimpor definisi fungsi.
sdenham
1
@ralph - envmenjalankan perintah dengan beberapa opsi dan variabel lingkungan yang ditetapkan. Perhatikan bahwa dalam contoh pertanyaan asli, envset xke string, dan panggilan bash -cdengan perintah untuk dijalankan. Jika Anda melakukannya env x='foo' vim, Vim akan diluncurkan, dan di sana Anda dapat memanggil shell / environment yang berisi dengannya !echo $x, dan ia akan mencetak foo, tetapi jika Anda keluar dan echo $x, itu tidak akan ditentukan, karena hanya ada saat vim sedang berjalan melalui envperintah. The exportperintah bukan menetapkan nilai-nilai yang terus-menerus dalam lingkungan saat ini sehingga subkulit menjalankan kemudian akan menggunakannya.
Gary Fixler
72

Saya menulis ini sebagai penyusunan ulang gaya tutorial dari jawaban yang sangat baik oleh Chris Down di atas.


Dalam bash, Anda dapat memiliki variabel shell seperti ini

$ t="hi there"
$ echo $t
hi there
$

Secara default, variabel-variabel ini tidak diwarisi oleh proses anak.

$ bash
$ echo $t

$ exit

Tetapi jika Anda menandai mereka untuk ekspor, bash akan menetapkan bendera yang berarti mereka akan masuk ke lingkungan subproses (meskipun envpparameternya tidak banyak terlihat, mainprogram C Anda memiliki tiga parameter: di main(int argc, char *argv[], char *envp[])mana array pointer terakhir adalah array variabel shell dengan definisi mereka).

Jadi mari kita ekspor tsebagai berikut:

$ echo $t
hi there
$ export t
$ bash
$ echo $t
hi there
$ exit

Sedangkan di atas ttidak ditentukan dalam subkulit, sekarang muncul setelah kami mengekspornya (gunakan export -n tjika Anda ingin berhenti mengekspornya).

Tetapi fungsi dalam bash adalah binatang yang berbeda. Anda mendeklarasikannya seperti ini:

$ fn() { echo "test"; }

Dan sekarang Anda bisa menjalankan fungsi dengan memanggilnya seolah-olah itu adalah perintah shell lain:

$ fn
test
$

Sekali lagi, jika Anda menelurkan subkulit, fungsi kami tidak diekspor:

$ bash
$ fn
fn: command not found
$ exit

Kami dapat mengekspor fungsi dengan export -f:

$ export -f fn
$ bash
$ fn
test
$ exit

Inilah bagian yang sulit: fungsi yang diekspor seperti fndikonversi menjadi variabel lingkungan seperti ekspor variabel shell tkami di atas. Ini tidak terjadi ketika fnmerupakan variabel lokal, tetapi setelah ekspor kita dapat melihatnya sebagai variabel shell. Namun, Anda juga dapat memiliki variabel shell reguler (yaitu, tidak berfungsi) dengan nama yang sama. bash membedakan berdasarkan isi variabel:

$ echo $fn

$ # See, nothing was there
$ export fn=regular
$ echo $fn
regular
$ 

Sekarang kita dapat menggunakan envuntuk menampilkan semua variabel shell yang ditandai untuk diekspor dan fungsi reguler fndan fnmuncul:

$ env
.
.
.
fn=regular
fn=() {  echo "test"
}
$

Sub-shell akan mencerna kedua definisi: satu sebagai variabel reguler dan satu sebagai fungsi:

$ bash
$ echo $fn
regular
$ fn
test
$ exit

Anda dapat mendefinisikan fnseperti yang kami lakukan di atas, atau langsung sebagai tugas variabel biasa:

$ fn='() { echo "direct" ; }'

Perhatikan ini adalah hal yang luar biasa tinggi untuk dilakukan! Biasanya kita akan mendefinisikan fungsi fnseperti yang kita lakukan di atas dengan fn() {...}sintaks. Tetapi karena bash mengekspornya melalui lingkungan, kita dapat "memotong" langsung ke definisi reguler di atas. Perhatikan bahwa (berlawanan dengan intuisi Anda, mungkin) ini tidak menghasilkan fungsi baru yang fntersedia di shell saat ini. Tetapi jika Anda menelurkan shell ** sub **, maka itu akan.

Mari kita batalkan ekspor fungsi fndan biarkan reguler baru fn(seperti yang ditunjukkan di atas) tetap utuh.

$ export -nf fn

Sekarang fungsinya fntidak lagi diekspor, tetapi variabel regulernya fn, dan berisi () { echo "direct" ; }di dalamnya.

Sekarang ketika subkulit melihat variabel biasa yang dimulai dengan ()itu mengartikan sisanya sebagai definisi fungsi. Tapi ini hanya ketika shell baru dimulai. Seperti yang kita lihat di atas, hanya mendefinisikan variabel shell biasa dimulai dengan ()tidak menyebabkannya berperilaku seperti fungsi. Anda harus memulai subkulit.

Dan sekarang bug "shellshock":

Seperti yang baru saja kita lihat, ketika shell baru mencerna definisi dari variabel biasa yang dimulai dengan ()itu menafsirkannya sebagai fungsi. Namun, jika ada lebih banyak diberikan setelah kurung kurawal yang mendefinisikan fungsi, ia mengeksekusi apa pun yang ada di sana.

Ini adalah persyaratan, sekali lagi:

  1. Pesta baru muncul
  2. Variabel lingkungan dicerna
  3. Variabel lingkungan ini dimulai dengan "()" dan kemudian berisi fungsi tubuh di dalam kurung kurawal, dan kemudian memiliki perintah sesudahnya

Dalam hal ini, bash yang rentan akan menjalankan perintah yang terakhir.

Contoh:

$ export ex='() { echo "function ex" ; }; echo "this is bad"; '
$ bash
this is bad
$ ex
function ex
$

Variabel ekspor reguler exditeruskan ke subshell yang ditafsirkan sebagai fungsi extetapi perintah trailing dieksekusi ( this is bad) ketika subshell muncul.


Menjelaskan tes satu baris yang apik

Garis satu yang populer untuk menguji kerentanan Shellshock adalah yang dikutip dalam pertanyaan @ jippie:

env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

Berikut ini rinciannya: pertama :in bash hanyalah singkatan true. truedan :keduanya mengevaluasi (Anda dapat menebaknya) benar, dalam bash:

$ if true; then echo yes; fi
yes
$ if :; then echo yes; fi
yes
$

Kedua, envperintah (juga dibangun ke dalam bash) mencetak variabel lingkungan (seperti yang kita lihat di atas) tetapi juga dapat digunakan untuk menjalankan perintah tunggal dengan variabel yang diekspor (atau variabel) yang diberikan kepada perintah itu, dan bash -cmenjalankan satu perintah dari garis komando:

$ bash -c 'echo hi'
hi
$ bash -c 'echo $t'

$ env t=exported bash -c 'echo $t'
exported
$

Jadi menjahit semua hal ini bersama-sama, kita dapat menjalankan bash sebagai sebuah perintah, memberinya beberapa hal dummy untuk dilakukan (seperti bash -c echo this is a test) dan mengekspor variabel yang dimulai dengan ()sehingga subkulit akan menafsirkannya sebagai fungsi. Jika shellshock ada, itu juga akan segera menjalankan perintah trailing di subkulit. Karena fungsi yang kami lewati tidak relevan bagi kami (tetapi harus diuraikan!), Kami menggunakan fungsi valid terpendek yang bisa dibayangkan:

$ f() { :;}
$ f
$ 

Fungsi di fsini hanya menjalankan :perintah, yang mengembalikan true dan keluar. Sekarang tambahkan beberapa perintah "jahat" dan ekspor variabel reguler ke subkulit dan Anda menang. Ini adalah satu-liner lagi:

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

Jadi xdiekspor sebagai variabel reguler dengan fungsi valid sederhana dengan echo vulnerableditempel di akhir. Ini diteruskan ke bash, dan bash mengartikan xsebagai fungsi (yang tidak kita pedulikan) maka mungkin mengeksekusi echo vulnerablejika shellshock hadir.

Kami dapat mempersingkat one-liner sedikit dengan menghapus this is a testpesan:

$ env x='() { :;}; echo vulnerable' bash -c :

Ini tidak mengganggu this is a testtetapi menjalankan :perintah diam lagi. (Jika Anda berhenti -c :maka Anda duduk di subkulit dan harus keluar secara manual.) Mungkin versi yang paling ramah pengguna adalah yang ini:

$ env x='() { :;}; echo vulnerable' bash -c "echo If you see the word vulnerable above, you are vulnerable to shellshock"
Fixee
sumber
12
Penjelasan yang bagus. Pertanyaan ini menerima banyak pandangan (mungkin tidak semua orang mahir dalam bash seperti yang lain) dan saya yakin belum ada yang menghabiskan beberapa kata pada apa yang { :;};sebenarnya dikatakan. Itu akan menjadi tambahan yang bagus untuk jawaban Anda menurut saya. Bisakah menjelaskan bagaimana Anda mendapatkan dari contoh Anda ke perintah asli dalam pertanyaan?
jippie
20

Jika Anda dapat memasukkan variabel lingkungan yang berubah-ubah ke program, Anda dapat membuatnya melakukan apa saja dengan meminta memuat pustaka yang Anda pilih. Dalam sebagian besar kasus, ini tidak dianggap sebagai kerentanan dalam program yang menerima variabel lingkungan tersebut, melainkan dalam mekanisme di mana orang luar dapat memberi makan dalam variabel lingkungan yang sewenang-wenang.

Namun CVE-2014-6271 berbeda.

Tidak ada salahnya memiliki data yang tidak dipercaya dalam variabel lingkungan. Kita hanya perlu memastikan itu tidak dimasukkan ke dalam salah satu variabel lingkungan yang dapat mengubah perilaku program. Masukkan sedikit lebih abstrak, untuk permohonan tertentu, Anda dapat membuat daftar putih nama variabel lingkungan, yang diizinkan untuk ditentukan secara langsung oleh orang luar.

Contoh yang telah diajukan dalam konteks CVE-2014-6271 adalah skrip yang digunakan untuk mem-parsing file log. Mereka mungkin memiliki kebutuhan yang sangat sah untuk mengirimkan data yang tidak dipercaya ke dalam variabel lingkungan. Tentu saja nama untuk variabel lingkungan seperti itu dipilih sedemikian rupa sehingga tidak memiliki pengaruh buruk.

Tapi di sini adalah apa yang buruk tentang kerentanan bash khusus ini. Itu dapat dieksploitasi melalui nama variabel apa saja. Jika Anda membuat variabel lingkungan bernama GET_REQUEST_TO_BE_PROCESSED_BY_MY_SCRIPT, Anda tidak akan mengharapkan program lain selain skrip Anda sendiri untuk menafsirkan konten variabel lingkungan itu. Tetapi dengan mengeksploitasi bug bash ini, setiap variabel lingkungan tunggal menjadi vektor serangan.

Perhatikan bahwa ini tidak berarti nama variabel lingkungan diharapkan dirahasiakan. Mengetahui nama-nama variabel lingkungan yang terlibat tidak membuat serangan lebih mudah.

Jika program1panggilan program2yang pada gilirannya panggilan program3, maka program1dapat meneruskan data program3melalui variabel lingkungan. Setiap program memiliki daftar variabel lingkungan tertentu yang ditetapkannya dan daftar spesifik yang ditindaklanjuti. Jika Anda memilih nama yang tidak dikenali program2, Anda dapat meneruskan data dari program1ke program3tanpa khawatir ini akan berdampak buruk program2.

Seorang penyerang yang mengetahui nama pasti variabel yang diekspor oleh program1dan nama-nama variabel yang ditafsirkan oleh program2tidak dapat mengeksploitasi pengetahuan ini untuk mengubah perilaku 'program2` jika tidak ada tumpang tindih antara set nama.

Tapi ini rusak jika program2itu bashskrip, karena karena bug ini bashakan menafsirkan setiap variabel lingkungan sebagai kode.

kasperd
sumber
1
"setiap variabel lingkungan tunggal menjadi vektor serangan" - itulah bagian yang saya lewatkan. Terima kasih.
wrschneider
9

Itu dijelaskan dalam artikel yang Anda tautkan ...

Anda bisa membuat variabel lingkungan dengan nilai yang dibuat khusus sebelum memanggil bash shell. Variabel-variabel ini dapat berisi kode, yang dieksekusi segera setelah shell dipanggil.

Yang berarti bash yang dipanggil dengan -c "echo this is a test"mengeksekusi kode dalam tanda kutip tunggal ketika dipanggil.

Bash memiliki fungsi, meskipun dalam implementasi yang agak terbatas, dan dimungkinkan untuk menempatkan fungsi bash ini ke dalam variabel lingkungan. Kelemahan ini dipicu ketika kode tambahan ditambahkan ke akhir definisi fungsi ini (di dalam variabel enivronment).

Berarti contoh kode yang Anda poskan mengeksploitasi fakta bahwa bash yang dipanggil tidak berhenti mengevaluasi string ini setelah melakukan penugasan. Tugas fungsi dalam kasus ini.

Hal yang benar-benar spesial tentang potongan kode yang Anda posting, seperti yang saya mengerti, adalah bahwa dengan menggunakan definisi fungsi sebelum kode yang ingin kita jalankan, beberapa mekanisme keamanan dapat dielakkan.

Bananguin
sumber