önsöz
İlk önce, sorunu çözmenin doğru yolu olmadığını söyleyebilirim. “ İnsanları öldürmemelisin çünkü aksi halde hapse gireceksin ” demek gibi bir şey .
Benzer şekilde değişkeninizi de alıntılamıyorsunuz, aksi halde güvenlik açıklarını ortaya çıkarıyorsunuz. Değişkenlerinizden alıntı yapıyorsunuz çünkü yanlış değil (ama hapis korkusu yardımcı olabilirse neden olmasın).
Trende yeni atlayanlar için küçük bir özet.
Çoğu kabukta, değişken bir genleşmeyi işaretsiz bırakmak (buna rağmen (ve bu cevabın geri kalanı) komut değiştirme ( `...`
veya $(...)
) için de geçerlidir ve aritmetik genişleme ( $((...))
veya $[...]
)) çok özel bir anlama sahiptir. Bunu tarif etmenin en iyi yolu, bir çeşit gizli split + glob operatörü¹ çağırmak gibidir .
cmd $var
başka bir dilde şöyle bir şey yazılmış olur:
cmd(glob(split($var)))
$var
önce $IFS
özel parametreyi ( bölünmüş kısım) içeren karmaşık kurallara göre bir sözcük listesine bölünür ve daha sonra bu bölünmeden kaynaklanan her sözcük , kendisiyle eşleşen dosyaların bir listesine genişletilen bir kalıp olarak değerlendirilir ( küre bölümü) .
Örnek olarak, $var
içerir *.txt,/var/*.xml
ve $IFS
içerirse ,
, cmd
birincisi cmd
ve diğeri txt
mevcut dizindeki xml
dosyalar ve içindeki dosyalar olmak üzere çeşitli argümanlarla çağrılır /var
.
cmd
Sadece iki değişmez argümanla aramak istersen cmd
ve şunu *.txt,/var/*.xml
yazarsın:
cmd "$var"
Diğer daha tanıdık bir dilde olurdu:
cmd($var)
Bir kabuktaki güvenlik açığı ile ne kastediyoruz ?
Ne de olsa, kabuk senaryolarının güvenliğe duyarlı bağlamlarda kullanılmaması gerektiğinden beri biliniyor. Elbette, tamam, değişkeni değişkensiz bırakmak bir hatadır ama bu çok fazla zarar veremez, değil mi?
Eh, kimsenin size kabuk komut dosyalarının hiçbir zaman web CGI'leri için kullanılması gerektiğini söylememesine rağmen ya da neyse ki çoğu sistem bugünlerde setuid / setgid kabuk komut dosyalarına izin vermez, kabukları tek bir şey (uzaktan sömürülebilir bash hatası yapan şey) Eylül 2014) 'de manşetler ortaya kabukları hala yaygın olarak kullanılmaktadır nerede olduğunu muhtemelen olmamalı: CGI'lerin, DHCP istemci kanca çağrılan sudoers komutları, içinde komut, tarafından (değilse olarak ) setuid komutlar ...
Bazen bilmeden. Örneğin system('cmd $PATH_INFO')
, bir php
/ perl
/ python
CGI betiğinde bu komut satırını yorumlamak için bir kabuk çağırır ( cmd
kendisinin bir kabuk betiği olabileceği ve yazarı bir CGI'dan çağrılmasını hiç beklemeyebileceği gerçeğinden bahsetmez ).
Ayrıcalık yükselmesi için bir yol olduğunda, yani birisinin (onu saldırgan olarak adlandıralım ) onun amaçlanmadığı bir şeyi yapabileceği bir güvenlik açığı vardır .
Değişmez bir şekilde bu , veri sağlayan saldırganın , verilerin bir hata nedeniyle çoğu durumda yanlışlıkla yapmaması gereken bir şeyi yapan, ayrıcalıklı bir kullanıcı / işlem tarafından işlendiği anlamına gelir .
Temel olarak, buggy kodunuz saldırganın kontrolü altındaki verileri işlerken bir sorun yaşarsınız .
Şimdi, bu verilerin nereden gelebileceği her zaman açık değildir ve kodunuzun güvenilmeyen verileri işleme alıp alamayacağını anlamak genellikle zordur.
Değişkenler söz konusu olduğunda, bir CGI betiği söz konusu olduğunda, bu oldukça açık, veriler CGI GET / POST parametreleri ve çerezler, yol, host ... parametreleri gibi şeylerdir.
Bir setuid betiği için (bir başkası tarafından çağrıldığında bir kullanıcı olarak çalışan), bağımsız değişkenler veya ortam değişkenleridir.
Diğer bir çok yaygın vektör dosya isimleridir. Bir dizinden bir dosya listesi alıyorsanız, dosyaları saldırgan tarafından oraya yerleştirilmiş olabilir .
(Dosyaları işlerken bu bağlamda, hatta interaktif bir kabuk isteminde, savunmasız olabilir /tmp
veya ~/tmp
örneğin).
Hatta bir ~/.bashrc
güvenlik açığı olabilir (örneğin, müşterinin kontrolü altında bazı değişkenlerle sunucu dağıtımlarında
benzerleri çalıştırmak için bash
çağrıldığında yorumlayacaktır ).ssh
ForcedCommand
git
Şimdi, bir komut dosyası güvenilir olmayan verileri işlemek için doğrudan çağrılmayabilir, ancak bunu yapan başka bir komutla çağrılabilir. Ya da yanlış kodunuz, kopyalayanlara (komut satırında veya iş arkadaşlarınızdan 3 yıl sonra) yazılan komut dosyalarına yapıştırılabilir. Özellikle kritik öneme sahip yerlerden biri, kodunuzun kopyalarının nerede biteceğini asla bilemeyeceğiniz için soru-cevap sitelerinde cevaplardır.
İş aşağı; ne kadar kötü
Bir değişkeni (veya komut değişikliğini) belirtmeden bırakmak, kabuk koduyla ilişkili bir numaralı güvenlik açıklarıdır. Kısmen bu hataların çoğu zaman güvenlik açıklarına dönüştüğü, ancak aynı zamanda işaretlenmemiş değişkenleri görmek çok yaygın olduğu için.
Aslında, kabuk kodundaki güvenlik açıklarını ararken, yapılacak ilk şey işaretsiz değişkenleri aramaktır. Genellikle iyi bir aday bulmak, genellikle saldırgan tarafından kontrol edilen verilere ulaşmak kolaydır.
Alıntı yapılmayan bir değişkenin güvenlik açığına dönüşmesinin sonsuz sayıda yolu vardır. Burada sadece birkaç ortak eğilim vereceğim.
Bilgi açıklaması
Çoğu kişi, ayrık bölüm nedeniyle (örneğin, günümüzde adlarında boşluk olması ve alanın IFS'nin varsayılan değerinde olması yaygındır), değişken olmayan değişkenlerle ilişkili hatalarla karşılaşacaktır . Birçok insan glob bölümünü görmezden gelecek
. Glob parçası olarak en az tehlikelidir
bölünmüş parçası.
Sterilize edilmemiş harici giriş üzerine yapılan küreleme , saldırganın herhangi bir dizinin içeriğini okumanızı sağladığı anlamına gelir .
İçinde:
echo You entered: $unsanitised_external_input
eğer $unsanitised_external_input
içeriyor /*
, yani saldırgan içeriğini görebilirsiniz /
. Önemli değil. Gerçi daha ilginç hale ile /home/*
, hangi makinede size kullanıcı adlarının bir listesini verir /tmp/*
, /home/*/.forward
diğer tehlikeli uygulamalara ipuçları için /etc/rc*/*
etkin hizmetler için ... Gerek tek tek isim. Değeri /*
/*/* /*/*/*...
sadece tüm dosya sistemini listeleyecektir.
Hizmet reddi güvenlik açıkları.
Önceki davayı biraz ileri götürdüğümüzde bir DoS var.
Aslında, liste içeriğindeki, onaylanmamış girdi içeren herhangi bir değişken olmayan değişken, en azından bir DoS güvenlik açığıdır.
Uzman kabuk senaryoları bile genellikle şu şeyleri alıntılamayı unutur:
#! /bin/sh -
: ${QUERYSTRING=$1}
:
no-op komutudur. Ne yanlış gidebilir ki?
Yani atamak için yaratılmış $1
için $QUERYSTRING
eğer $QUERYSTRING
tanımsız oldu. Bu, CGI betiğini komut satırından da çağrılabilir hale getirmenin hızlı bir yoludur.
Bu $QUERYSTRING
hala genişlemiş ve alıntı olmadığından split + glob operatörü çağrılıyor.
Şimdi, özellikle büyümesi pahalı olan bazı küreler var. /*/*/*/*
Aşağı 4 seviyelerine kadar listeleme dizinleri demektir gibi bir yeterince kötü. Diske ve CPU aktivitesine ek olarak, bu on binlerce dosya yolunun depolanması anlamına gelir (burada minimum sunucu VM'de 40k, 10k hangi dizinlerde).
Şimdi /*/*/*/*/../../../../*/*/*/*
40k x 10k anlamına geliyor ve
/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*
en güçlü makineyi bile dizlerine getirmek için yeterli.
Kendiniz deneyin (makinenizin çarpması veya takılması için hazırlıklı olsanız da):
a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'
Tabii ki, eğer kod:
echo $QUERYSTRING > /some/file
Sonra diski doldurabilirsiniz.
Sadece kabuk cgi veya bash cgi veya ksh cgi üzerinde bir google araması yapın. CGI'lerin kabuklara nasıl yazılacağını gösteren birkaç sayfa bulacaksınız. Parametreleri işleyenlerin yarısının korunmasız olduğuna dikkat edin.
David Korn'un bile kendi
savunmasızları (çerez kullanımına bakın).
rastgele kod yürütme güvenlik açıklarına kadar
Rasgele kod yürütme, en kötü güvenlik açığı türüdür, çünkü saldırgan herhangi bir komutu çalıştırabilirse, yapabileceklerinin sınırı yoktur.
Bu genellikle onlara götüren bölünmüş kısımdır. Bu bölme işlemi, yalnızca bir tane beklendiğinde komutlara iletilmesi gereken çeşitli argümanlarla sonuçlanır. Bunlardan ilki beklenen bağlamda kullanılacak olsa da, diğerleri farklı bir bağlamda olacak ve potansiyel olarak farklı şekilde yorumlanacaktır. Bir örnek ile daha iyi:
awk -v foo=$external_input '$2 == foo'
Burada amaç, $external_input
kabuk değişkeninin içeriğini değişkene atamaktı
foo
awk
.
Şimdi:
$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux
Ayrılmasından kaynaklanan ikinci sözcük $external_input
, atanmamış foo
ancak awk
kod olarak kabul edilmiştir (burada isteğe bağlı bir komut yürüten:) uname
.
Bu da diğer komutları yürütebilirsiniz komutlar için özellikle bir problem ( awk
, env
, sed
(GNU bir), perl
, find
...) özellikle (argümanlar sonra seçenekleri kabul) GNU biçimlerine sahiptir. Bazen, gibileri yürütmek edebilmek için komutlar şüpheli olmaz ksh
, bash
ya zsh
's [
ya printf
...
for file in *; do
[ -f $file ] || continue
something-that-would-be-dangerous-if-$file-were-a-directory
done
Eğer bir dizin yaratırsak x -o yes
, test pozitif olur, çünkü değerlendirdiğimiz tamamen farklı bir koşullu ifadedir.
Daha da kötüsü, x -a a[0$(uname>&2)] -gt 1
en azından tüm ksh uygulamalarıyla ( sh
çoğu ticari Unice ve bazı BSD'leri içerir) adında bir dosya oluşturursak , uname
bu kabukları [
komutun sayısal karşılaştırma işleçlerinde aritmetik değerlendirme gerçekleştirdiğinden çalıştırır .
$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux
bash
Gibi bir dosya adı için aynı x -a -v a[0$(uname>&2)]
.
Tabii ki, keyfi bir şekilde yürütülemiyorlarsa, saldırgan daha az hasar alabilir (bu, keyfi bir şekilde yürütmeye yardımcı olabilir). Dosya yazabilen veya izinleri değiştirebilen, sahiplik yapan veya herhangi bir ana veya yan etkiye sahip olan herhangi bir komut kullanılabilir.
Dosya adlarıyla her türlü şey yapılabilir.
$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done
Ve sen sonunda ..
yazı yazabilirsin (özyinelemeli GNU ile
chmod
).
Halka açık alanlardaki dosyaların otomatik olarak işlenmesini sağlayan komut dosyaları /tmp
çok dikkatli bir şekilde yazılmalıdır.
Ne dersin [ $# -gt 1 ]
Bu yorucu buluyorum bir şey. Bazı insanlar, belirli bir genişlemenin, teklifleri göz ardı edip etmeyeceklerine karar vermede sorunlu olup olmayacağını merak etme derdinden kurtulur.
Söylemek gibi. Hey, $#
split + glob operatörüne tabi olamaz gibi görünüyor , hadi kabuğundan split + glob yapmasını isteyelim . Veya Hey, hatayı yanlış kodlayalım çünkü hatayı vurmak pek mümkün değil .
Şimdi bu ne kadar mümkün değil? Tamam, $#
(veya $!
, $?
veya herhangi bir aritmetik ikame) sadece rakamları (veya -
bazıları için) içerebilir, böylece glob kısmı dışarıda kalır . İçin bölünmüş bir parçası olsa bir şeyler yapmak için tek ihtiyacımız olan $IFS
rakam içermeyen (veya -
).
Bazı mermilerle $IFS
çevreden miras alınabilir, ancak ortam güvenli değilse, yine de oyun biter.
Şimdi şöyle bir işlev yazarsanız:
my_function() {
[ $# -eq 2 ] || return
...
}
Bunun anlamı, işlevinizin davranışının, çağrıldığı içeriğe bağlı olmasıdır. Veya başka bir deyişle, $IFS
girdilerden biri haline gelir. Açıkçası, işlevinize ilişkin API belgelerini yazdığınızda, şöyle bir şey olmalı:
# my_function
# inputs:
# $1: source directory
# $2: destination directory
# $IFS: used to split $#, expected not to contain digits...
Ve fonksiyonunuzu çağıran kodun $IFS
rakam içermediğinden emin olması gerekir . Tüm bunlar, bu 2 çift alıntı karakterini yazmaktan hoşlanmadığınız için.
Şimdi, bu [ $# -eq 2 ]
hatanın bir güvenlik açığı olması için , saldırganın$IFS
kontrolünde olması için bir şekilde değere ihtiyacınız olacak . Muhtemelen, saldırgan başka bir hatadan yararlanmayı başaramazsa normalde olmazdı .
Bu hiç duyulmamış değil. Ortak bir durum, insanların aritmetik ifadede kullanmadan önce verileri temizlemeyi unuttukları durumdur. Yukarıda, bazı mermilerde rastgele kod yürütülmesine izin verebileceğini gördük, ancak hepsinde , saldırganın herhangi bir değişkene bir tamsayı değeri vermesini sağlar
.
Örneğin:
n=$(($1 + 1))
if [ $# -gt 2 ]; then
echo >&2 "Too many arguments"
exit 1
fi
Ve bir $1
değere sahip olarak (IFS=-1234567890)
, bu aritmetik değerlendirme, IFS ayarlarının yan etkisine sahiptir ve bir sonraki [
komut başarısız olur, bu da çok fazla arızanın kontrolünün atlandığı anlamına gelir .
Ne zaman hakkında bölünmüş + glob operatörü çağrılan değil?
Değişkenler ve diğer açılımlar etrafında alıntı yapılması gereken başka bir durum daha var: desen olarak kullanıldığında.
[[ $a = $b ]] # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac
olmadığını test yoktur $a
ve $b
(haricinde aynıdır zsh
) ama eğer $a
desenle eşleşirse $b
. Ve alıntı gerekir $b
Eğer dizeleri olarak karşılaştırmak istiyorsanız (aynı şey içinde "${a#$b}"
ya "${a%$b}"
ya "${a##*$b*}"
nerede $b
o değil bir desen olarak alınacak olursa alıntı olmalıdır).
Bunun anlamı olmasıdır [[ $a = $b ]]
durumlarda doğru döndürebilir $a
farklıdır $b
(örneğin, zaman $a
olduğu anything
ve $b
olduğu *
) ya da aynı olduğunda (her iki örneği için yanlış döndürebilir $a
ve $b
vardır [a]
).
Bu bir güvenlik açığına neden olabilir mi? Evet, herhangi bir böcek gibi. Burada, saldırgan betiğinizin mantıksal kod akışını değiştirebilir ve / veya betiğinizin yaptığı varsayımları ihlal edebilir. Örneğin, şöyle bir kodla:
if [[ $1 = $2 ]]; then
echo >&2 '$1 and $2 cannot be the same or damage will incur'
exit 1
fi
Saldırgan , pas geçerek kontrolü atlayabilir '[a]' '[a]'
.
Şimdi, ne eşleşen desen ne de split + glob operatörü geçerli değilse , bir değişkeni işaretsiz bırakma tehlikesi nedir?
Yazdığımı itiraf etmeliyim:
a=$b
case $a in...
Burada alıntı yapmak zarar vermez, ancak kesinlikle gerekli değildir.
Bununla birlikte, bu durumlarda teklif vermemenin bir yan etkisi (örneğin Q&A cevaplarında) yeni başlayanlara yanlış bir mesaj gönderebilmesidir: değişkenleri alıntılamamanın tamam olabileceği .
Örneğin, eğer a=$b
tamamsa, o export a=$b
zaman da iyi olacağını düşünmeye başlayabilirler (ki bu, liste bağlamında komutun argümanlarında olduğu gibi birçok kabukta değildirexport
) veya env a=$b
.
Ne hakkında zsh
?
zsh
Bu tasarım garipliklerinin çoğunu düzeltti. In zsh
(en azından sh / Ksh benzetim modunda iken) İstersen, bölme veya globbing veya desen eşleştirme , siz özellikle istemek zorunda: $=var
bölmek ve $~var
glob veya değişkenin içeriği için olduğu gibi tedavi edilecek Bir kalıp
Bununla birlikte, bölünme (ancak topaklanma) hala kote edilmemiş komut değiştirme (dolaylı olarak) ile örtük olarak yapılır echo $(cmd)
.
Ayrıca, değişkenlerden alıntı yapmamanın bazen istenmeyen bir yan etkisi boşalmaları gidermektir . zsh
Davranış (ile tamamen globbing devre dışı bırakarak diğer kabuklarda neler başarabileceğini benzer set -f
) ve bölme (ile IFS=''
). Hala içinde:
cmd $var
Split + glob olmayacak , ancak $var
boşsa, boş bir argüman almak yerine, cmd
hiçbir argüman almayacaksınız.
Bu, böceklere (bariz gibi [ -n $var ]
) neden olabilir . Bu muhtemelen bir betiğin beklentilerini ve varsayımlarını bozabilir ve güvenlik açıklarına yol açabilir, ancak şu anda çok fazla zorlanamayan bir örnek bulamıyorum).
Ne zaman hakkında ne gerek bölünmüş + glob operatörünü?
Evet, bu genellikle değişkeninizi alıntılanmamış bırakmak istersiniz. Ancak, split ve glob operatörlerinizi kullanmadan önce doğru şekilde ayarladığınızdan emin olmanız gerekir. Eğer bölünmüş parçayı sadece küre parçasını değil (çoğu zaman olduğu gibi) istiyorsanız, globbing'i ( set -o noglob
/ set -f
) devre dışı bırakmanız ve düzeltmeniz gerekir $IFS
. Aksi halde, güvenlik açıklarına neden olursunuz (yukarıda belirtilen David Korn'ın CGI örneği gibi).
Sonuç
Kısacası, kabuklarda işaretsiz bir değişkenin (ya da komut ikame veya aritmetik genişlemenin) bırakılması, özellikle yanlış bağlamlarda yapıldığında gerçekten tehlikeli olabilir ve bu yanlış bağlamların hangileri olduğunu bilmek çok zor olabilir.
Kötü uygulama olarak kabul edilmesinin sebeplerinden biri de budur .
Şimdiye kadar okuduğunuz için teşekkürler. Başının üzerinden geçerse, endişelenme. Herkesin, kodlarını yazdıkları şekilde yazmanın tüm etkilerini anlamalarını bekleyemezsiniz. Bu yüzden
iyi uygulama önerilerimiz var , bu yüzden mutlaka nedenini anlamadan takip edilebilirler.
(ve henüz kesin olarak açık değilse, lütfen güvenlik açısından hassas kodu kabuklara yazmaktan kaçının).
Ve lütfen bu sitedeki cevaplarınıza değişkenlerinizden alıntı yapın!
And ksh93
ve pdksh
ve türevleri, topaklanma devre dışı bırakılmadıkça ayraç genişlemesi de gerçekleştirilir ( seçenek, devre dışı bırakılmış ksh93
olsa bile ksh93u + sürümlerinde braceexpand
).