Что делает env x = '() {:;}; команда 'bash do и почему она небезопасна?

237

Похоже, в bash существует уязвимость (CVE-2014-6271): атака с помощью внедрения кода специально созданных переменных среды Bash

Я пытаюсь понять, что происходит, но я не совсем уверен, что понимаю это. Как echoвыполнить в одинарных кавычках?

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

РЕДАКТИРОВАТЬ 1 : исправленная система выглядит следующим образом:

$ 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

РЕДАКТИРОВАТЬ 2 : Существует связанная уязвимость / патч: CVE-2014-7169, который использует немного другой тест:

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

непропатченный вывод :

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

частично (ранняя версия) исправленный вывод :

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

исправлен вывод до CVE-2014-7169 включительно:

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

РЕДАКТИРОВАТЬ 3 : история продолжается с:

jippie
источник
Это не эхо, которое выполняется. это определение функции х. Если функция, определенная в x, выполняет скрытную скрытную работу, bash не может проверить возвращаемое значение, если функция x действительна. Обратите внимание, что функция пуста в тестовом коде. Непроверенное возвращаемое значение может привести к внедрению скрипта. Внедрение скрипта приводит к повышению привилегий, а повышение привилегий - к корневому доступу. Патч отключает создание x как функции
eyoung100
26
eyoung100, эхо не выполняется. Вы можете видеть, как он выполняется, потому что слово vulnerableпоявляется в выводе. Основная проблема заключается в том, что bash также выполняет синтаксический анализ и выполнение кода после определения функции. Посмотрите /bin/idчасть seclists.org/oss-sec/2014/q3/650 для другого примера.
Микель
4
Просто быстрый побочный комментарий. Red Hat сообщила, что выпущенный патч является лишь частичным и оставляет системы по-прежнему в опасности.
Питер
2
@ eyoung100 разница в том, что код внутри функции выполняется только тогда, когда переменная окружения явно вызывается. Код после определения функции выполняется каждый раз, когда начинается новый процесс Bash.
Дэвид Фаррелл
1
См. Stackoverflow.com/questions/26022248/… для получения дополнительной информации
Barmar

Ответы:

204

bash хранит экспортированные определения функций как переменные среды. Экспортируемые функции выглядят так:

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

То есть переменная окружения fooимеет буквальное содержимое:

() {  bar
}

Когда запускается новый экземпляр bash, он ищет эти специально созданные переменные окружения и интерпретирует их как определения функций. Вы даже можете написать один и увидеть, что он все еще работает:

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

К сожалению, разбор определений функций из строк (переменных среды) может иметь более широкий эффект, чем предполагалось. В не исправленных версиях он также интерпретирует произвольные команды, которые появляются после завершения определения функции. Это связано с недостаточными ограничениями в определении приемлемых функционально-подобных строк в среде. Например:

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

Обратите внимание, что эхо за пределами определения функции было неожиданно выполнено во время запуска bash. Определение функции - это всего лишь шаг к выполнению оценки и эксплуатации, само определение функции и используемая переменная окружения являются произвольными. Оболочка просматривает переменные окружения, видит foo, как будто она соответствует ограничениям, которые она знает о том, как выглядит определение функции, и она оценивает строку, непреднамеренно выполняя также эхо (которое может быть любой командой, злонамеренной или нет).

Это считается небезопасным, поскольку переменные, как правило, сами по себе не допускаются или не ожидают непосредственного вызова произвольного кода, содержащегося в них. Возможно, ваша программа устанавливает переменные окружения из ненадежного пользовательского ввода. Было бы весьма неожиданно, что этими переменными среды можно было манипулировать таким образом, чтобы пользователь мог выполнять произвольные команды без вашего явного намерения сделать это, используя эту переменную среды по такой причине, объявленной в коде.

Вот пример жизнеспособной атаки. Вы запускаете веб-сервер, который запускает уязвимую оболочку, где-то, как часть его жизни. Этот веб-сервер передает переменные среды в сценарий bash, например, если вы используете CGI, информация о HTTP-запросе часто включается в качестве переменных среды с веб-сервера. Например, HTTP_USER_AGENTможет быть установлено содержимое вашего пользовательского агента. Это означает, что если вы подделаете свой пользовательский агент, чтобы он был похож на '() {:; }; echo foo ', когда этот сценарий оболочки echo fooбудет запущен, будет выполнен. Опять же, echo fooможет быть что угодно, злое или нет.

