Co oznacza env x = '() {:;}; polecenie 'bash zrobić i dlaczego jest niepewny?


237

Podobno w bash istnieje luka (CVE-2014-6271): Bash, specjalnie spreparowany kod iniekcji kodu zmiennych środowiskowych

Próbuję dowiedzieć się, co się dzieje, ale nie jestem do końca pewien, czy to rozumiem. Jak można echowykonać w postaci pojedynczych cudzysłowów?

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

EDYCJA 1 : Poprawiony system wygląda następująco:

$ 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

EDYCJA 2 : Istnieje powiązana luka / łatka: CVE-2014-7169, która wykorzystuje nieco inny test:

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

niezaładowane dane wyjściowe :

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

częściowo (wczesna wersja) załatane wyjście :

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

załatane wyjście do CVE-2014-7169 włącznie:

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

EDYCJA 3 : historia kontynuuje:


To nie echo zostaje wykonane. jest to definicja funkcji x. Jeśli funkcja zdefiniowana w x wykonuje podstępną, podstępną pracę, bash nie może sprawdzić wartości zwracanej, jeśli funkcja x była prawdziwa. Zauważ, że funkcja jest pusta w kodzie testowym. Niesprawdzona wartość zwracana może prowadzić do wstrzyknięcia skryptu. Zastrzyk skryptu prowadzi do eskalacji uprawnień, a eskalacja uprawnień prowadzi do dostępu do katalogu głównego. Łatka wyłącza tworzenie x jako funkcji
eyoung100,

26
eyoung100, echo nie jest wykonywane. Możesz zobaczyć, jak się wykonuje, ponieważ słowo vulnerablepojawia się na wyjściu. Głównym problemem jest to, że bash analizuje i wykonuje kod również po definicji funkcji. Patrz /bin/idczęść seclists.org/oss-sec/2014/q3/650 na innym przykładzie.
Mikel

4
Szybki komentarz boczny. Red Hat poinformował, że łatka, która została wydana, jest jedynie częściową łatą i nadal naraża systemy na ryzyko.
Peter

2
@ eyoung100 różnica polega na tym, że kod wewnątrz funkcji jest wykonywany tylko wtedy, gdy zmienna środowiskowa jest jawnie wywołana. Kod po definicji funkcji jest wykonywany przy każdym uruchomieniu nowego procesu Bash.
David Farrell,

1
Dodatkowe informacje można znaleźć na stackoverflow.com/questions/26022248/ ...
Barmar

Odpowiedzi:


204

bash przechowuje wyeksportowane definicje funkcji jako zmienne środowiskowe. Wyeksportowane funkcje wyglądają następująco:

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

Oznacza to, że zmienna środowiskowa fooma dosłowną zawartość:

() {  bar
}

Po uruchomieniu nowej instancji bash szuka tych specjalnie spreparowanych zmiennych środowiskowych i interpretuje je jako definicje funkcji. Możesz nawet napisać jeden sam i przekonać się, że nadal działa:

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

Niestety parsowanie definicji funkcji z ciągów znaków (zmiennych środowiskowych) może mieć szerszy efekt niż zamierzony. W niezałatanych wersjach interpretuje także dowolne polecenia, które pojawiają się po zakończeniu definicji funkcji. Wynika to z niewystarczających ograniczeń w określaniu akceptowalnych ciągów funkcjonalnych w środowisku. Na przykład:

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

Zauważ, że echo poza definicją funkcji zostało nieoczekiwanie wykonane podczas uruchamiania bash. Definicja funkcji jest tylko krokiem do przeprowadzenia oceny i wykorzystania, sama definicja funkcji i zmienna środowiskowa są dowolne. Powłoka patrzy na zmienne środowiskowe, widzi foo, że wygląda na to, że spełnia ograniczenia, które wie o tym, jak wygląda definicja funkcji, i ocenia linię, mimowolnie również wykonując echo (może to być dowolne polecenie, złośliwe lub nie).

Jest to uważane za niepewne, ponieważ zmienne zwykle nie są dozwolone ani oczekiwane, aby bezpośrednio powodować wywołanie dowolnego kodu w nich zawartego. Być może twój program ustawia zmienne środowiskowe na podstawie niezaufanych danych użytkownika. Byłoby bardzo nieoczekiwane, że tymi zmiennymi środowiskowymi można manipulować w taki sposób, aby użytkownik mógł uruchamiać dowolne polecenia bez wyraźnej woli, używając tej zmiennej środowiskowej z takiego powodu zadeklarowanego w kodzie.

