Pembukaan
Pertama, saya katakan itu bukan cara yang tepat untuk mengatasi masalah tersebut. Ini seperti mengatakan " Anda tidak harus membunuh orang karena kalau tidak Anda akan masuk penjara ".
Demikian pula, Anda tidak mengutip variabel Anda karena jika tidak Anda memperkenalkan kerentanan keamanan. Anda mengutip variabel Anda karena itu salah untuk tidak (tetapi jika takut penjara dapat membantu, mengapa tidak).
Ringkasan kecil untuk mereka yang baru saja naik kereta.
Dalam kebanyakan shell, membiarkan ekspansi variabel tidak dikutip (meskipun itu (dan sisa jawaban ini) juga berlaku untuk substitusi perintah ( `...`
atau $(...)
) dan ekspansi aritmatika ( $((...))
atau $[...]
)) memiliki arti yang sangat istimewa. Cara terbaik untuk mendeskripsikannya adalah seperti memanggil semacam operator + pembelahan implisit¹ .
cmd $var
dalam bahasa lain akan dituliskan sesuatu seperti:
cmd(glob(split($var)))
$var
pertama-tama dipecah menjadi daftar kata sesuai dengan aturan kompleks yang melibatkan $IFS
parameter khusus (bagian terbagi ) dan kemudian setiap kata yang dihasilkan dari pemisahan itu dianggap sebagai pola yang diperluas ke daftar file yang cocok dengan itu (bagian gumpalan ) .
Sebagai contoh, jika $var
berisi *.txt,/var/*.xml
dan $IFS
mengandung ,
, cmd
akan dipanggil dengan sejumlah argumen, yang pertama adalah cmd
yang berikutnya adalah txt
file dalam direktori saat ini dan xml
file dalam /var
.
Jika Anda ingin menelepon cmd
hanya dengan dua argumen literal cmd
dan *.txt,/var/*.xml
, Anda akan menulis:
cmd "$var"
yang akan menggunakan bahasa lain yang lebih Anda kenal:
cmd($var)
Apa yang kita maksud dengan kerentanan di shell ?
Bagaimanapun, sudah diketahui sejak awal waktu bahwa skrip shell tidak boleh digunakan dalam konteks sensitif-keamanan. Tentunya, OK, membiarkan variabel tidak dicantumkan adalah bug, tetapi itu tidak banyak merugikan, bukan?
Yah, terlepas dari kenyataan bahwa siapa pun akan memberi tahu Anda bahwa skrip shell tidak boleh digunakan untuk CGI web, atau untungnya sebagian besar sistem tidak mengizinkan skrip shell setuid / setgid saat ini, satu hal yang shellshock (bug bash yang dapat dieksploitasi dari jarak jauh yang membuat berita utama pada bulan September 2014) mengungkapkan bahwa shell masih digunakan secara luas di mana mereka seharusnya tidak boleh: di CGI, dalam skrip kait klien DHCP, dalam perintah sudoers, dipanggil oleh (jika bukan sebagai ) perintah setuid ...
Terkadang tanpa sadar. Sebagai contoh system('cmd $PATH_INFO')
dalam skrip php
/ perl
/ python
CGI memang memanggil shell untuk menafsirkan baris perintah itu (belum lagi fakta bahwa cmd
itu sendiri mungkin merupakan skrip shell dan penulisnya mungkin tidak pernah berharap akan dipanggil dari CGI).
Anda memiliki kerentanan ketika ada jalan untuk peningkatan hak istimewa, yaitu ketika seseorang (sebut saja penyerangnya ) dapat melakukan sesuatu yang tidak seharusnya.
Selalu itu berarti penyerang menyediakan data, bahwa data sedang diproses oleh pengguna / proses istimewa yang secara tidak sengaja melakukan sesuatu yang seharusnya tidak dilakukan, dalam sebagian besar kasus karena bug.
Pada dasarnya, Anda memiliki masalah ketika kode kereta Anda memproses data di bawah kendali penyerang .
Sekarang, tidak selalu jelas dari mana data itu berasal, dan seringkali sulit untuk mengetahui apakah kode Anda akan pernah memproses data yang tidak dipercaya.
Sejauh menyangkut variabel, Dalam kasus skrip CGI, cukup jelas, datanya adalah parameter GET / POST CGI dan hal-hal seperti cookie, path, host ... parameter.
Untuk skrip setuid (berjalan sebagai satu pengguna ketika dipanggil oleh yang lain), itu argumen atau variabel lingkungan.
Vektor lain yang sangat umum adalah nama file. Jika Anda mendapatkan daftar file dari direktori, ada kemungkinan file telah ditanam di sana oleh penyerang .
Dalam hal itu, bahkan pada prompt shell interaktif, Anda bisa rentan (saat memproses file di /tmp
atau ~/tmp
misalnya).
Bahkan a ~/.bashrc
bisa rentan (misalnya, bash
akan menafsirkannya ketika dipanggil ssh
untuk menjalankan ForcedCommand
seperti dalam git
penyebaran server dengan beberapa variabel di bawah kendali klien).
Sekarang, skrip mungkin tidak dipanggil secara langsung untuk memproses data yang tidak dipercaya, tetapi skrip tersebut dapat dipanggil oleh perintah lain yang melakukannya. Atau kode yang salah Anda dapat disalin-salin ke skrip yang melakukannya (oleh Anda 3 tahun ke depan atau salah satu rekan Anda) Satu tempat yang sangat kritis adalah jawaban di situs Q&A karena Anda tidak akan pernah tahu di mana salinan kode Anda mungkin berakhir.
Turun ke bisnis; seberapa buruk?
Meninggalkan variabel (atau substitusi perintah) tanpa tanda kutip sejauh ini merupakan sumber kerentanan keamanan nomor satu yang terkait dengan kode shell. Sebagian karena bug itu sering diterjemahkan menjadi kerentanan tetapi juga karena sangat umum untuk melihat variabel yang tidak dikutip.
Sebenarnya, ketika mencari kerentanan dalam kode shell, hal pertama yang harus dilakukan adalah mencari variabel yang tidak dikutip. Mudah dikenali, seringkali kandidat yang baik, umumnya mudah dilacak kembali ke data yang dikendalikan penyerang.
Ada jumlah tak terbatas cara variabel berubah kutip bisa berubah menjadi kerentanan. Saya hanya akan memberikan beberapa tren umum di sini.
Pengungkapan informasi
Sebagian besar orang akan menabrak bug yang terkait dengan variabel yang tidak dikutip karena pembagian bagian (misalnya, sudah umum bagi file untuk memiliki ruang dalam nama mereka saat ini dan ruang dalam nilai default IFS). Banyak orang akan mengabaikan bagian
gumpal . Bagian gumpalan setidaknya sama berbahayanya dengan bagian yang
terbelah .
Gumpalan yang dilakukan atas input eksternal yang tidak disanitasi berarti penyerang dapat membuat Anda membaca konten direktori mana pun.
Di:
echo You entered: $unsanitised_external_input
jika $unsanitised_external_input
mengandung /*
, itu berarti penyerang dapat melihat konten /
. Bukan masalah besar. Ini menjadi lebih menarik meskipun dengan /home/*
yang memberi Anda daftar nama pengguna pada mesin /tmp/*
,, /home/*/.forward
untuk petunjuk tentang praktik berbahaya lainnya, /etc/rc*/*
untuk layanan yang diaktifkan ... Tidak perlu menyebutkan namanya secara individual. Nilai /*
/*/* /*/*/*...
hanya akan daftar seluruh sistem file.
Penolakan kerentanan layanan.
Mengambil kasus sebelumnya agak terlalu jauh dan kami punya DoS.
Sebenarnya, setiap variabel yang tidak dikutip dalam konteks daftar dengan input yang tidak bersih setidaknya adalah kerentanan DoS.
Bahkan penulis shell ahli biasanya lupa mengutip hal-hal seperti:
#! /bin/sh -
: ${QUERYSTRING=$1}
:
adalah perintah no-op. Apa yang mungkin salah?
Yang dimaksudkan untuk menetapkan $1
untuk $QUERYSTRING
jika $QUERYSTRING
itu tidak diset. Itu cara cepat untuk membuat skrip CGI dapat dipanggil dari baris perintah juga.
Itu $QUERYSTRING
masih diperluas dan karena tidak dikutip, operator split + glob dipanggil.
Sekarang, ada beberapa gumpalan yang sangat mahal untuk dikembangkan. Yang /*/*/*/*
satu cukup buruk karena itu berarti daftar direktori hingga 4 level ke bawah. Selain aktivitas disk dan CPU, itu berarti menyimpan puluhan ribu path file (40k di sini di VM server minimal, 10k di antaranya direktori).
Sekarang /*/*/*/*/../../../../*/*/*/*
berarti 40k x 10k dan
/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*
cukup untuk membuat mesin terkuat sekalipun.
Cobalah sendiri (meskipun bersiaplah untuk mesin Anda crash atau hang):
a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'
Tentu saja, jika kodenya adalah:
echo $QUERYSTRING > /some/file
Kemudian Anda dapat mengisi disk.
Lakukan saja pencarian google di shell cgi atau bash cgi atau ksh cgi , dan Anda akan menemukan beberapa halaman yang menunjukkan cara menulis CGI dalam shell. Perhatikan bagaimana setengah dari mereka yang memproses parameter rentan.
Bahkan David Korn sendiri
sangat rentan (lihat penanganan cookie).
hingga kerentanan eksekusi kode arbitrer
Eksekusi kode sewenang-wenang adalah jenis kerentanan terburuk, karena jika penyerang dapat menjalankan perintah apa pun, tidak ada batasan apa yang dapat ia lakukan.
Itu umumnya bagian split yang mengarah ke sana. Pemisahan itu menghasilkan beberapa argumen untuk diteruskan ke perintah ketika hanya satu yang diharapkan. Sementara yang pertama akan digunakan dalam konteks yang diharapkan, yang lain akan berada dalam konteks yang berbeda sehingga berpotensi ditafsirkan secara berbeda. Lebih baik dengan contoh:
awk -v foo=$external_input '$2 == foo'
Di sini, tujuannya adalah untuk menetapkan konten dari
$external_input
variabel shell ke foo
awk
variabel.
Sekarang:
$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux
Kata kedua yang dihasilkan dari pemisahan $external_input
tidak ditugaskan foo
tetapi dianggap sebagai awk
kode (di sini yang mengeksekusi perintah arbitrer:) uname
.
Itu terutama masalah bagi perintah yang dapat mengeksekusi perintah lain ( awk
, env
, sed
(GNU satu), perl
, find
...) terutama dengan varian GNU (yang menerima pilihan setelah argumen). Kadang-kadang, Anda tidak akan menduga perintah untuk dapat mengeksekusi orang lain seperti ksh
, bash
atau zsh
's [
atau printf
...
for file in *; do
[ -f $file ] || continue
something-that-would-be-dangerous-if-$file-were-a-directory
done
Jika kita membuat direktori bernama x -o yes
, maka tes menjadi positif, karena itu adalah ekspresi kondisional yang sama sekali berbeda yang kami evaluasi.
Lebih buruk lagi, jika kita membuat file bernama x -a a[0$(uname>&2)] -gt 1
, dengan semua implementasi ksh setidaknya (yang mencakup sh
sebagian besar Unices komersial dan beberapa BSD), yang dijalankan uname
karena shell tersebut melakukan evaluasi aritmatika pada operator perbandingan numerik dari [
perintah.
$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux
Sama dengan bash
untuk nama file seperti x -a -v a[0$(uname>&2)]
.
Tentu saja, jika mereka tidak bisa mendapatkan eksekusi sewenang-wenang, penyerang dapat menerima kerusakan yang lebih kecil (yang dapat membantu untuk mendapatkan eksekusi sewenang-wenang). Perintah apa pun yang dapat menulis file atau mengubah izin, kepemilikan atau memiliki efek utama atau samping dapat dieksploitasi.
Segala macam hal dapat dilakukan dengan nama file.
$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done
Dan Anda akhirnya ..
dapat menulis (secara rekursif dengan GNU
chmod
).
Skrip yang melakukan pemrosesan file secara otomatis di area /tmp
yang dapat ditulis untuk umum harus ditulis dengan sangat hati-hati.
Bagaimana dengan [ $# -gt 1 ]
Itu sesuatu yang saya temukan menjengkelkan. Beberapa orang turun semua kesulitan bertanya-tanya apakah ekspansi tertentu mungkin bermasalah untuk memutuskan apakah mereka dapat menghilangkan tanda kutip.
Ini seperti mengatakan. Hei, sepertinya $#
tidak bisa dikenai operator split + glob, mari kita minta shell untuk membagi + glob itu . Atau Hei, mari kita menulis kode yang salah hanya karena bug tidak mungkin terkena .
Sekarang seberapa tidak mungkin itu? OKE, $#
(atau $!
, $?
atau substitusi aritmatika apa pun) hanya boleh berisi angka (atau -
untuk sebagian) sehingga bagian gumpalan keluar. Untuk bagian yang terbagi untuk melakukan sesuatu, yang kita butuhkan hanyalah $IFS
memuat angka (atau -
).
Dengan beberapa cangkang, $IFS
mungkin diwarisi dari lingkungan, tetapi jika lingkungan tidak aman, permainan akan berakhir.
Sekarang jika Anda menulis fungsi seperti:
my_function() {
[ $# -eq 2 ] || return
...
}
Artinya adalah bahwa perilaku fungsi Anda tergantung pada konteksnya. Atau dengan kata lain, $IFS
menjadi salah satu masukan untuk itu. Sebenarnya, ketika Anda menulis dokumentasi API untuk fungsi Anda, itu harus seperti:
# my_function
# inputs:
# $1: source directory
# $2: destination directory
# $IFS: used to split $#, expected not to contain digits...
Dan kode yang memanggil fungsi Anda perlu memastikan $IFS
tidak mengandung digit. Semua itu karena Anda tidak ingin mengetik 2 karakter kutipan ganda itu.
Sekarang, agar [ $# -eq 2 ]
bug itu menjadi kerentanan, Anda perlu entah bagaimana agar nilainya $IFS
menjadi di bawah kendali penyerang . Dapat dibayangkan, itu tidak akan terjadi kecuali penyerang berhasil mengeksploitasi bug lain.
Tapi itu tidak pernah terdengar. Kasus yang umum adalah ketika orang lupa membersihkan data sebelum menggunakannya dalam ekspresi aritmatika. Kita telah melihat di atas bahwa itu dapat memungkinkan eksekusi kode arbitrer di beberapa shell, tetapi di semua itu, memungkinkan
penyerang untuk memberikan variabel nilai integer.
Misalnya:
n=$(($1 + 1))
if [ $# -gt 2 ]; then
echo >&2 "Too many arguments"
exit 1
fi
Dan dengan $1
nilai (IFS=-1234567890)
, evaluasi aritmatika tersebut memiliki efek samping dari pengaturan IFS dan [
perintah berikutnya gagal yang berarti pemeriksaan untuk terlalu banyak argumen dilewati.
Bagaimana dengan ketika operator split + glob tidak dipanggil?
Ada kasus lain di mana tanda kutip diperlukan di sekitar variabel dan ekspansi lainnya: ketika itu digunakan sebagai pola.
[[ $a = $b ]] # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac
jangan menguji apakah $a
dan $b
sama (kecuali dengan zsh
) tetapi jika $a
cocok dengan pola di $b
. Dan Anda perlu untuk mengutip $b
jika Anda ingin membandingkan sebagai string (hal yang sama di "${a#$b}"
atau "${a%$b}"
atau "${a##*$b*}"
di mana $b
harus dikutip jika tidak diambil sebagai pola).
Apa itu artinya bahwa [[ $a = $b ]]
mungkin kembali benar dalam kasus di mana $a
berbeda dari $b
(misalnya ketika $a
adalah anything
dan $b
adalah *
) atau dapat kembali palsu ketika mereka identik (misalnya ketika kedua $a
dan $b
yang [a]
).
Bisakah itu membuat kerentanan keamanan? Ya, seperti bug apa pun. Di sini, penyerang dapat mengubah alur kode logis skrip Anda dan / atau mematahkan asumsi yang dibuat skrip Anda. Misalnya, dengan kode seperti:
if [[ $1 = $2 ]]; then
echo >&2 '$1 and $2 cannot be the same or damage will incur'
exit 1
fi
Penyerang dapat melewati cek dengan melewati '[a]' '[a]'
.
Sekarang, jika tidak ada pencocokan pola atau operator glob + split , apa bahaya meninggalkan variabel yang tidak dikutip?
Saya harus mengakui bahwa saya memang menulis:
a=$b
case $a in...
Di sana, mengutip tidak membahayakan tetapi tidak sepenuhnya diperlukan.
Namun, satu efek samping dari menghilangkan kuotasi dalam kasus-kasus tersebut (misalnya dalam jawaban Q&A) adalah bahwa ia dapat mengirim pesan yang salah kepada para pemula: bahwa boleh saja tidak mengutip variabel .
Sebagai contoh, mereka mungkin mulai berpikir bahwa jika a=$b
OK, maka export a=$b
akan baik (yang tidak dalam banyak shell seperti dalam argumen ke export
perintah sehingga dalam konteks daftar) atau env a=$b
.
Bagaimana dengan zsh
?
zsh
memang memperbaiki sebagian besar canggung desain. Dalam zsh
(setidaknya ketika tidak dalam mode emulasi sh / ksh), jika Anda ingin membelah , atau menggumpal , atau mencocokkan pola , Anda harus memintanya secara eksplisit: $=var
untuk membelah, dan $~var
untuk menggumpal atau agar isi dari variabel diperlakukan sebagai sebuah pola.
Namun, pemisahan (tetapi tidak globbing) masih dilakukan secara implisit pada substitusi perintah yang tidak dikutip (seperti dalam echo $(cmd)
).
Juga, efek samping yang kadang-kadang tidak diinginkan dari tidak mengutip variabel adalah penghapusan kosong . The zsh
perilaku ini mirip dengan apa yang dapat Anda capai dalam kerang lainnya dengan menonaktifkan globbing sama sekali (dengan set -f
) dan membelah (dengan IFS=''
). Tetap:
cmd $var
Tidak akan ada split + glob , tetapi jika $var
kosong, alih-alih menerima satu argumen kosong, tidak cmd
akan menerima argumen sama sekali.
Itu bisa menyebabkan bug (seperti yang sudah jelas [ -n $var ]
). Itu mungkin dapat mematahkan harapan dan asumsi skrip dan menyebabkan kerentanan, tetapi saya tidak dapat memberikan contoh yang tidak terlalu jauh-baru saja diambil).
Bagaimana bila Anda lakukan membutuhkan perpecahan + gumpal Operator?
Ya, itu biasanya ketika Anda ingin membiarkan variabel Anda tidak dikutip. Tetapi kemudian Anda perlu memastikan Anda menyetel split dan operator glob Anda dengan benar sebelum menggunakannya. Jika Anda hanya ingin bagian split dan bukan bagian glob (yang merupakan kasus sebagian besar waktu), maka Anda perlu menonaktifkan globbing ( set -o noglob
/ set -f
) dan memperbaikinya $IFS
. Kalau tidak, Anda akan menyebabkan kerentanan juga (seperti contoh CGI David Korn yang disebutkan di atas).
Kesimpulan
Singkatnya, meninggalkan variabel (atau perintah substitusi atau ekspansi aritmatika) tanpa tanda kutip di shell bisa sangat berbahaya memang terutama ketika dilakukan dalam konteks yang salah, dan sangat sulit untuk mengetahui mana konteks yang salah itu.
Itulah salah satu alasan mengapa itu dianggap praktik yang buruk .
Terima kasih sudah membaca sejauh ini. Jika itu melampaui kepala Anda, jangan khawatir. Orang tidak dapat mengharapkan semua orang memahami semua implikasi dari penulisan kode mereka seperti cara mereka menulisnya. Itu sebabnya kami memiliki
rekomendasi praktik yang baik , sehingga dapat diikuti tanpa harus memahami mengapa.
(dan jika itu belum jelas, harap hindari menulis kode sensitif keamanan dalam shells).
Dan silahkan kutipan variabel Anda pada jawaban Anda di situs ini!
¹Dalam ksh93
dan pdksh
dan turunannya, ekspansi brace juga dilakukan kecuali globbing dinonaktifkan (dalam kasus ksh93
versi hingga ksh93u +, bahkan ketika braceexpand
opsi dinonaktifkan).