Крис Даун
источник
3
Может ли это повлиять на любую другую оболочку, похожую на Bash, например, на Zsh?
Амелио Васкес-Рейна
3
@ user815423426 Нет, у zsh нет этой функции. В Ksh он есть, но реализован по-другому, я думаю, что функции могут передаваться только в очень узких обстоятельствах, только если оболочка разветвляется, а не через среду.
Жиль
20
@ user815423426 rc - это другая оболочка, которая передает функции в среде, но с переменной с именами с префиксом "fn_", и они интерпретируются только при вызове.
Стефан Шазелас
18
@ StéphaneChazelas - спасибо за сообщение об ошибке.
Охотник на оленей
13
@gnclmorais Вы имеете в виду, что вы бежите, export bar='() { echo "bar" ; }'; zsh -c barи он отображает, barа не zsh:1: command not found: bar? Вы уверены, что не путаете оболочку, которую вы вызываете, с оболочкой, которую вы используете для настройки теста?
Жиль
85

Это может помочь в дальнейшей демонстрации того, что происходит:

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

Если вы запускаете уязвимую оболочку, то, когда вы запускаете новую подоболочку (здесь, просто используя оператор bash), вы увидите, что произвольный код ( echo "pwned") сразу же выполняется как часть его инициации. По-видимому, оболочка видит, что переменная окружения (dummy) содержит определение функции, и оценивает определение, чтобы определить эту функцию в ее окружении (обратите внимание, что она не выполняет функцию: это выдает «hi».)

К сожалению, он не только оценивает определение функции, но и оценивает весь текст значения переменной среды, включая, возможно, вредоносные операторы, которые следуют за определением функции. Обратите внимание, что без начального определения функции переменная среды не будет оценена, она будет просто добавлена ​​в среду в виде текстовой строки. Как отметил Крис Даун, это особый механизм для реализации импорта экспортируемых функций оболочки.

Мы можем увидеть функцию, которая была определена в новой оболочке (и помечена как экспортированная там), и мы можем выполнить ее. Кроме того, пустышка не была импортирована как текстовая переменная:

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

Ни создание этой функции, ни что-либо, что бы она делала, если бы она выполнялась, не является частью эксплойта - это всего лишь средство, с помощью которого эксплойт выполняется. Дело в том, что если злоумышленник может предоставить вредоносный код, которому предшествует минимальное и неважное определение функции, в текстовой строке, которая помещается в экспортированную переменную среды, то он будет выполняться при запуске подоболочки, что является обычным событием. во многих сценариях. Кроме того, он будет выполнен с правами скрипта.

sdenham
источник
17
Хотя принятый ответ действительно говорит об этом, если вы внимательно его прочитаете, я нашел этот ответ еще более ясным и более полезным для понимания того, что проблема заключается в оценке определения (а не в выполнении самой функции).
natevw
1
почему в этом примере есть exportкоманда, а в других env? я думал env, что используется для определения переменных среды, которые будут вызываться при запуске другой оболочки bash. тогда как это работаетexport
Харис
До этого момента не было принято ответа. Я, вероятно, подожду еще пару дней, прежде чем принять один. Недостатком этого ответа является то, что он не разбивает исходную команду и не обсуждает, как перейти от исходной команды в вопросе к командам в этом ответе, показывая, что они идентичны. Кроме того, это хорошее объяснение.
Джиппи
@ralph - оба определения envи exportопределения среды экспорта, чтобы они были доступны в подоболочке. Проблема заключается в том, как эти экспортированные определения импортируются в среду подоболочки, а именно в механизм, который импортирует определения функций.
Сденхем
1
@ralph - envзапускает команду с некоторыми опциями и установленными переменными среды. Обратите внимание , что в оригинальных примерах вопроса, envустанавливает xв строку, и вызовы bash -cс помощью команды для запуска. Если вы это сделаете env x='foo' vim, Vim запустится, и там вы сможете вызвать его содержащую оболочку / окружение с помощью !echo $x, и он напечатает foo, но если вы затем выйдете и echo $x, он не будет определен, так как он существовал только во время работы vim. через envкоманду. exportКоманда вместо устанавливает постоянные значения в текущей среде , так что подоболочка запустить позже будет использовать их.
Гэри Фикслер,
72

Я написал это как повторение превосходного ответа Криса Дауна в стиле учебника.


В bash вы можете использовать такие переменные оболочки

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

По умолчанию эти переменные не наследуются дочерними процессами.

$ bash
$ echo $t

$ exit

Но если вы пометите их для экспорта, bash установит флаг, который означает, что они войдут в среду подпроцессов (хотя envpпараметр не очень заметен, mainв вашей программе на С есть три параметра: main(int argc, char *argv[], char *envp[])где последний массив указателей является массивом переменных оболочки с их определениями).

