“Bağımsız değişkenleri komut olarak yorumlayan komutlara güvenilir olmayan verileri iletecek herhangi bir komutu çalıştır”


18

Findutils 'el kitabından:

Örneğin, bu iki komut gibi yapılar

# risky
find -exec sh -c "something {}" \;
find -execdir sh -c "something {}" \;

çok tehlikelidir. Bunun nedeni, '{}' öğesinin noktalı virgül veya kabuğa özel diğer karakterleri içerebilecek bir dosya adına genişletilmesidir. Örneğin birisi dosyayı oluşturursa /tmp/foo; rm -rf $HOMEyukarıdaki iki komut birinin giriş dizinini silebilir.

Bu nedenle, bağımsız değişkenleri daha fazla yorumlanacak komutlar olarak yorumlayan komutlara (örneğin 'sh') güvenilmeyen verileri (dosya adlarının adları gibi) iletecek herhangi bir komut çalıştırmayın.

Kabuk durumunda, bu sorun için akıllı bir geçici çözüm vardır:

# safer
find -exec sh -c 'something "$@"' sh {} \;
find -execdir sh -c 'something "$@"' sh {} \;

Bu yaklaşımın her problemden kaçınacağı garanti edilmez, ancak bir saldırganın seçtiği verileri bir kabuk komutunun metnine koymaktan çok daha güvenlidir.

  1. Sorunun nedeni find -exec sh -c "something {}" \;, değiştirilmesinin {}alıntılanmamış olması ve bu nedenle tek bir dize olarak ele alınmaması mı?
  2. Çözümde find -exec sh -c 'something "$@"' sh {} \;,

    • Birincisi {}değiştirilir, ancak {}alıntı yapılmadığından "$@", orijinal komutla aynı problemi yoktur? Örneğin, "$@"genişletilmiş etmek olacaktır "/tmp/foo;", "rm", "-rf"ve "$HOME"?

    • neden {}kaçmıyor ya da alıntı yapılmıyor?

  3. Aynı tür problem ve çözümlerin uygulandığı ve sorun ve çözüm üzerine odaklanabilmemiz için asgari örnekler olan başka örnekler verebilir misiniz (hala sh -cvarsa veya olmasa; findgerekli olan veya olmayan gerekli) mümkün olduğunca az dikkat dağıtıcı? Bkz Yolları bash -c` `tarafından yürütülen bir komuta argümanlar sunmak

Teşekkürler.


Yanıtlar:


29

Bu gerçekten alıntı ile değil, argüman işleme ile ilgilidir.

Riskli örneği düşünün:

find -exec sh -c "something {}" \;
  • Bu altı kelimelere kabuk ve bölünmüş tarafından ayrıştırılır: find, -exec, sh, -c, something {}(tırnak işaretleri artık), ;. Genişleyecek bir şey yok. Kabuk findbu altı kelimeyle argüman olarak çalışır .

  • Ne zaman findsürecine buluntular şey, diyelim ki foo; rm -rf $HOME, yerini aldığı {}ile foo; rm -rf $HOME, ve çalışır shargümanlarla sh, -cve something foo; rm -rf $HOME.

  • shşimdi görür -cve sonuç olarak ayrıştırır something foo; rm -rf $HOME( ilk seçenek olmayan argüman ) ve sonucu yürütür.

Şimdi daha güvenli varyantı düşünün:

find -exec sh -c 'something "$@"' sh {} \;
  • Kabuk çalışır findargümanlarla find, -exec, sh, -c, something "$@", sh, {}, ;.

  • Şimdi zaman findbulur foo; rm -rf $HOME, onu değiştirir {}yine ve çalıştırır shargümanlarla sh, -c, something "$@", sh, foo; rm -rf $HOME.

  • shgörür -cve ayrıştırır something "$@"çalıştırmak için komut olarak ve shve foo; rm -rf $HOMEkonumsal parametreler (şekilde başlayarak$0 ) genişler "$@"için foo; rm -rf $HOME tek bir değer olarak , ve çalışır somethingbir bağımsız değişken ile foo; rm -rf $HOME.

Bunu kullanarak görebilirsiniz printf. Yeni bir dizin oluşturun, girin ve çalıştırın

touch "hello; echo pwned"

İlk varyantı aşağıdaki gibi çalıştırma

find -exec sh -c "printf \"Argument: %s\n\" {}" \;

üretir

Argument: .
Argument: ./hello
pwned

ikinci varyant ise

find -exec sh -c 'printf "Argument: %s\n" "$@"' sh {} \;

üretir

Argument: .
Argument: ./hello; echo pwned

Daha güvenli varyantta, neden {}kaçmıyor veya alıntı yapılmıyor?
Tim

1
Genellikle alıntılanmasına gerek yoktur; sadece başka bir anlamı olan bir kabukta alıntılanması gerekirdi ve bugünlerde kullanılan ana kabukların herhangi birinde böyle olduğunu sanmıyorum.
Stephen Kitt

