Является ли поведение уязвимости Shellshock в Bash документированным или вообще преднамеренным?
Недавняя уязвимость CVE-2014-6271 в том, как Bash интерпретирует переменные среды . Эксплоит полагается на Bash, анализируя некоторые объявления переменных среды как определения функций, но затем продолжая выполнение кода, следующего за определением:
$ x='() { echo i do nothing; }; echo vulnerable' bash -c ':'
vulnerable
Но я не понимаю. Я ничего не смог найти в руководстве Bash о интерпретации переменных среды как функций вообще (кроме наследования функций, которые различны). Действительно, правильное определение имени функции просто рассматривается как значение:
$ x='y() { :; }' bash -c 'echo $x'
y() { :; }
Но коррумпированный ничего не печатает:
$ x='() { :; }' bash -c 'echo $x'
$ # Nothing but newline
Коррумпированная функция не называется, и поэтому я не могу просто назвать ее. Является ли эта уязвимость чистой ошибкой реализации, или здесь есть намеченная функция, которую я просто не вижу?
Update
В сообщении Barmar, я предположил, что имя функции было именем параметра:
$ n='() { echo wat; }' bash -c 'n'
wat
Кого я мог бы поклясться, что раньше я пытался, но я думаю, что я не очень старался. Теперь это повторяемо. Здесь немного больше тестов:
$ env n='() { echo wat; }; echo vuln' bash -c 'n'
vuln
wat
$ env n='() { echo wat; }; echo $1' bash -c 'n 2' 3 -- 4
wat
... поэтому очевидно, что аргументы не заданы во время выполнения эксплойта.
В любом случае, основной ответ на мой вопрос: да, вот как Bash реализует унаследованные функции.
Ответы
Ответ 1
Это похоже на ошибку реализации.
По-видимому, способ экспорта экспортируемых функций в bash
заключается в том, что они используют специально отформатированные переменные среды. Если вы экспортируете функцию:
f() { ... }
он определяет переменную среды, например:
f='() { ... }'
Что, вероятно, происходит, когда новая оболочка видит переменную среды, значение которой начинается с ()
, она добавляет имя переменной и выполняет полученную строку. Ошибка заключается в том, что это включает в себя выполнение чего-либо после определения функции.
Обнаруженное исправление, по-видимому, должно анализировать результат, чтобы увидеть, действительно ли оно является корректным определением функции. Если нет, он выводит предупреждение о попытке определения недопустимой функции.
Эта статья подтверждает мое объяснение причины ошибки. В нем также подробно описывается, как исправление устраняет это: не только они более тщательно анализируют значения, но и переменные, которые используются для передачи экспортируемых функций, следуют специальному соглашению об именах. Это соглашение об именах отличается от того, которое используется для переменных среды, созданных для сценариев CGI, поэтому HTTP-клиент никогда не должен иметь возможность войти в эту дверь.
Ответ 2
Следующее:
x='() { echo I do nothing; }; echo vulnerable' bash -c 'typeset -f'
печатает
vulnerable
x ()
{
echo I do nothing
}
declare -fx x
кажется, чем Bash, после анализа x=...
, обнаружил его как функцию, экспортировал его, увидел declare -fx x
и разрешил выполнение команды после объявления.
echo vulnerable
x='() { x; }; echo vulnerable' bash -c 'typeset -f'
печатает:
vulnerable
x ()
{
echo I do nothing
}
и запустите x
x='() { x; }; echo Vulnerable' bash -c 'x'
печатает
Vulnerable
Segmentation fault: 11
segfaults - бесконечные рекурсивные вызовы
Он не переопределяет уже определенную функцию
$ x() { echo Something; }
$ declare -fx x
$ x='() { x; }; echo Vulnerable' bash -c 'typeset -f'
печатает:
x ()
{
echo Something
}
declare -fx x
например. x остается прежней (правильно) определенной функцией.
Для Bash 4.3.25(1)-release
уязвимость закрыта, поэтому
x='() { echo I do nothing; }; echo Vulnerable' bash -c ':'
печатает
bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
но - что странно (по крайней мере для меня)
x='() { x; };' bash -c 'typeset -f'
STILL PRINTS
x ()
{
x
}
declare -fx x
и
x='() { x; };' bash -c 'x'
так что STILL принимает странное определение функции...
Ответ 3
Мне кажется, стоит посмотреть на код Bash. Патч дает некоторое представление о проблеме. В частности,
*** ../bash-4.3-patched/variables.c 2014-05-15 08:26:50.000000000 -0400
--- variables.c 2014-09-14 14:23:35.000000000 -0400
***************
*** 359,369 ****
strcpy (temp_string + char_index + 1, string);
! if (posixly_correct == 0 || legal_identifier (name))
! parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);
!
! /* Ancient backwards compatibility. Old versions of bash exported
! functions like name()=() {...} */
! if (name[char_index - 1] == ')' && name[char_index - 2] == '(')
! name[char_index - 2] = '\0';
if (temp_var = find_function (name))
--- 364,372 ----
strcpy (temp_string + char_index + 1, string);
! /* Don't import function names that are invalid identifiers from the
! environment, though we still allow them to be defined as shell
! variables. */
! if (legal_identifier (name))
! parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_FUNCDEF|SEVAL_ONECMD);
if (temp_var = find_function (name))
Когда Bash экспортирует функцию, она отображается как переменная среды, например:
$ foo() { echo 'hello world'; }
$ export -f foo
$ cat /proc/self/environ | tr '\0' '\n' | grep -A1 foo
foo=() { echo 'hello world'
}
Когда новый Bash процесс находит функцию, определенную таким образом в своей среде, она вычисляет код в переменной с помощью parse_and_execute()
. Для обычного, не вредоносного кода его выполнение просто определяет функцию в Bash и перемещается. Однако, поскольку он передан в общую функцию выполнения, Bash будет правильно анализировать и выполнять дополнительный код, определенный в этой переменной после определения функции.
Вы можете видеть, что в новом коде добавлен флаг с именем SEVAL_ONECMD
, который сообщает Bash оценивать только первую команду (то есть определение функции) и SEVAL_FUNCDEF
, чтобы разрешать только определения functio0n.
Ответ 4
В отношении вашего вопроса о документации обратите внимание на документацию командной строки для команды env
, что изучение синтаксиса показывает, что env
работает как документально.
- Есть, возможно, 4 возможных варианта
- Необязательный дефис как синоним для
-i
(для обратной совместимости я предполагаю)
- Нулевая или более NAME = пары VALUE. Это переменные назначения, которые могут включать определения функций.
- Обратите внимание, что не требуется точка с запятой (;) между или после назначений.
- Последний аргумент может быть одной командой, за которой следуют ее аргументы. Он будет работать с любыми разрешениями, предоставленными используемому логину. Безопасность контролируется путем ограничения разрешений для пользователя входа и установки разрешений на пользовательские исполняемые файлы, так что пользователи, отличные от исполняемого владельца, могут читать и выполнять программу, а не изменять ее.
[ [email protected]:~ ] env --help
Usage: env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]
Set each NAME to VALUE in the environment and run COMMAND.
-i, --ignore-environment start with an empty environment
-u, --unset=NAME remove variable from the environment
--help display this help and exit
--version output version information and exit
A mere - implies -i. If no COMMAND, print the resulting environment.
Report env bugs to [email protected]
GNU coreutils home page: <http://www.gnu.org/software/coreutils/>
General help using GNU software: <http://www.gnu.org/gethelp/>
Report env translation bugs to <http://translationproject.org/team/>