Oto przykład realnego ataku. Prowadzisz serwer WWW, który uruchamia gdzieś wrażliwą powłokę w ramach swojego życia. Ten serwer WWW przekazuje zmienne środowiskowe do skryptu bash, na przykład jeśli używasz CGI, informacje o żądaniu HTTP są często dołączane jako zmienne środowiskowe z serwera WWW. Na przykład HTTP_USER_AGENTmoże być ustawiony na zawartość klienta użytkownika. Oznacza to, że jeśli sfałszujesz swojego agenta użytkownika jako coś w stylu „() {:; }; echo foo ', po uruchomieniu skryptu powłoki, echo foozostanie wykonane. Ponownie echo foomoże to być wszystko, złośliwe lub nie.


3
Czy może to wpłynąć na inną powłokę podobną do Bash, jak Zsh?
Amelio Vazquez-Reina

3
@ user815423426 Nie, zsh nie ma tej funkcji. Ksh ma to, ale zaimplementowano inaczej, myślę, że funkcje mogą być przesyłane tylko w bardzo wąskich okolicznościach, tylko jeśli powłoka rozwidla się, a nie przez środowisko.
Gilles

20
@ user815423426 rc to druga powłoka, która przekazuje funkcje w środowisku, ale ma zmienną o nazwach poprzedzonych „fn_” i są one interpretowane tylko po wywołaniu.
Stéphane Chazelas

18
@ StéphaneChazelas - dziękuję za zgłoszenie błędu.
Deer Hunter,

13
@gnclmorais Masz na myśli, że biegasz export bar='() { echo "bar" ; }'; zsh -c bari wyświetla się barzamiast zsh:1: command not found: bar? Czy na pewno nie mylisz powłoki, którą wywołujesz, z powłoką, której używasz do skonfigurowania testu?
Gilles

85

Może to pomóc w dalszym zademonstrowaniu, co się dzieje:

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

Jeśli używasz wrażliwej powłoki, to kiedy uruchomisz nową podpowłokę (tutaj, po prostu za pomocą instrukcji bash), zobaczysz, że dowolny kod ( echo "pwned") jest natychmiast wykonywany w ramach jego inicjacji. Najwyraźniej powłoka widzi, że zmienna środowiskowa (obojętna) zawiera definicję funkcji, i ocenia tę definicję w celu zdefiniowania tej funkcji w swoim środowisku (zauważ, że nie wykonuje funkcji: wypisałby „cześć”).

Niestety, nie tylko ocenia definicję funkcji, ocenia cały tekst wartości zmiennej środowiskowej, w tym potencjalnie złośliwe instrukcje, które następują po definicji funkcji. Zauważ, że bez początkowej definicji funkcji zmienna środowiskowa nie byłaby oceniana, byłaby jedynie dodana do środowiska jako ciąg tekstowy. Jak zauważył Chris Down, jest to specyficzny mechanizm do implementacji importu eksportowanych funkcji powłoki.

Widzimy funkcję, która została zdefiniowana w nowej powłoce (i że została tam oznaczona jako wyeksportowana) i możemy ją wykonać. Ponadto, manekin nie został zaimportowany jako zmienna tekstowa:

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

Ani stworzenie tej funkcji, ani nic, co by zrobił, gdyby ją uruchomić, nie jest częścią exploita - jest tylko pojazdem, za pomocą którego exploit jest wykonywany. Chodzi o to, że jeśli osoba atakująca może dostarczyć złośliwy kod, poprzedzony minimalną i nieistotną definicją funkcji, w ciągu tekstowym, który zostaje umieszczony w eksportowanej zmiennej środowiskowej, zostanie on wykonany po uruchomieniu podpowłoki, co jest częstym zdarzeniem w wielu skryptach. Ponadto będzie wykonywany z uprawnieniami skryptu.


17
Podczas gdy zaakceptowana odpowiedź faktycznie to mówi, jeśli się ją dokładnie przeczyta, znalazłem tę odpowiedź jeszcze jaśniejszą i bardziej pomocną w zrozumieniu, że problemem jest ocena definicji (a nie wykonywanie samej funkcji).
natevw

1
dlaczego ten przykład ma exportpolecenie, podczas gdy inni mieli env? myślałem, envże jest używany do definiowania zmiennych środowiskowych, które będą wywoływane, gdy uruchomiona zostanie kolejna powłoka bash. jak to działa zexport
Haris,