Gönderen gnu.org/software/findutils/manual/html_mono/... : "Bu yapılar (her ikisi de ;ve {}.) İhtiyacı (a '\' ile birlikte) kaçan veya kabuk tarafından genişleme onları korumak için tırnak edilecek" Yani bash için yanlış mı demek istiyorsun?
Tim

3
Yani genel olarak mermiler için yanlış. POSIX'e bakınız . O belirli deyimi findutilsbu teklifi hiçbir zararı yok elbette Of manuel tarihleri ... en azından 1996 geri {}olarak ister '{}'ya "{}", ama bu gerekli değildir.
Stephen Kitt

@Tim Sezginizin henüz burada iki farklı türde argüman işleme türü olduğu gerçeğini hesaba katmadığı ortak bir durum yaşıyorsunuz: Kabuk , bir metin satırından argümanları ne zaman / nasıl ayrıştırır ( (tırnak işaretleri önemlidir) ham işletim sistemi bağımsız değişken geçişinden farklıdır (tırnak işaretleri yalnızca normal karakterlerdir). Kabuk komutunda find some/path -exec sh -c 'something "$@"' {} \;aslında üç kabuk argüman işleme / geçirme katmanı vardır , kabuk çeşitlerinden ikisi ve temel ham işletim sistemi çeşitlerinden biri.
mtraceur

3

Bölüm 1:

find yalnızca metin değişimini kullanır.

Evet, alıntı yapmadan yaptıysanız, şöyle:

find . -type f -exec sh -c "echo {}" \;

ve bir saldırgan adlı bir dosya oluşturabildi ; echo owned,

sh -c "echo ; echo owned"

ki çalışan kabuk ile sonuçlanacaktır echosonra echo owned.

Ancak tırnak işaretleri eklediyseniz, saldırgan tırnaklarınızı sonlandırabilir ve ardından kötü amaçlı komutu aşağıdaki adla bir dosya oluşturarak koyabilir '; echo owned:

find . -type f -exec sh -c "echo '{}'" \;

bu kabuk çalışma ile sonuçlanacaktır echo '', echo owned.

(çift tırnakları tek tırnak için değiştirdiyseniz, saldırgan diğer tırnak türlerini de kullanabilir.)


Bölüm 2:

İçinde find -exec sh -c 'something "$@"' sh {} \;, {}başlangıçta kabuk tarafından yorumlanmaz, doğrudan yürütülür execve, bu nedenle kabuk tırnak eklemek yardımcı olmaz.

find -exec sh -c 'something "$@"' sh "{}" \;

Etkisi yoktur, çünkü kabuk çalıştırmadan önce çift tırnak işaretlerini kaldırır find.

find -exec sh -c 'something "$@"' sh "'{}'" \;

Kabuğun özel olarak işlemediği tırnak işaretleri ekler, bu nedenle çoğu durumda komutun istediğinizi yapamayacağı anlamına gelir.

Şu şekilde genişleyebilir olması /tmp/foo;, rm, -rf, $HOMEolanlara argümanlar, çünkü bir sorun olmamalı somethingve somethingmuhtemelen yürütülecek komutu olarak argümanları kabul değil.


Bölüm 3:

Ben benzer hususlar, örneğin güvenilmeyen girdi alır ve (bir bölümü) bir komut olarak çalışan herhangi bir şey için geçerlidir varsayalım xargsve parallel.


xargskeşke tehlikelidir -Iveya -Jkullanılır. Normal işlemde, tıpkı listenin sonuna sadece argümanlar ekler -exec ... {} +.
Charles Duffy

1
( parallelaksine, kullanıcıdan açık bir istek olmadan varsayılan olarak bir kabuk çalıştırır, savunmasız yüzeyi arttırır; bu çok tartışmaya konu olan bir konudur; açıklama için bkz. unix.stackexchange.com/questions/349483/… , ve lists.gnu.org/archive/html/bug-parallel/2015-05/msg00005.html bağlantısını içerir ).
Charles Duffy

@CharlesDuffy " Hassas yüzeyin azaltılması " demek istediniz . OP tarafından gösterilen saldırı Gerçekten de öyle değil GNU Paralel ile varsayılan olarak çalışmasını.
Ole Tange

1
Evet, SSH genelinde parite için ihtiyacınız olduğunu biliyorum - eğerparallel herhangi bir soketin diğer ucunda doğrudan bir kabuksuz yapabilen uzak bir "alıcı" işlemi üretmediyseniz execv. Ayakkabıyı uygularken aleti uygularsam tam olarak ne yapardım. Etkileşimli olmayan bir programın şu andaki değerine göre farklı davranması SHELLpek de şaşırtıcı değildir.
Charles Duffy

1
Evet - Kullanıcı açıkça bir kabuk başlatmadıkça , boruların ve yönlendirmelerin imkansız olması gerektiğini düşünüyorum, bu noktada yalnızca açıkça başlattıkları kabuğun açık davranışını elde ediyorlar. Biri yalnızca ne execvsağladığını bekleyebilirse , bu daha basit ve daha az şaşırtıcı ve konumlar / çalışma zamanı ortamlarında değişmeyen bir kural.
Charles Duffy

3

1. Değişimin nedeni find -exec sh -c "something {}" \;değiştirilmemesinden kaynaklandığından {}ve bu nedenle tek bir dize olarak değerlendirilmemesinden kaynaklanıyor mu?

Bir anlamda, ancak alıntı burada yardımcı olamaz . Bunun yerine değiştirilen dosya adı, tırnak işaretleri dahil herhangi bir karakter {}içerebilir . Hangi alıntılama yöntemi kullanılırsa kullanılsın, dosya adı aynı alıntıyı içerebilir ve alıntılamayı "bozabilir".

2. ... ancak {}alıntı yapılmadığından "$@", orijinal komutla aynı sorunu yaşamıyor mu? Örneğin, "$@"genişletilmiş olacaktır "/tmp/foo;", "rm", "-rf", and "$HOME"?

Hayır "$@"ayrı kelimeler olarak, konumsal parametrelere genişler ve yok ayrıca bunları bölmek. Burada kendi içinde {}bir argüman var findve findmevcut dosya adını da ayrı bir argüman olarak geçiriyor sh. Kabuk betiğinde doğrudan değişken olarak kullanılabilir, kabuk komutunun kendisi olarak işlenmez.

... neden {}kaçmıyor ya da alıntı yapılmıyor?

Çoğu kabukta olması gerekmez. Eğer koşarsanız fish, olması gerekir: fish -c 'echo {}'boş bir satır yazdırır. Ama alıntı yaparsanız önemli değil, kabuk sadece tırnakları kaldıracaktır.

3. Başka örnekler verebilir misiniz?

Bir dosya adını (veya denetlenmeyen başka bir dizeyi), bir tür kod (*) olarak alınan bir dizenin içinde olduğu gibi genişlettiğinizde , rastgele komut yürütme olasılığı vardır.

Örneğin, bu $fdoğrudan Perl kodunu genişletir ve bir dosya adı çift tırnak içerdiğinde sorunlara neden olur. Dosya adındaki alıntı Perl kodundaki alıntıyı sona erdirir ve dosya adının geri kalanı herhangi bir Perl kodu içerebilir:

touch '"; print "HELLO";"'
for f in ./*; do
    perl -le "print \"size: \" . -s \"$f\""
done

(Perl herhangi bir kodu çalıştırmadan önce kodun tamamını ayrıştırdığından dosya adı biraz garip olmalı. Bu nedenle bir ayrıştırma hatasından kaçınmamız gerekecek.)

Bu bir argümandan güvenli bir şekilde geçerken:

for f in ./*; do
    perl -le 'print "size: " . -s $ARGV[0]' "$f"
done

(Başka bir mermiyi doğrudan bir mermiden çalıştırmak mantıklı değildir, ancak bunu yaparsanız find -exec sh ...durum benzerdir )

(* bir tür kod SQL içerir, bu yüzden zorunlu XKCD: https://xkcd.com/327/ artı açıklama: https://www.explainxkcd.com/wiki/index.php/Little_Bobby_Tables )


1

Endişeniz tam olarak GNU Parallel tırnaklarının giriş nedenidir:

touch "hello; echo pwned"
find . -print0 | parallel -0 printf \"Argument: %s\\n\" {}

Bu çalışmaz echo pwned.

Bir kabuk çalıştırır, böylece komutunuzu uzatırsanız aniden bir sürpriz elde edemezsiniz:

# This _could_ be run without spawining a shell
parallel "grep -E 'a|bc' {}" ::: foo
# This cannot
parallel "grep -E 'a|bc' {} | wc" ::: foo

Kabuk yumurtlama sorunu hakkında daha fazla bilgi için bkz. Https://www.gnu.org/software/parallel/parallel_design.html#Always-running-commands-in-a-shell


1
... dedi ki, find . -print0 | xargs -0 printf "Argument: %s\n"aynı derecede güvenli (ya da daha doğrusu, moreso, çünkü -print0biriyle yeni satırlara sahip dosya adlarını doğru bir şekilde işliyor); parallel'in alıntısı, hiçbir kabuk bulunmadığında var olmayan bir problem için geçici bir çözümdür .
Charles Duffy

Ancak | wckomuta eklediğinizde , güvenli hale getirmek için çemberlerden atlamanız gerekir. GNU Paralel varsayılan olarak güvenlidir.
Ole Tange

ITYM: Karmaşık her şeyi dikkatlice alıntılama süreciyle kabuk dilinin her bir sorununu örtmeye çalışır. Bu, verileri kodla karıştırılmamış değişkenlerde düzgün bir şekilde depolamak kadar güvenli değildir. Şimdi, bunu otomatik foo {} | wcolarak sh -c 'foo "$1" | wcsh {} `ye dönüştürecek olsaydı , bu farklı olabilir.
ilkkachu
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.