Preambuła
Po pierwsze, powiedziałbym, że nie jest to właściwy sposób rozwiązania problemu. To trochę jak powiedzenie „ nie powinieneś mordować ludzi, bo inaczej pójdziesz do więzienia ”.
Podobnie nie podajesz swojej zmiennej, ponieważ w przeciwnym razie wprowadzasz luki w zabezpieczeniach. Cytujesz swoje zmienne, ponieważ błędem jest nie (ale jeśli strach przed więzieniem może pomóc, dlaczego nie).
Małe podsumowanie dla tych, którzy właśnie wskoczyli do pociągu.
W większości powłok pozostawienie cudzysłowu zmiennego (choć to (i reszta tej odpowiedzi) dotyczy także podstawiania poleceń ( `...`
lub $(...)
) i rozszerzania arytmetycznego ( $((...))
lub $[...]
)) ma bardzo szczególne znaczenie. Najlepszym sposobem na opisanie tego jest to, że przypomina to wywoływanie jakiegoś domyślnego operatora split + glob operator¹.
cmd $var
w innym języku byłoby napisane coś takiego:
cmd(glob(split($var)))
$var
jest najpierw dzielony na listę słów zgodnie ze złożonymi regułami obejmującymi $IFS
specjalny parametr ( część podzielona ), a następnie każde słowo powstałe w wyniku tego podziału jest uważane za wzorzec, który jest rozwijany do listy pasujących plików ( część globalna ) .
Na przykład, jeśli $var
zawiera *.txt,/var/*.xml
i $IFS
zawiera ,
, cmd
zostanie wywołany z wieloma argumentami, z których pierwszy cmd
to txt
pliki, a następne to pliki w bieżącym katalogu i xml
pliki w /var
.
Jeśli chcesz zadzwonić cmd
za pomocą dwóch dosłownych argumentów cmd
i *.txt,/var/*.xml
piszesz:
cmd "$var"
który byłby w twoim innym, bardziej znanym języku:
cmd($var)
Co rozumiemy przez podatność w powłoce ?
W końcu od samego początku wiadomo, że skryptów powłoki nie należy używać w kontekstach wrażliwych pod względem bezpieczeństwa. Z pewnością OK pozostawienie zmiennej bez cudzysłowu jest błędem, ale to nie może wyrządzić tyle szkody, prawda?
Cóż, pomimo faktu, że ktoś powiedziałby ci, że skrypty powłoki nigdy nie powinny być używane do internetowych interfejsów graficznych, lub że na szczęście większość systemów nie pozwala obecnie na skrypty powłoki setuid / setgid, jedną z rzeczy, które shellshock (zdalnie wykorzystywany błąd bash, który spowodował nagłówki z września 2014 r.) ujawniły, że powłoki są nadal szeroko stosowane tam, gdzie prawdopodobnie nie powinny: w CGI, w skryptach przechwytujących klienta DHCP, w poleceniach sudoers, wywoływanych przez (jeśli nie jako ) polecenia setuid ...
Czasem nieświadomie. Na przykład system('cmd $PATH_INFO')
w skrypcie php
/ perl
/ python
CGI wywołuje powłokę, aby zinterpretować ten wiersz poleceń (nie wspominając o tym, że cmd
sam może być skryptem powłoki, a jego autor nigdy nie spodziewał się, że zostanie wywołany z CGI).
Masz lukę, gdy istnieje ścieżka do eskalacji uprawnień, to znaczy, gdy ktoś (nazwijmy go atakującym ) jest w stanie zrobić coś, do czego nie jest przeznaczony.
Niezmiennie oznacza to, że atakujący dostarcza dane, które są przetwarzane przez uprzywilejowanego użytkownika / proces, który przypadkowo robi coś, czego nie powinien robić, w większości przypadków z powodu błędu.
Zasadniczo masz problem, gdy Twój błędny kod przetwarza dane pod kontrolą atakującego .
Teraz nie zawsze jest oczywiste, skąd pochodzą te dane , i często trudno jest stwierdzić, czy Twój kod kiedykolwiek przetworzy niezaufane dane.
Jeśli chodzi o zmienne, w przypadku skryptu CGI jest dość oczywiste, że dane to parametry GET / POST CGI i parametry takie jak pliki cookie, ścieżka, parametry hosta ...
W przypadku skryptu setuid (uruchamianego jako jeden użytkownik, gdy jest wywoływany przez innego), są to argumenty lub zmienne środowiskowe.
Innym bardzo częstym wektorem są nazwy plików. Jeśli otrzymujesz listę plików z katalogu, możliwe, że atakujący umieścił tam pliki .
W tym względzie, nawet po zachęcie interaktywnej powłoki, możesz być narażony (np. Podczas przetwarzania plików w /tmp
lub ~/tmp
na przykład).
Nawet a ~/.bashrc
może być podatny na atak (na przykład bash
zinterpretuje go, gdy zostanie wywołany, ssh
aby uruchomić ForcedCommand
podobnie jak we git
wdrożeniach serwera z niektórymi zmiennymi pod kontrolą klienta).
Teraz skrypt nie może być wywoływany bezpośrednio w celu przetwarzania niezaufanych danych, ale może być wywoływany przez inne polecenie, które to robi. Albo twój niepoprawny kod może zostać wklejony do skryptów, które to robią (przez ciebie 3 lata później lub jeden z twoich kolegów). Jednym z miejsc, w którym jest to szczególnie ważne, są odpowiedzi na stronach z pytaniami i odpowiedziami, ponieważ nigdy nie wiadomo, gdzie mogą znaleźć się kopie kodu.
W dół do biznesu; jak bardzo jest źle?
Pozostawienie cudzysłowu zmiennej (lub podstawienia polecenia) jest zdecydowanie najważniejszym źródłem luk w zabezpieczeniach związanych z kodem powłoki. Częściowo dlatego, że te błędy często przekładają się na luki w zabezpieczeniach, ale także dlatego, że tak często można zobaczyć niecytowane zmienne.
W rzeczywistości, szukając luk w kodzie powłoki, pierwszą rzeczą do zrobienia jest poszukiwanie niecytowanych zmiennych. Jest łatwa do wykrycia, często jest dobrym kandydatem, na ogół łatwa do prześledzenia do danych kontrolowanych przez osobę atakującą.
Istnieje nieskończona liczba sposobów, w jakie niecytowana zmienna może przekształcić się w podatność na atak. Podam tylko kilka wspólnych trendów.
Ujawnienie informacji
Większość ludzi wpadnie na błędy związane z niecytowanymi zmiennymi ze względu na podzieloną część (na przykład często pliki mają spacje w swoich nazwach, a spacja ma domyślną wartość IFS). Wiele osób przeoczy
część globalną . Część glob jest co najmniej tak samo niebezpieczna jak
część podzielona .
Globowanie wykonywane na nieautoryzowanych wejściach zewnętrznych oznacza, że osoba atakująca może zmusić Cię do przeczytania zawartości dowolnego katalogu.
W:
echo You entered: $unsanitised_external_input
jeśli $unsanitised_external_input
zawiera /*
, oznacza to, że atakujący może zobaczyć zawartość /
. Nie ma sprawy. Staje się bardziej interesująca choć /home/*
co daje listę nazw użytkowników na maszynie /tmp/*
, /home/*/.forward
na podpowiedzi w innych niebezpiecznych praktyk, /etc/rc*/*
dla służb obsługujących ... Nie ma potrzeby, aby wymienić je indywidualnie. Wartość /*
/*/* /*/*/*...
spowoduje tylko wyświetlenie całego systemu plików.
Luki w zabezpieczeniach typu „odmowa usługi”.
Biorąc poprzednią sprawę trochę za daleko i mamy DoS.
W rzeczywistości każda niecytowana zmienna w kontekście listy z niezaszyfrowanymi danymi wejściowymi stanowi przynajmniej lukę w zabezpieczeniach DoS.
Nawet eksperci od skryptów powłoki często zapominają cytować takie rzeczy jak:
#! /bin/sh -
: ${QUERYSTRING=$1}
:
jest poleceniem no-op. Co może pójść nie tak?
Która jest przeznaczona do przypisania $1
do $QUERYSTRING
jeśli $QUERYSTRING
był wyłączony. To szybki sposób na wywołanie skryptu CGI również z wiersza poleceń.
Jest $QUERYSTRING
to jednak nadal rozwinięte, a ponieważ nie jest cytowane, wywoływany jest operator split + glob .
Obecnie istnieją globusy, których rozbudowa jest szczególnie droga. Ten /*/*/*/*
jest wystarczająco zły, ponieważ oznacza wyświetlanie list katalogów do 4 poziomów w dół. Oprócz aktywności dysku i procesora oznacza to przechowywanie dziesiątek tysięcy ścieżek do plików (40 tys. Tutaj na minimalnej maszynie wirtualnej serwera, z czego 10 tys. Katalogów).
Teraz /*/*/*/*/../../../../*/*/*/*
oznacza 40k x 10k i
/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*
wystarcza, aby rzucić nawet najpotężniejszą maszynę na kolana.
Wypróbuj sam (choć przygotuj się na awarię lub zawieszenie komputera):
a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'
Oczywiście, jeśli kod to:
echo $QUERYSTRING > /some/file
Następnie możesz wypełnić dysk.
Wystarczy zrobić wyszukiwania Google na CGI powłoki lub bash cgi lub ksh CGI , a znajdziesz kilka stron, które pokazują, jak napisać CGI w muszli. Zauważ, że połowa z tych, które przetwarzają parametry, jest wrażliwa.
Nawet własny David Korn
jest podatny na atak (patrz obsługa plików cookie).
aż do luk w wykonywaniu dowolnego kodu
Wykonanie dowolnego kodu jest najgorszym rodzajem podatności, ponieważ jeśli atakujący może uruchomić dowolne polecenie, nie ma ograniczeń co do tego, co może zrobić.
Na ogół jest to podzielona część, która do nich prowadzi. Podział powoduje przekazanie kilku poleceń do poleceń, gdy tylko jeden jest oczekiwany. Podczas gdy pierwszy z nich zostanie użyty w oczekiwanym kontekście, pozostałe będą w innym kontekście, więc potencjalnie będą interpretowane inaczej. Lepiej z przykładem:
awk -v foo=$external_input '$2 == foo'
Tutaj celem było przypisanie zawartości
$external_input
zmiennej powłoki do foo
awk
zmiennej.
Teraz:
$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux
Drugie słowo wynikające z podziału $external_input
nie jest przypisywane, foo
ale traktowane jako awk
kod (tutaj, które wykonuje dowolne polecenie:) uname
.
To przede wszystkim problem dla poleceń, które może wykonywać inne polecenia ( awk
, env
, sed
(GNU jeden) perl
, find
...), zwłaszcza z wariantów GNU (które akceptują opcje po argumentach). Zdarza się, że nie będzie podejrzewał polecenia, aby móc wykonać jak inni ksh
, bash
lub zsh
„S [
lub printf
...
for file in *; do
[ -f $file ] || continue
something-that-would-be-dangerous-if-$file-were-a-directory
done
Jeśli utworzymy katalog o nazwie x -o yes
, test staje się pozytywny, ponieważ oceniamy to wyrażenie warunkowe zupełnie inne.
Co gorsza, jeśli utworzymy plik o nazwie x -a a[0$(uname>&2)] -gt 1
, zawierający co najmniej wszystkie implementacje ksh (w tym sh
większość komercyjnych uniksów i niektóre BSD), który jest wykonywany, uname
ponieważ te powłoki wykonują obliczenia arytmetyczne na numerycznych operatorach porównania [
polecenia.
$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux
To samo bash
dotyczy nazwy pliku takiej jak x -a -v a[0$(uname>&2)]
.
Oczywiście, jeśli nie mogą uzyskać arbitralnej egzekucji, atakujący może zadowolić się mniejszymi obrażeniami (co może pomóc w uzyskaniu arbitralnej egzekucji). Można użyć dowolnego polecenia, które może zapisywać pliki lub zmieniać uprawnienia, własność lub mieć jakikolwiek efekt główny lub uboczny.
Za pomocą nazw plików można wykonywać różne czynności.
$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done
I w końcu ..
zapisujesz (rekurencyjnie w GNU
chmod
).
Skrypty wykonujące automatyczne przetwarzanie plików w miejscach, w których można publicznie zapisywać, /tmp
należy pisać bardzo ostrożnie.
Co powiesz na [ $# -gt 1 ]
Irytuje mnie to. Niektórzy ludzie zadają sobie trud zastanowienia się, czy dane rozszerzenie może być problematyczne, aby zdecydować, czy mogą pominąć cytaty.
To jak mówienie. Hej, wygląda na to, że $#
nie może podlegać operatorowi split + glob, zapytajmy powłokę o split + glob . Albo hej, napiszmy niepoprawny kod tylko dlatego, że błąd prawdopodobnie nie zostanie trafiony .
Teraz jak mało prawdopodobne? OK $#
(lub $!
, $?
albo dowolny arytmetyka substytucja) może zawierać tylko cyfry (lub -
dla niektórych), więc glob część jest na zewnątrz. Aby część podzielona mogła coś zrobić, wszystko, czego potrzebujemy, to $IFS
zawierać cyfry (lub -
).
Niektóre powłoki $IFS
mogą być dziedziczone ze środowiska, ale jeśli środowisko nie jest bezpieczne, i tak gra się kończy.
Teraz, jeśli napiszesz funkcję taką jak:
my_function() {
[ $# -eq 2 ] || return
...
}
Oznacza to, że zachowanie twojej funkcji zależy od kontekstu, w którym jest ona wywoływana. Innymi słowy, $IFS
staje się jednym z danych wejściowych. Ściśle mówiąc, kiedy piszesz dokumentację API dla swojej funkcji, powinna ona wyglądać mniej więcej tak:
# my_function
# inputs:
# $1: source directory
# $2: destination directory
# $IFS: used to split $#, expected not to contain digits...
Kod wywołujący twoją funkcję musi się upewnić, $IFS
że nie zawiera cyfr. Wszystko to dlatego, że nie miałeś ochoty pisać 2 podwójnych znaków cudzysłowu.
Teraz, aby ten [ $# -eq 2 ]
błąd mógł stać się podatny na atak, musisz w jakiś sposób uzyskać $IFS
kontrolę nad atakującym . Możliwe, że normalnie tak by się nie stało, gdyby atakujący nie wykorzystał innego błędu.
Nie jest to jednak niespotykane. Częstym przypadkiem jest to, że ludzie zapominają o odkażaniu danych przed użyciem ich w wyrażeniach arytmetycznych. Widzieliśmy już powyżej, że może pozwolić na wykonanie dowolnego kodu w niektórych powłokach, ale we wszystkich pozwala
atakującemu na podanie dowolnej zmiennej wartości całkowitej.
Na przykład:
n=$(($1 + 1))
if [ $# -gt 2 ]; then
echo >&2 "Too many arguments"
exit 1
fi
A przy $1
wartościach (IFS=-1234567890)
ta ocena arytmetyczna ma efekt uboczny ustawień IFS, a następna [
komenda kończy się niepowodzeniem, co oznacza, że sprawdzanie zbyt wielu argumentów jest pomijane.
Co się stanie, gdy operator split + glob nie zostanie wywołany?
Jest inny przypadek, w którym potrzebne są cudzysłowy wokół zmiennych i innych rozszerzeń: gdy jest on używany jako wzorzec.
[[ $a = $b ]] # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac
nie sprawdzaj, czy $a
i $b
są takie same (oprócz z zsh
), ale czy $a
pasuje do wzorca w $b
. I musisz zacytować, $b
jeśli chcesz porównać jako łańcuchy (to samo w "${a#$b}"
lub "${a%$b}"
lub "${a##*$b*}"
gdzie $b
należy je zacytować, jeśli nie należy tego traktować jako wzorzec).
Oznacza to, że [[ $a = $b ]]
może zwrócić wartość true w przypadkach, gdy $a
jest różna od $b
(na przykład, kiedy $a
jest anything
i $b
jest *
) lub może zwrócić false, gdy są identyczne (na przykład, gdy oba są $a
i $b
są [a]
).
Czy może to stanowić lukę w zabezpieczeniach? Tak, jak każdy błąd. W tym miejscu atakujący może zmienić logiczny przepływ kodu skryptu i / lub złamać założenia, które przyjmuje twój skrypt. Na przykład z kodem takim jak:
if [[ $1 = $2 ]]; then
echo >&2 '$1 and $2 cannot be the same or damage will incur'
exit 1
fi
Atakujący może ominąć czek, przekazując '[a]' '[a]'
.
Teraz, jeśli nie stosuje się ani dopasowania wzorca, ani operatora split + glob , jakie jest niebezpieczeństwo pozostawienia zmiennej bez cudzysłowu?
Muszę przyznać, że piszę:
a=$b
case $a in...
Cytowanie nie szkodzi, ale nie jest absolutnie konieczne.
Jednak jednym z efektów ubocznych pomijania cytatów w tych przypadkach (na przykład w odpowiedziach na pytania i odpowiedzi) jest to, że może wysłać niewłaściwą wiadomość do początkujących: że dobrze jest nie cytować zmiennych .
Na przykład mogą zacząć myśleć, że jeśli a=$b
jest OK, to export a=$b
również będzie (co nie jest w wielu powłokach, ponieważ jest w argumentach export
polecenia, więc w kontekście listy) lub env a=$b
.
Co zsh
?
zsh
naprawiono większość tych niezręczności projektowych. W zsh
(przynajmniej jeśli nie w sh / tryb emulacji ksh), jeśli chcesz, podział lub globbing lub pasujące do wzorca , musisz poprosić go wyraźnie: $=var
do podziału, a $~var
na glob lub za treść zmiennej należy traktować jako wzorzec.
Jednak dzielenie (ale nie globowanie) wciąż odbywa się w sposób dorozumiany po niecytowanym zastąpieniu polecenia (jak w echo $(cmd)
).
Ponadto czasami niepożądanym efektem ubocznym nie cytowania zmiennej jest usunięcie pustki . zsh
Zachowanie jest podobne do tego, co można osiągnąć w innych skorup wyłączając globbing całkowicie (z set -f
) i dzielenie (z IFS=''
). Jeszcze w:
cmd $var
Nie będzie podziału + glob , ale jeśli $var
jest pusty, zamiast otrzymać jeden pusty argument, cmd
nie otrzyma żadnego argumentu.
Może to powodować błędy (jak oczywiste [ -n $var ]
). Może to zepsuć oczekiwania i założenia skryptu i spowodować luki w zabezpieczeniach, ale nie mogę teraz wymyślić niezbyt dalekiego przykładu).
A co kiedy zrobić potrzebujemy podziału + glob operatora?
Tak, zwykle wtedy, gdy chcesz pozostawić zmienną bez cudzysłowu. Ale musisz upewnić się, że odpowiednio dostroiłeś operatorów split i glob przed użyciem. Jeśli chcesz tylko część podzieloną, a nie część globalną (co ma miejsce przez większość czasu), musisz wyłączyć globbing ( set -o noglob
/ set -f
) i naprawić $IFS
. W przeciwnym razie spowodujesz również luki w zabezpieczeniach (jak wspomniany wyżej przykład CGI Davida Korna).
Wniosek
Krótko mówiąc, pozostawienie cudzysłowu w powłoce zmiennej (lub podstawieniu polecenia lub interpretacji arytmetycznej) może być bardzo niebezpieczne, szczególnie, gdy jest wykonywane w niewłaściwych kontekstach, i bardzo trudno jest ustalić, które z tych niewłaściwych kontekstów.
To jeden z powodów, dla których uważa się to za złą praktykę .
Dzięki za przeczytanie do tej pory. Jeśli przejdzie ci przez głowę, nie martw się. Nie można oczekiwać, że wszyscy zrozumieją wszystkie konsekwencje pisania kodu w taki sposób, w jaki go piszą. Dlatego mamy
zalecenia dotyczące dobrych praktyk , dzięki czemu można je stosować, niekoniecznie rozumiejąc dlaczego.
(a jeśli nie jest to jeszcze oczywiste, unikaj pisania poufnych kodów w powłokach).
I proszę podać swoje zmienne w odpowiedziach na tej stronie!
¹ W ksh93
i pdksh
pochodnych interpretacja nawiasów jest również wykonywana, chyba że globbing jest wyłączony (w przypadku ksh93
wersji do ksh93u +, nawet gdy braceexpand
opcja jest wyłączona).