Do tej pory nie było akceptowanej odpowiedzi. Prawdopodobnie poczekam jeszcze kilka dni, zanim je zaakceptuję. Minusem tej odpowiedzi jest to, że nie rozkłada ona oryginalnego polecenia ani nie omawia, jak przejść z oryginalnego polecenia w pytaniu do poleceń w tej odpowiedzi, pokazując, że są one identyczne. Poza tym jest to dobre wytłumaczenie.
jippie

@ralph - zarówno definicje środowiska eksportu, jak envi exporteksportu, dzięki czemu są one dostępne w podpowłoce. Problem tkwi w sposobie, w jaki te eksportowane definicje są importowane do środowiska podpowłoki, a w szczególności w mechanizmie importującym definicje funkcji.
sdenham

1
@ralph - envuruchamia polecenie z ustawionymi niektórymi opcjami i zmiennymi środowiskowymi. Zauważ, że w oryginalnych przykładach pytań envustawia xciąg znaków i wywołuje bash -cpolecenie do uruchomienia. Jeśli to zrobisz env x='foo' vim, Vim uruchomi się, a tam możesz wywołać jego zawierającą powłokę / środowisko za pomocą !echo $xi wydrukuje foo, ale jeśli następnie wyjdziesz i echo $xnie zostanie zdefiniowany, ponieważ istniał tylko podczas działania vima za pomocą envpolecenia. exportPolecenie zamiast ustawia wartości trwałych w bieżącym środowisku powłoki w tle, tak aby uruchomić później będą z nich korzystać.
Gary Fixler

72

Napisałem to jako powtórkę w stylu samouczka doskonałej odpowiedzi Chrisa Down powyżej.


W bash możesz mieć takie zmienne powłoki

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

Domyślnie zmienne te nie są dziedziczone przez procesy potomne.

$ bash
$ echo $t

$ exit

Ale jeśli zaznaczysz je do eksportu, bash ustawi flagę, co oznacza, że ​​przejdą do środowiska podprocesów (chociaż envpparametr nie jest zbyt często widoczny, mainw twoim programie C ma trzy parametry: main(int argc, char *argv[], char *envp[])gdzie ostatnia tablica wskaźników jest tablicą zmiennych powłoki z ich definicjami).

Eksportujmy więc tw następujący sposób:

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

Podczas gdy powyższa nie tzostała zdefiniowana w podpowłoce, teraz pojawia się po jej wyeksportowaniu (użyj, export -n tjeśli chcesz przestać eksportować).

Ale funkcje w bash są innym zwierzęciem. Deklarujesz je w ten sposób:

$ fn() { echo "test"; }

A teraz możesz po prostu wywołać funkcję, wywołując ją tak, jakby była kolejną komendą powłoki:

$ fn
test
$

Jeszcze raz, jeśli odrodzisz podpowłokę, nasza funkcja nie zostanie wyeksportowana:

$ bash
$ fn
fn: command not found
$ exit

Możemy wyeksportować funkcję za pomocą export -f:

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

Oto trudna część: wyeksportowana funkcja, taka jak, fnjest przekształcana w zmienną środowiskową, tak jak nasz eksport zmiennej powłoki tbył powyżej. Nie dzieje się tak, gdy fnbyła zmienną lokalną, ale po eksporcie możemy zobaczyć ją jako zmienną powłoki. Jednak możesz również mieć zwykłą (tj. Niefunkcjonalną) zmienną powłoki o tej samej nazwie. bash rozróżnia na podstawie zawartości zmiennej:

$ echo $fn

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

Teraz możemy użyć, envaby wyświetlić wszystkie zmienne powłoki oznaczone do eksportu, a zarówno normalna, jak fni funkcja fnpokazują:

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

Sub-powłoka będzie przyjmować obie definicje: jedną jako zmienną regularną i jedną jako funkcję:

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

Możesz zdefiniować, fnjak to zrobiliśmy powyżej, lub bezpośrednio jako zwykłe przypisanie zmiennej:

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

Uwaga: jest to niezwykła rzecz do zrobienia! Normalnie zdefiniowalibyśmy tę funkcję, fntak jak to zrobiliśmy powyżej, za pomocą fn() {...}składni. Ale ponieważ bash eksportuje go przez środowisko, możemy „skrócić” bezpośrednio do powyższej zwykłej definicji. Zauważ, że (być może wbrew twojej intuicji) nie skutkuje to nową funkcją fndostępną w bieżącej powłoce. Ale jeśli odrodzisz powłokę ** sub **, to zrobi to.

