преамбула
Во-первых, я бы сказал, что это неправильный способ решения проблемы. Это все равно, что сказать: « Вы не должны убивать людей, потому что иначе вы попадете в тюрьму ».
Точно так же вы не заключаете в кавычки свою переменную, потому что иначе вы вводите уязвимости безопасности. Вы цитируете свои переменные, потому что это не правильно (но если страх тюрьмы может помочь, почему бы и нет).
Небольшое резюме для тех, кто только что прыгнул на поезд.
В большинстве оболочек расширение переменной без кавычек (хотя это (и остальная часть этого ответа) также относится к подстановке команд ( `...`
или $(...)
) и арифметическому расширению ( $((...))
или $[...]
)) имеет совершенно особое значение. Лучший способ описать это так: это все равно что вызывать некий неявный оператор split + globb .
cmd $var
на другом языке было бы написано что-то вроде:
cmd(glob(split($var)))
$var
сначала разбивается на список слов в соответствии со сложными правилами, включающими $IFS
специальный параметр ( часть разбиения ), а затем каждое слово, полученное в результате этого разбиения, рассматривается как шаблон, который расширяется до списка файлов, которые ему соответствуют ( часть глобуса ) ,
Например, если он $var
содержит *.txt,/var/*.xml
и $IFS
содержит ,
, cmd
будет вызван с несколькими аргументами, первый из которых будет, cmd
а следующие - txt
файлы в текущем каталоге и xml
файлы в /var
.
Если вы хотите вызвать cmd
только с двумя буквальными аргументами cmd
и *.txt,/var/*.xml
, вы бы написали:
cmd "$var"
который будет на вашем другом более знакомом языке:
cmd($var)
Что мы подразумеваем под уязвимостью в оболочке ?
В конце концов, с незапамятных времен известно, что сценарии оболочки не должны использоваться в контекстах, чувствительных к безопасности. Конечно, хорошо, если оставить переменную без кавычек, это ошибка, но это не может принести столько вреда, не так ли?
Что ж, несмотря на тот факт, что кто-то скажет вам, что сценарии оболочки никогда не должны использоваться для веб-CGI, или что, к счастью, большинство систем в настоящее время не допускают сценарии оболочки setuid / setgid, это единственная вещь, которая вызывает шеллшок (удаленно эксплуатируемая ошибка bash, которая сделала заголовки в сентябре 2014 г.) показали, что скорлупа по - прежнему широко используется там , где они , вероятно , не следует: в CGIs, в DHCP - клиент скрипты ловушек, в sudoers команд, вызываемых с помощью (если не как ) Setuid команды ...
Иногда по незнанию. Например, system('cmd $PATH_INFO')
в php
/ perl
/ python
CGI-скрипте вызывается оболочка для интерпретации этой командной строки (не говоря уже о том, что он cmd
сам может быть сценарием оболочки и его автор, возможно, никогда не ожидал, что он будет вызван из CGI).
У вас есть уязвимость, когда есть путь для повышения привилегий, то есть когда кто-то (назовем его атакующим ) может сделать то, для чего он не предназначен.
Неизменно это означает, что злоумышленник предоставляет данные, то есть данные, обрабатываемые привилегированным пользователем / процессом, который непреднамеренно делает то, что не должен делать, в большинстве случаев из-за ошибки.
По сути, у вас есть проблема, когда ваш ошибочный код обрабатывает данные под контролем злоумышленника .
Теперь не всегда очевидно, откуда эти данные могут поступать, и зачастую трудно сказать, удастся ли вашему коду обработать ненадежные данные.
Что касается переменных, то в случае сценария CGI совершенно очевидно, что данные представляют собой параметры CGI GET / POST и такие вещи, как cookie, path, host ... parameters.
Для сценария setuid (запускается как один пользователь при вызове другим) это аргументы или переменные окружения.
Другой очень распространенный вектор - это имена файлов. Если вы получаете список файлов из каталога, возможно, что файлы были установлены злоумышленником .
В связи с этим даже при появлении интерактивной оболочки вы можете быть уязвимы (например, при обработке файлов в /tmp
или ~/tmp
).
Даже a ~/.bashrc
может быть уязвимым (например, bash
будет интерпретировать его при вызове ssh
для запуска ForcedCommand
аналогичного git
развертывания сервера с некоторыми переменными под контролем клиента).
Теперь скрипт не может быть вызван напрямую для обработки ненадежных данных, но он может быть вызван другой командой, которая это делает. Или ваш неправильный код может быть скопирован в скрипты, которые делают (вы 3 года подряд или один из ваших коллег). Одним из мест, где это особенно важно, являются ответы на сайтах вопросов и ответов, так как вы никогда не узнаете, где могут оказаться копии вашего кода.
Вниз к делу; насколько плохо?
Оставление переменной (или подстановки команд) без кавычек - безусловно, источник номер один уязвимостей безопасности, связанных с шелл-кодом. Отчасти потому, что эти ошибки часто приводят к уязвимостям, а также потому, что часто можно увидеть переменные без кавычек.
На самом деле, при поиске уязвимостей в шелл-коде, первое, что нужно сделать, это искать переменные без кавычек. Его легко обнаружить, часто это хороший кандидат, как правило, легко отследить до данных, контролируемых злоумышленниками.
Существует бесконечное количество способов, которыми переменная без кавычек может превратиться в уязвимость. Я просто приведу несколько общих тенденций здесь.
Раскрытие информации
Большинство людей сталкиваются с ошибками, связанными с переменными без кавычек из-за разделенной части (например, в настоящее время файлы имеют пробелы в своих именах, а пробел находится в значении по умолчанию IFS). Многие люди будут упускать из виду
глобус часть. Часть шара по меньшей мере так же опасна, как и
разделенная часть.
Глобализация, выполняемая на внешнем входе, который не подвергается контролю, означает, что злоумышленник может заставить вас прочитать содержимое любого каталога.
В:
echo You entered: $unsanitised_external_input
если $unsanitised_external_input
содержит /*
, это означает, что злоумышленник может видеть содержимое /
. Ничего страшного. Тем не менее, становится интереснее то, /home/*
что дает вам список имен пользователей на машине /tmp/*
, /home/*/.forward
для подсказок о других опасных практиках, /etc/rc*/*
для включенных служб ... Не нужно называть их по отдельности. Значение /*
/*/* /*/*/*...
просто перечислит всю файловую систему.
Уязвимости отказа в обслуживании.
Взяв предыдущий случай слишком далеко, мы получили DoS.
Фактически, любая переменная без кавычек в контексте списка с неанизированным вводом является, по крайней мере, уязвимостью DoS.
Даже опытные сценаристы оболочки обычно забывают цитировать такие вещи, как:
#! /bin/sh -
: ${QUERYSTRING=$1}
:
это команда no-op. Что возможно могло пойти не так?
Это означало , чтобы назначить $1
для $QUERYSTRING
если $QUERYSTRING
было отключено. Это быстрый способ сделать CGI-скрипт доступным из командной строки.
Это $QUERYSTRING
все еще расширяется, и поскольку он не заключен в кавычки, вызывается оператор split + glob .
Теперь есть некоторые глобусы, которые особенно дороги в расширении. /*/*/*/*
Один достаточно плохо , как это означает , что листинг каталогов до 4 -х уровней вниз. Помимо работы с диском и процессором, это означает хранение десятков тысяч путей к файлам (здесь 40 КБ на минимальной виртуальной машине сервера, из которых 10 КБ каталогов).
Теперь /*/*/*/*/../../../../*/*/*/*
означает 40k x 10k и этого
/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*
достаточно, чтобы поставить на колени даже самую мощную машину.
Попробуйте сами (хотя будьте готовы к поломке или зависанию вашей машины):
a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'
Конечно, если код:
echo $QUERYSTRING > /some/file
Затем вы можете заполнить диск.
Просто выполните поиск в Google по shell cgi или bash cgi или ksh cgi , и вы найдете несколько страниц, которые покажут вам, как писать CGI в оболочках. Обратите внимание, что половина тех, которые обрабатывают параметры, уязвимы.
Даже собственный Дэвид Корн
уязвим (посмотрите на обработку куки).
до произвольных уязвимостей при выполнении кода
Выполнение произвольного кода является наихудшим типом уязвимости, поскольку, если злоумышленник может выполнить какую-либо команду, нет предела тому, что он может сделать.
Это обычно разделенная часть, которая ведет к этим. Это разделение приводит к тому, что командам передается несколько аргументов, когда ожидается только один. В то время как первый из них будет использоваться в ожидаемом контексте, другие будут в другом контексте, поэтому потенциально интерпретируются по-разному. Лучше с примером:
awk -v foo=$external_input '$2 == foo'
Здесь предполагалось назначить содержимое
$external_input
переменной оболочки foo
awk
переменной.
В настоящее время:
$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux
Второе слово, полученное в результате разбиения, $external_input
не присваивается, foo
а рассматривается как awk
код (здесь, который выполняет произвольную команду:) uname
.
Это особенно проблема для команд , которые могут выполняться другие команды ( awk
, env
, sed
(GNU один) perl
, find
...) , особенно с вариантами GNU (которые принимают параметры после аргументов). Иногда вы не подозреваете, что команды могут выполнять другие, такие как ksh
, bash
или zsh
, [
или printf
...
for file in *; do
[ -f $file ] || continue
something-that-would-be-dangerous-if-$file-were-a-directory
done
Если мы создадим каталог с именем x -o yes
, тогда тест станет положительным, потому что это абсолютно другое условное выражение, которое мы оцениваем.
Хуже того, если мы создадим файл с именем x -a a[0$(uname>&2)] -gt 1
, по крайней мере, со всеми реализациями ksh (который включает в себя sh
большинство коммерческих Unices и некоторые BSD), который выполняется, uname
потому что эти оболочки выполняют арифметическую оценку операторов числового сравнения [
команды.
$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux
То же самое с bash
именем файла, как x -a -v a[0$(uname>&2)]
.
Конечно, если они не могут получить произвольное выполнение, атакующий может согласиться на меньший ущерб (который может помочь получить произвольное выполнение). Любая команда, которая может записывать файлы или изменять разрешения, владельца или иметь любой основной или побочный эффект, может быть использована.
Все виды вещей могут быть сделаны с именами файлов.
$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done
И вы в итоге делаете ..
доступным для записи (рекурсивно с GNU
chmod
).
Сценарии, выполняющие автоматическую обработку файлов в общедоступных областях, например /tmp
, должны быть написаны очень осторожно.
Что о [ $# -gt 1 ]
Это то, что я нахожу раздражающим. Некоторые люди не понимают, насколько проблематичным может быть конкретное расширение, чтобы решить, могут ли они опустить кавычки.
Это как сказать. Эй, похоже, на него $#
не распространяется оператор split + glob, давайте попросим оболочку split + glob . Или , давайте напишем неправильный код только потому, что ошибка вряд ли будет устранена .
Насколько это маловероятно? ОК, $#
(или $!
, $?
или какая - либо арифметическая замещение) может содержать только цифры (или -
для некоторых) , так что Глоб часть выходит. Чтобы разделенная часть могла что-то делать, нам нужно $IFS
только содержать цифры (или -
).
Некоторые оболочки $IFS
могут быть унаследованы от окружения, но если окружение небезопасно, игра все равно окончена.
Теперь, если вы напишите такую функцию:
my_function() {
[ $# -eq 2 ] || return
...
}
Это означает, что поведение вашей функции зависит от контекста, в котором она вызывается. Или, другими словами, $IFS
становится одним из входов к нему. Строго говоря, когда вы пишете API-документацию для своей функции, она должна выглядеть примерно так:
# my_function
# inputs:
# $1: source directory
# $2: destination directory
# $IFS: used to split $#, expected not to contain digits...
И код, вызывающий вашу функцию, должен убедиться, что $IFS
она не содержит цифр. Все из-за того, что вам не хотелось печатать эти две двойные кавычки.
Теперь, чтобы эта [ $# -eq 2 ]
ошибка стала уязвимостью, вам нужно как-то, чтобы значение $IFS
оказалось под контролем злоумышленника . Вполне возможно, что это не произойдет, если злоумышленнику не удастся использовать другую ошибку.
Это не неслыханно, хотя. Распространенный случай - когда люди забывают очистить данные, прежде чем использовать их в арифметическом выражении. Мы уже видели выше, что он может разрешить выполнение произвольного кода в некоторых оболочках, но во всех из них он позволяет
злоумышленнику дать любой переменной целочисленное значение.
Например:
n=$(($1 + 1))
if [ $# -gt 2 ]; then
echo >&2 "Too many arguments"
exit 1
fi
А со $1
значением «а» (IFS=-1234567890)
эта арифметическая оценка имеет побочный эффект настроек IFS, и следующая [
команда не выполняется, что означает, что проверка на слишком много аргументов обойдена.
Как насчет того, когда оператор split + glob не вызывается?
Есть еще один случай, когда кавычки нужны вокруг переменных и других расширений: когда это используется в качестве шаблона.
[[ $a = $b ]] # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac
не испытывают ли $a
и $b
те же ( за исключением zsh
) , но если $a
соответствует шаблону в $b
. И вам нужно заключать в кавычки, $b
если вы хотите сравнивать как строки (то же самое в "${a#$b}"
или "${a%$b}"
или "${a##*$b*}"
где $b
следует заключать в кавычки, если это не следует воспринимать как образец).
Это означает, что он [[ $a = $b ]]
может возвращать true в случаях, когда $a
он отличается от $b
(например, когда $a
есть anything
и $b
есть *
), или может возвращать false, когда они идентичны (например, когда оба $a
и $b
являются [a]
).
Это может сделать для уязвимости безопасности? Да, как любая ошибка. Здесь злоумышленник может изменить поток логического кода вашего скрипта и / или нарушить предположения, которые делает ваш скрипт. Например, с кодом вроде:
if [[ $1 = $2 ]]; then
echo >&2 '$1 and $2 cannot be the same or damage will incur'
exit 1
fi
Атакующий может обойти проверку, пройдя мимо '[a]' '[a]'
.
Теперь, если ни это сопоставление с образцом, ни оператор split + glob не применимы, какова опасность оставить переменную без кавычек?
Я должен признать, что я пишу:
a=$b
case $a in...
Там цитирование не вредит, но не является строго необходимым.
Тем не менее, одним из побочных эффектов пропуска кавычек в этих случаях (например, в ответах на вопросы и ответы) является то, что он может отправлять неправильное сообщение новичкам: может быть, все в порядке, не заключая в кавычки переменные .
Например, они могут начать думать, что если a=$b
все в порядке, то export a=$b
это будет так же хорошо (что не во многих оболочках, а в аргументах export
команды и в контексте списка) или env a=$b
.
Как насчет zsh
?
zsh
действительно исправил большинство из этих дизайнерских неудобств. В zsh
(по крайней мере, когда не в режиме эмуляции sh / ksh), если вы хотите расщепление , или подстановку , или сопоставление с образцом , вы должны явно запросить это: $=var
расщепление и $~var
выделение или для содержимого переменной, которая будет рассматриваться как шаблон.
Тем не менее, разделение (но не глобализация) все еще выполняется неявно при замене команд без кавычек (как в echo $(cmd)
).
Кроме того, иногда нежелательным побочным эффектом отсутствия кавычек является удаление тары . zsh
Поведение похоже на то , что вы можете достичь в других оболочках, отключив подстановку вообще (с set -f
) и расщепление (с IFS=''
). Еще в:
cmd $var
Не будет split + glob , но если $var
он пуст, вместо получения одного пустого аргумента, cmd
он вообще не получит аргумента.
Это может вызвать ошибки (как очевидное [ -n $var ]
). Это может нарушить ожидания и предположения сценария и стать причиной уязвимостей, но я не могу сейчас привести не слишком надуманный пример).
А когда вы делаете нужен раскол + Glob оператор?
Да, обычно вы хотите оставить свою переменную без кавычек. Но затем вам нужно убедиться, что вы правильно настроили операторы split и glob, прежде чем использовать его. Если вам нужна только разделенная часть, а не глобальная часть (что имеет место в большинстве случаев), вам нужно отключить глобализацию ( set -o noglob
/ set -f
) и исправить $IFS
. В противном случае вы также создадите уязвимости (как в приведенном выше примере CGI Дэвида Корна).
Заключение
Короче говоря, оставлять в кавычках переменную (или подстановку команд, или арифметическое расширение) без кавычек действительно может быть очень опасно, особенно когда это делается в неправильных контекстах, и очень трудно понять, что это за неправильные контексты.
Это одна из причин, почему это считается плохой практикой .
Спасибо за чтение до сих пор. Если это идет по твоей голове, не волнуйся. Нельзя ожидать, что все поймут все последствия написания своего кода так, как они его пишут. Вот почему у нас есть
рекомендации по хорошей практике , поэтому они могут быть выполнены, не обязательно понимая, почему.
(и в случае, если это еще не очевидно, пожалуйста, избегайте написания кода, чувствительного к безопасности, в оболочках).
И, пожалуйста, укажите ваши переменные в ваших ответах на этом сайте!
And В ksh93
и pdksh
и производных расширение фигурных скобок также выполняется, если не отключено ksh93
глобирование (в случае версий до ksh93u +, даже если braceexpand
опция отключена).