Итак, давайте экспортировать tследующим образом:

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

Принимая во внимание, что выше tбыло не определено в подоболочке, теперь оно появляется после того, как мы его экспортировали (используйте, export -n tесли вы хотите прекратить его экспорт).

Но функции в bash - это другое животное. Вы объявляете их так:

$ fn() { echo "test"; }

И теперь вы можете просто вызвать функцию, вызвав ее, как если бы это была другая команда оболочки:

$ fn
test
$

Еще раз, если вы создаете подоболочку, наша функция не экспортируется:

$ bash
$ fn
fn: command not found
$ exit

Мы можем экспортировать функцию с export -f:

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

Вот сложная часть: экспортированная функция наподобие fnпреобразуется в переменную окружения так же, как был описан наш экспорт переменной оболочки t. Этого не происходит, когда fnбыла локальная переменная, но после экспорта мы можем видеть ее как переменную оболочки. Однако вы также можете иметь обычную (то есть, не функциональную) переменную оболочки с тем же именем. Bash различает на основе содержимого переменной:

$ echo $fn

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

Теперь мы можем использовать envдля отображения всех переменных оболочки, помеченных для экспорта, и отображаются как обычные, так fnи функции fn:

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

Под-оболочка будет принимать оба определения: одно как обычная переменная, а другое как функция:

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

Вы можете определить, fnкак мы делали выше, или напрямую как обычное присвоение переменной:

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

Обратите внимание, что это необычная вещь! Обычно мы определяем функцию, fnкак мы делали выше, с помощью fn() {...}синтаксиса. Но поскольку bash экспортирует его через среду, мы можем «сократить путь» прямо к обычному определению, приведенному выше. Обратите внимание, что (вопреки вашей интуиции, возможно) это не приводит к появлению новой функции, fnдоступной в текущей оболочке. Но если вы породите ** sub ** shell, то это произойдет.

Давайте отменим экспорт функции fnи оставим новый обычный fn(как показано выше) без изменений.

$ export -nf fn

Теперь функция fnбольше не экспортируется, но есть обычная переменная fn, и она () { echo "direct" ; }в ней содержится .

Теперь, когда подоболочка видит обычную переменную, которая начинается с ()нее, остальная часть интерпретируется как определение функции. Но это только тогда, когда начинается новая оболочка. Как мы видели выше, простое определение обычной переменной оболочки, начинающейся с (), не приводит к тому, что она ведет себя как функция. Вы должны начать подоболочку.

А теперь ошибка "Shellshock":

Как мы только что увидели, когда новая оболочка принимает определение обычной переменной, начиная с ()нее, она интерпретируется как функция. Однако, если после закрывающей скобки задано больше данных, определяющих функцию, она также выполняет все, что там есть.

Это требования, еще раз:

  1. Новый Bash появляется
  2. Переменная окружения принята
  3. Эта переменная окружения начинается с "()" и затем содержит тело функции внутри фигурных скобок, а затем имеет команды после

В этом случае уязвимый bash выполнит последние команды.

Пример:

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

Обычная экспортируемая переменная exбыла передана в подоболочку, которая была интерпретирована как функция, exно завершающие команды были выполнены ( this is bad) как порожденная подоболочка.


Объясняя гладкий однострочный тест

Популярный однострочный документ для тестирования уязвимости Shellshock - это тот, который упоминается в вопросе @ jippie:

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

Вот разбивка: во-первых, :в Bash это просто сокращение для true. trueи :оба оценивают (как вы уже догадались) в bash:

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

Во-вторых, envкоманда (также встроенная в bash) печатает переменные среды (как мы видели выше), но также может использоваться для запуска одной команды с экспортированной переменной (или переменными), заданной для этой команды, и bash -cзапускает одну команду из ее командная строка:

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

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

Объединяя все эти вещи вместе, мы можем запустить bash как команду, дать ему какую-то глупую вещь (например bash -c echo this is a test) и экспортировать переменную, которая начинается с ()так, чтобы подоболочка интерпретировала ее как функцию. Если присутствует шеллшок, он также немедленно выполнит любые завершающие команды в подоболочке. Поскольку функция, которую мы передаем, не имеет отношения к нам (но должна анализировать!), Мы используем самую короткую допустимую возможную функцию:

$ f() { :;}
$ f
$ 

Функция fздесь просто выполняет :команду, которая возвращает true и завершает работу. Теперь добавьте к этому некоторую «злую» команду и экспортируйте обычную переменную в подоболочку, и вы выиграете. Вот еще одна строка:

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