Anulujmy eksport funkcji fni pozostawmy nowy regularny fn(jak pokazano powyżej) nietknięty.

$ export -nf fn

Teraz funkcja fnnie jest już eksportowana, ale zmienna zwykła fnjest i zawiera się () { echo "direct" ; }w niej.

Teraz, gdy podpowłoka widzi zwykłą zmienną, która zaczyna się od ()niej, interpretuje resztę jako definicję funkcji. Ale to tylko wtedy, gdy zaczyna się nowa powłoka. Jak widzieliśmy powyżej, samo zdefiniowanie zwykłej zmiennej powłoki zaczynającej się od ()nie powoduje, że zachowuje się ona jak funkcja. Musisz uruchomić podpowłokę.

A teraz błąd „shellshock”:

Jak właśnie widzieliśmy, gdy nowa powłoka przyjmuje definicję zmiennej regularnej, zaczynając od ()niej, interpretuje ją jako funkcję. Jeśli jednak po nawiasie zamykającym jest więcej danych, które definiują funkcję, wykonuje to wszystko, co tam jest.

Oto jeszcze raz wymagania:

  1. Pojawia się nowa bitwa
  2. Przyjmowana jest zmienna środowiskowa
  3. Ta zmienna środowiskowa zaczyna się od „()”, a następnie zawiera treść funkcji w nawiasach klamrowych, a następnie zawiera polecenia

W takim przypadku wrażliwe bash wykona te ostatnie polecenia.

Przykład:

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

Regularnie eksportowana zmienna exzostała przekazana do podpowłoki, która została zinterpretowana jako funkcja, exale polecenia końcowe zostały wykonane ( this is bad) podczas odrodzenia podpowłoki.


Wyjaśnienie zręcznego testu jednowierszowego

Popularnym narzędziem do testowania podatności na Shellshock jest cytowany w pytaniu @ jippie:

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

Oto podział: po pierwsze :in bash to tylko skrót true. truei :obie oceniają (zgadłeś) to prawda, w bash:

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

Po drugie, envpolecenie (również wbudowane w bash) drukuje zmienne środowiskowe (jak widzieliśmy powyżej), ale można go również użyć do uruchomienia pojedynczego polecenia z wyeksportowaną zmienną (lub zmiennymi) przekazanymi temu poleceniu i bash -curuchamia pojedyncze polecenie z jego wiersz poleceń:

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

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

Więc łącząc wszystkie te rzeczy razem, możemy uruchomić bash jako polecenie, dać mu coś do roboty (lubić bash -c echo this is a test) i wyeksportować zmienną, która zaczyna się od, ()aby podpowłoka zinterpretowała ją jako funkcję. Jeśli shellshock jest obecny, natychmiast wykona również wszelkie końcowe polecenia w podpowłoce. Ponieważ funkcja, którą przekazujemy, jest dla nas nieistotna (ale musi zostać przeanalizowana!), Używamy najkrótszej prawidłowej funkcji, jaką można sobie wyobrazić:

$ f() { :;}
$ f
$ 

Ta funkcja fpo prostu wykonuje :polecenie, które zwraca true i kończy działanie. Teraz dołącz do tego jakieś „złe” polecenie i wyeksportuj zmienną regularną do podpowłoki, a wygrasz. Oto znowu jedna linijka:

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

xJest więc eksportowany jako zwykła zmienna z prostą poprawną funkcją z echo vulnerabledołączoną do końca. Jest to przekazywane do bash, a bash interpretuje xjako funkcję (o którą nie dbamy), a następnie może wykonuje, echo vulnerablejeśli shellshock jest obecny.

Możemy skrócić nieco linijkę, usuwając this is a testkomunikat:

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

Nie przeszkadza to, this is a testale ponownie uruchamia ciche :polecenie. (Jeśli -c :zrezygnujesz, to siedzisz w podpowłoce i musisz wyjść ręcznie.) Być może najbardziej przyjazna dla użytkownika wersja byłaby:

$ env x='() { :;}; echo vulnerable' bash -c "echo If you see the word vulnerable above, you are vulnerable to shellshock"

12
Ładne wyjaśnienie. To pytanie zyskuje wiele poglądów (prawdopodobnie nie wszyscy są tak biegli w bash jak inni) i wierzę, że nikt jeszcze nie wydał kilku słów na to, co { :;};faktycznie mówi. Moim zdaniem byłby to miły dodatek do twojej odpowiedzi. Czy może wyjaśnić, w jaki sposób przechodzisz od przykładu do oryginalnej komendy w pytaniu?
jippie