Так xэкспортируется как обычная переменная с простой допустимой функцией с echo vulnerableприкрепленным до конца. Это передается в bash, и bash интерпретирует xкак функцию (о которой мы не заботимся), а затем, возможно, выполняет echo vulnerableif shellshock.

Мы могли бы немного укоротить одну строку, удалив this is a testсообщение:

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

Это не беспокоит, this is a testно :снова запускает команду без вывода сообщений. (Если вы отключите его, -c :то будете сидеть в подоболочке и выходить вручную.) Возможно, наиболее удобной версией будет эта:

$ env x='() { :;}; echo vulnerable' bash -c "echo If you see the word vulnerable above, you are vulnerable to shellshock"
Fixee
источник
12
Хорошее объяснение. Этот вопрос получает много мнений (вероятно, не все так же искусны в bash, как другие), и я считаю, что никто еще не потратил пару слов на то, что на { :;};самом деле говорит. Это было бы хорошим дополнением к вашему ответу на мой взгляд. Можете объяснить, как вы переходите от своего примера к исходной команде в вопросе?
Джиппи
20

Если вы можете передавать произвольные переменные окружения в программу, вы можете заставить ее делать что угодно, загружая библиотеки по вашему выбору. В большинстве случаев это не считается уязвимостью в программе, получающей эти переменные среды, а скорее в механизме, с помощью которого посторонний может передавать произвольные переменные среды.

Однако CVE-2014-6271 отличается.

Нет ничего плохого в наличии ненадежных данных в переменной среды. Нужно просто убедиться, что он не помещен ни в одну из переменных среды, которые могут изменить поведение программы. Поместив немного более абстрактно, для конкретного вызова вы можете создать белый список имен переменных среды, которые могут быть заданы напрямую посторонним.

Примером, который был предложен в контексте CVE-2014-6271, являются сценарии, используемые для анализа файлов журналов. У них может быть вполне законная потребность передавать ненадежные данные в переменных среды. Конечно, имя такой переменной среды выбирается таким образом, чтобы оно не оказывало негативного влияния.

Но вот что плохо в этой конкретной уязвимости bash. Его можно использовать через любое имя переменной. Если вы создадите переменную среды с именем GET_REQUEST_TO_BE_PROCESSED_BY_MY_SCRIPT, вы не ожидаете, что любая другая программа, кроме вашего собственного скрипта, будет интерпретировать содержимое этой переменной среды. Но, используя эту ошибку, каждая переменная среды становится вектором атаки.

Обратите внимание, что это не означает, что имена переменных среды должны быть секретными. Знание имен переменных среды не облегчает атаку.

Если program1вызовы, program2которые в свою очередь вызывают program3, то program1могут передавать данные program3через переменные среды. Каждая программа имеет определенный список переменных среды, которые она устанавливает, и определенный список, на который она действует. Если вы выбрали имя, которое не было распознано program2, вы можете передавать данные из него program1в систему, program3не беспокоясь о том, что это может иметь какие-либо неблагоприятные последствия program2.

Злоумышленник, зная точные имена переменных, экспортируемых с помощью, program1и имена переменных, с которыми он интерпретируется, program2не может использовать эти знания для изменения поведения «program2», если нет совпадения между набором имен.

Но это сломалось, если бы program2был bashскрипт, потому что из-за этой ошибки bashкаждая переменная среды интерпретировалась как код.

kasperd
источник
1
«Каждая переменная среды становится вектором атаки» - это та часть, которую мне не хватало. Благодарю.
wrschneider
9

Это объясняется в статье, которую вы связали ...

Вы можете создавать переменные окружения со специально созданными значениями перед вызовом оболочки bash. Эти переменные могут содержать код, который выполняется, как только вызывается оболочка.

Это означает, что bash, вызываемый с помощью, -c "echo this is a test"выполняет код в одинарных кавычках, когда он вызывается.

Bash имеет функции, хотя и в несколько ограниченной реализации, и эти функции bash можно поместить в переменные окружения. Этот недостаток срабатывает при добавлении дополнительного кода в конец этих определений функций (внутри переменной enivronment).

Означает, что пример кода, который вы опубликовали, использует тот факт, что вызванный bash не прекращает оценку этой строки после выполнения присваивания. Назначение функции в этом случае.

На самом деле, особая вещь в фрагменте кода, который вы разместили, насколько я понимаю, заключается в том, что при использовании определения функции перед кодом, который мы хотим выполнить, можно обойти некоторые механизмы безопасности.

Bananguin
источник