20

Jeśli możesz podać dowolne zmienne środowiskowe do programu, możesz sprawić, że zrobi on prawie wszystko, poprzez załadowanie wybranych bibliotek. W większości przypadków nie jest to uważane za usterkę w programie odbierającym te zmienne środowiskowe, ale raczej w mechanizmie, za pomocą którego osoba z zewnątrz może zasilać dowolne zmienne środowiskowe.

Jednak CVE-2014-6271 jest inny.

Nie ma nic złego w niezaufaniu danych w zmiennej środowiskowej. Trzeba tylko upewnić się, że nie zostanie wprowadzone do żadnej z tych zmiennych środowiskowych, które mogą modyfikować zachowanie programu. Mówiąc nieco bardziej abstrakcyjnie, dla konkretnego wywołania możesz utworzyć białą listę nazw zmiennych środowiskowych, które mogą być określone bezpośrednio przez osobę z zewnątrz.

Przykładem przedstawionym w kontekście CVE-2014-6271 są skrypty używane do analizowania plików dziennika. Mogą one mieć bardzo uzasadnioną potrzebę przekazywania niezaufanych danych w zmiennych środowiskowych. Oczywiście nazwa takiej zmiennej środowiskowej jest wybierana tak, aby nie miała żadnych negatywnych skutków.

Ale oto, co jest złego w tej szczególnej podatności na bash. Można go wykorzystać za pomocą dowolnej nazwy zmiennej. Jeśli utworzysz zmienną środowiskową o nazwie GET_REQUEST_TO_BE_PROCESSED_BY_MY_SCRIPT, nie spodziewałbyś się, że inny program oprócz własnego skryptu interpretuje zawartość tej zmiennej środowiskowej. Ale wykorzystując ten błąd bash, każda zmienna środowiskowa staje się wektorem ataku.

Zauważ, że nie oznacza to, że nazwy zmiennych środowiskowych powinny być tajne. Znajomość nazw zmiennych środowiskowych nie ułatwia ataku.

Jeśli program1połączenia, program2które z kolei wywołują program3, program1mogą przekazywać dane program3przez zmienne środowiskowe. Każdy program ma określoną listę zmiennych środowiskowych, które ustawia oraz określoną listę, na którą działa. Jeśli wybierzesz nazwę nieuznawaną przez program2, możesz przesyłać dane od program1do, program3nie martwiąc się, że będzie to miało negatywny wpływ na program2.

Osoba atakująca, która zna dokładne nazwy zmiennych eksportowanych przez program1i nazw zmiennych interpretowanych przez, program2nie może wykorzystać tej wiedzy do zmodyfikowania zachowania „programu2”, jeśli zestaw nazw nie zachodzi na siebie.

Ale to się zepsuło, gdyby program2był bashskrypt, ponieważ z powodu tego błędu bashinterpretowałby każdą zmienną środowiskową jako kod.


1
„każda zmienna środowiskowa staje się wektorem ataku” - tej części mi brakowało. Dzięki.
wrschneider

9

Wyjaśniono to w artykule, który połączyłeś ...

możesz utworzyć zmienne środowiskowe ze specjalnie spreparowanymi wartościami przed wywołaniem powłoki bash. Zmienne te mogą zawierać kod, który jest wykonywany natychmiast po wywołaniu powłoki.

Co oznacza, że ​​wywoływana przez bash funkcja wywołuje -c "echo this is a test"kod w pojedynczych cudzysłowach po jego wywołaniu.

Bash ma funkcje, choć w nieco ograniczonej implementacji, i możliwe jest umieszczenie tych funkcji bash w zmiennych środowiskowych. Ta usterka jest wywoływana, gdy dodatkowy kod zostanie dodany na końcu tych definicji funkcji (wewnątrz zmiennej enivronment).

Oznacza, że ​​opublikowany przykład kodu wykorzystuje fakt, że wywołany bash nie przestaje oceniać tego ciągu po wykonaniu przypisania. Przypisanie funkcji w tym przypadku.

Tak naprawdę, jak rozumiem, fragment kodu, który opublikowałeś, polega na tym, że umieszczając definicję funkcji przed kodem, który chcemy wykonać, można obejść niektóre mechanizmy bezpieczeństwa.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.