Если вы следили за unix.stackexchange.com какое-то время, вы должны надеяться, что уже знаете, что оставление переменной без кавычек в контексте списка (как в echo $var
) в оболочках Bourne / POSIX (исключение zsh) имеет очень особое значение и не должно быть сделано, если у вас нет очень веских причин.
Это обсуждается в ряде Q & A здесь (примеры: ? Почему мой заслонку сценарий оболочки на пробел или другие специальные символы , Когда двойные кавычки необходимо? , Расширение переменной оболочки и эффект Глоб и раскола на нем , Цитируется расширение строки без кавычек)
Это имело место с момента первоначального выпуска оболочки Bourne в конце 70-х годов и не было изменено оболочкой Korn (одно из самых больших сожалений Дэвида Корна (вопрос № 7) ) или bash
которая в основном копировала оболочку Korn, и это как это было указано в POSIX / Unix.
Теперь мы все еще видим здесь несколько ответов и даже время от времени публично публикуемый шелл-код, где переменные не заключены в кавычки. Вы бы подумали, что люди уже узнали бы.
По моему опыту, в основном есть 3 типа людей, которые опускают в кавычки свои переменные:
начинающих. Это может быть оправдано тем, что это совершенно не интуитивный синтаксис. И наша роль на этом сайте - обучать их.
забывчивые люди.
люди, которые не убеждены даже после многократного избиения, которые думают, что, конечно, автор оболочки Bourne не намеревался цитировать все наши переменные .
Может быть, мы сможем убедить их, если выявим риск, связанный с таким поведением.
Что хуже всего, чем может случиться, если вы забудете процитировать свои переменные. Действительно ли это , что плохо?
О какой уязвимости мы говорим здесь?
В каких случаях это может быть проблемой?
Ответы:
преамбула
Во-первых, я бы сказал, что это неправильный способ решения проблемы. Это все равно, что сказать: « Вы не должны убивать людей, потому что иначе вы попадете в тюрьму ».
Точно так же вы не заключаете в кавычки свою переменную, потому что иначе вы вводите уязвимости безопасности. Вы цитируете свои переменные, потому что это не правильно (но если страх тюрьмы может помочь, почему бы и нет).
Небольшое резюме для тех, кто только что прыгнул на поезд.
В большинстве оболочек расширение переменной без кавычек (хотя это (и остальная часть этого ответа) также относится к подстановке команд (
`...`
или$(...)
) и арифметическому расширению ($((...))
или$[...]
)) имеет совершенно особое значение. Лучший способ описать это так: это все равно что вызывать некий неявный оператор split + globb .на другом языке было бы написано что-то вроде:
$var
сначала разбивается на список слов в соответствии со сложными правилами, включающими$IFS
специальный параметр ( часть разбиения ), а затем каждое слово, полученное в результате этого разбиения, рассматривается как шаблон, который расширяется до списка файлов, которые ему соответствуют ( часть глобуса ) ,Например, если он
$var
содержит*.txt,/var/*.xml
и$IFS
содержит,
,cmd
будет вызван с несколькими аргументами, первый из которых будет,cmd
а следующие -txt
файлы в текущем каталоге иxml
файлы в/var
.Если вы хотите вызвать
cmd
только с двумя буквальными аргументамиcmd
и*.txt,/var/*.xml
, вы бы написали:который будет на вашем другом более знакомом языке:
Что мы подразумеваем под уязвимостью в оболочке ?
В конце концов, с незапамятных времен известно, что сценарии оболочки не должны использоваться в контекстах, чувствительных к безопасности. Конечно, хорошо, если оставить переменную без кавычек, это ошибка, но это не может принести столько вреда, не так ли?
Что ж, несмотря на тот факт, что кто-то скажет вам, что сценарии оболочки никогда не должны использоваться для веб-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). Многие люди будут упускать из виду глобус часть. Часть шара по меньшей мере так же опасна, как и разделенная часть.
Глобализация, выполняемая на внешнем входе, который не подвергается контролю, означает, что злоумышленник может заставить вас прочитать содержимое любого каталога.
В:
если
$unsanitised_external_input
содержит/*
, это означает, что злоумышленник может видеть содержимое/
. Ничего страшного. Тем не менее, становится интереснее то,/home/*
что дает вам список имен пользователей на машине/tmp/*
,/home/*/.forward
для подсказок о других опасных практиках,/etc/rc*/*
для включенных служб ... Не нужно называть их по отдельности. Значение/* /*/* /*/*/*...
просто перечислит всю файловую систему.Уязвимости отказа в обслуживании.
Взяв предыдущий случай слишком далеко, мы получили DoS.
Фактически, любая переменная без кавычек в контексте списка с неанизированным вводом является, по крайней мере, уязвимостью DoS.
Даже опытные сценаристы оболочки обычно забывают цитировать такие вещи, как:
:
это команда no-op. Что возможно могло пойти не так?Это означало , чтобы назначить
$1
для$QUERYSTRING
если$QUERYSTRING
было отключено. Это быстрый способ сделать CGI-скрипт доступным из командной строки.Это
$QUERYSTRING
все еще расширяется, и поскольку он не заключен в кавычки, вызывается оператор split + glob .Теперь есть некоторые глобусы, которые особенно дороги в расширении.
/*/*/*/*
Один достаточно плохо , как это означает , что листинг каталогов до 4 -х уровней вниз. Помимо работы с диском и процессором, это означает хранение десятков тысяч путей к файлам (здесь 40 КБ на минимальной виртуальной машине сервера, из которых 10 КБ каталогов).Теперь
/*/*/*/*/../../../../*/*/*/*
означает 40k x 10k и этого/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*
достаточно, чтобы поставить на колени даже самую мощную машину.Попробуйте сами (хотя будьте готовы к поломке или зависанию вашей машины):
Конечно, если код:
Затем вы можете заполнить диск.
Просто выполните поиск в Google по shell cgi или bash cgi или ksh cgi , и вы найдете несколько страниц, которые покажут вам, как писать CGI в оболочках. Обратите внимание, что половина тех, которые обрабатывают параметры, уязвимы.
Даже собственный Дэвид Корн уязвим (посмотрите на обработку куки).
до произвольных уязвимостей при выполнении кода
Выполнение произвольного кода является наихудшим типом уязвимости, поскольку, если злоумышленник может выполнить какую-либо команду, нет предела тому, что он может сделать.
Это обычно разделенная часть, которая ведет к этим. Это разделение приводит к тому, что командам передается несколько аргументов, когда ожидается только один. В то время как первый из них будет использоваться в ожидаемом контексте, другие будут в другом контексте, поэтому потенциально интерпретируются по-разному. Лучше с примером:
Здесь предполагалось назначить содержимое
$external_input
переменной оболочкиfoo
awk
переменной.В настоящее время:
Второе слово, полученное в результате разбиения,
$external_input
не присваивается,foo
а рассматривается какawk
код (здесь, который выполняет произвольную команду:)uname
.Это особенно проблема для команд , которые могут выполняться другие команды (
awk
,env
,sed
(GNU один)perl
,find
...) , особенно с вариантами GNU (которые принимают параметры после аргументов). Иногда вы не подозреваете, что команды могут выполнять другие, такие какksh
,bash
илиzsh
,[
илиprintf
...Если мы создадим каталог с именем
x -o yes
, тогда тест станет положительным, потому что это абсолютно другое условное выражение, которое мы оцениваем.Хуже того, если мы создадим файл с именем
x -a a[0$(uname>&2)] -gt 1
, по крайней мере, со всеми реализациями ksh (который включает в себяsh
большинство коммерческих Unices и некоторые BSD), который выполняется,uname
потому что эти оболочки выполняют арифметическую оценку операторов числового сравнения[
команды.То же самое с
bash
именем файла, какx -a -v a[0$(uname>&2)]
.Конечно, если они не могут получить произвольное выполнение, атакующий может согласиться на меньший ущерб (который может помочь получить произвольное выполнение). Любая команда, которая может записывать файлы или изменять разрешения, владельца или иметь любой основной или побочный эффект, может быть использована.
Все виды вещей могут быть сделаны с именами файлов.
И вы в итоге делаете
..
доступным для записи (рекурсивно с GNUchmod
).Сценарии, выполняющие автоматическую обработку файлов в общедоступных областях, например
/tmp
, должны быть написаны очень осторожно.Что о
[ $# -gt 1 ]
Это то, что я нахожу раздражающим. Некоторые люди не понимают, насколько проблематичным может быть конкретное расширение, чтобы решить, могут ли они опустить кавычки.
Это как сказать. Эй, похоже, на него
$#
не распространяется оператор split + glob, давайте попросим оболочку split + glob . Или , давайте напишем неправильный код только потому, что ошибка вряд ли будет устранена .Насколько это маловероятно? ОК,
$#
(или$!
,$?
или какая - либо арифметическая замещение) может содержать только цифры (или-
для некоторых) , так что Глоб часть выходит. Чтобы разделенная часть могла что-то делать, нам нужно$IFS
только содержать цифры (или-
).Некоторые оболочки
$IFS
могут быть унаследованы от окружения, но если окружение небезопасно, игра все равно окончена.Теперь, если вы напишите такую функцию:
Это означает, что поведение вашей функции зависит от контекста, в котором она вызывается. Или, другими словами,
$IFS
становится одним из входов к нему. Строго говоря, когда вы пишете API-документацию для своей функции, она должна выглядеть примерно так:И код, вызывающий вашу функцию, должен убедиться, что
$IFS
она не содержит цифр. Все из-за того, что вам не хотелось печатать эти две двойные кавычки.Теперь, чтобы эта
[ $# -eq 2 ]
ошибка стала уязвимостью, вам нужно как-то, чтобы значение$IFS
оказалось под контролем злоумышленника . Вполне возможно, что это не произойдет, если злоумышленнику не удастся использовать другую ошибку.Это не неслыханно, хотя. Распространенный случай - когда люди забывают очистить данные, прежде чем использовать их в арифметическом выражении. Мы уже видели выше, что он может разрешить выполнение произвольного кода в некоторых оболочках, но во всех из них он позволяет злоумышленнику дать любой переменной целочисленное значение.
Например:
А со
$1
значением «а»(IFS=-1234567890)
эта арифметическая оценка имеет побочный эффект настроек IFS, и следующая[
команда не выполняется, что означает, что проверка на слишком много аргументов обойдена.Как насчет того, когда оператор split + glob не вызывается?
Есть еще один случай, когда кавычки нужны вокруг переменных и других расширений: когда это используется в качестве шаблона.
не испытывают ли
$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]
).Это может сделать для уязвимости безопасности? Да, как любая ошибка. Здесь злоумышленник может изменить поток логического кода вашего скрипта и / или нарушить предположения, которые делает ваш скрипт. Например, с кодом вроде:
Атакующий может обойти проверку, пройдя мимо
'[a]' '[a]'
.Теперь, если ни это сопоставление с образцом, ни оператор split + glob не применимы, какова опасность оставить переменную без кавычек?
Я должен признать, что я пишу:
Там цитирование не вредит, но не является строго необходимым.
Тем не менее, одним из побочных эффектов пропуска кавычек в этих случаях (например, в ответах на вопросы и ответы) является то, что он может отправлять неправильное сообщение новичкам:
может быть, все в порядке, не заключая в кавычки переменные.Например, они могут начать думать, что если
a=$b
все в порядке, тоэто будет так же хорошо (что не во многих оболочках, а в аргументахexport a=$b
export
команды и в контексте списка) или.env a=$b
Как насчет
zsh
?zsh
действительно исправил большинство из этих дизайнерских неудобств. Вzsh
(по крайней мере, когда не в режиме эмуляции sh / ksh), если вы хотите расщепление , или подстановку , или сопоставление с образцом , вы должны явно запросить это:$=var
расщепление и$~var
выделение или для содержимого переменной, которая будет рассматриваться как шаблон.Тем не менее, разделение (но не глобализация) все еще выполняется неявно при замене команд без кавычек (как в
echo $(cmd)
).Кроме того, иногда нежелательным побочным эффектом отсутствия кавычек является удаление тары .
zsh
Поведение похоже на то , что вы можете достичь в других оболочках, отключив подстановку вообще (сset -f
) и расщепление (сIFS=''
). Еще в:Не будет split + glob , но если
$var
он пуст, вместо получения одного пустого аргумента,cmd
он вообще не получит аргумента.Это может вызвать ошибки (как очевидное
[ -n $var ]
). Это может нарушить ожидания и предположения сценария и стать причиной уязвимостей, но я не могу сейчас привести не слишком надуманный пример).А когда вы делаете нужен раскол + Glob оператор?
Да, обычно вы хотите оставить свою переменную без кавычек. Но затем вам нужно убедиться, что вы правильно настроили операторы split и glob, прежде чем использовать его. Если вам нужна только разделенная часть, а не глобальная часть (что имеет место в большинстве случаев), вам нужно отключить глобализацию (
set -o noglob
/set -f
) и исправить$IFS
. В противном случае вы также создадите уязвимости (как в приведенном выше примере CGI Дэвида Корна).Заключение
Короче говоря, оставлять в кавычках переменную (или подстановку команд, или арифметическое расширение) без кавычек действительно может быть очень опасно, особенно когда это делается в неправильных контекстах, и очень трудно понять, что это за неправильные контексты.
Это одна из причин, почему это считается плохой практикой .
Спасибо за чтение до сих пор. Если это идет по твоей голове, не волнуйся. Нельзя ожидать, что все поймут все последствия написания своего кода так, как они его пишут. Вот почему у нас есть рекомендации по хорошей практике , поэтому они могут быть выполнены, не обязательно понимая, почему.
(и в случае, если это еще не очевидно, пожалуйста, избегайте написания кода, чувствительного к безопасности, в оболочках).
И, пожалуйста, укажите ваши переменные в ваших ответах на этом сайте!
And В
ksh93
иpdksh
и производных расширение фигурных скобок также выполняется, если не отключеноksh93
глобирование (в случае версий до ksh93u +, даже еслиbraceexpand
опция отключена).источник
[[
этом нужно указывать только RHS сравнений:if [[ $1 = "$2" ]]; then
[[ $* = "$var" ]]
это не то же самое, что[[ "$*" = "$var" ]]
если первый символ$IFS
не является пробеломbash
(а такжеmksh
если$IFS
пусто, хотя в этом случае я не уверен, что$*
равно, должен ли я вызвать ошибку?)).foo='bar; rm *'
, нет, это не будет, однако он будет перечислять содержимое текущего каталога, который может рассматриваться как раскрытие информации.print $foo
вksh93
(гдеprint
замена дляecho
этого устраняет некоторые из его недостатков) имеет уязвимость внедрения кода, хотя (например, сfoo='-f%.0d z[0$(uname>&2)]'
) (вам на самом деле нужноprint -r -- "$foo"
.echo "$foo"
все еще неправильно и не исправимо (хотя обычно менее опасно)).[Вдохновленный этот ответ по КАСУ .]
Но что, если …?
Не могу сказать ; это не удастся, если это пустая строка.
grep "$ignorecase" other_grep_args
$ignorecase
Отклик:
Как обсуждалось в другом ответе, это все равно не удастся, если
IFS
содержит a-
или ani
. Если выIFS
убедились, что в вашей переменной нет ни одного символа (и вы уверены, что ваша переменная не содержит символов-глобусов), то это, вероятно, безопасно.Но есть способ, который более безопасен (хотя он несколько уродлив и совершенно не интуитивен): использовать
${ignorecase:+"$ignorecase"}
. Из спецификации языка команд оболочки POSIX , в разделе 2.6.2 Расширение параметров ,Хитрость здесь, такая как она есть, в том, что мы используем
ignorecase
какparameter
и"$ignorecase"
какword
. Так${ignorecase:+"$ignorecase"}
значитЭто приведет нас туда, куда мы хотим пойти: если для переменной задана пустая строка, она будет «удалена» (это целое, запутанное выражение будет ничего не вычислять - даже пустую строку), и если переменная не имеет - пустое значение, мы получаем это значение в кавычках.
Но что, если …?
Отклик:
Вы можете подумать, что это случай использованияНет! Не поддавайтесь искушению даже подумать об использованииeval
.eval
здесь.Опять же, если вы
IFS
убедились, что в вашей переменной нет символов (за исключением пробелов, которые вы хотите отметить), и вы уверены, что ваша переменная не содержит символов-глобусов, то, вероятно, приведенное выше безопасный.Но если вы используете bash (или ksh, zsh или yash), есть более безопасный способ: использовать массив:
От Баш (1) ,
Таким образом,
"${criteria[@]}"
расширяется (в приведенном выше примере) ноль, два или четыре элементаcriteria
массива, каждый в кавычках. В частности, если ни одно из условий s не является истинным,criteria
массив не имеет содержимого (как установленоcriteria=()
оператором) и ничего не"${criteria[@]}"
вычисляет (даже неудобную пустую строку).Это становится особенно интересным и сложным, когда вы имеете дело с несколькими словами, некоторые из которых представляют собой динамический (пользовательский) ввод, который вы не знаете заранее, и могут содержать пробелы или другие специальные символы. Рассмотреть возможность:
Обратите внимание, что
$fname
цитируется каждый раз, когда он используется. Это работает, даже если пользователь вводит что-то вродеfoo bar
илиfoo*
;"${criteria[@]}"
оценивает-name "foo bar"
или-name "foo*"
. (Помните, что каждый элемент массива указан в кавычках.)Массивы не работают во всех оболочках POSIX; Массивы - это ksh / bash / zsh / yash-ism. За исключением ... есть один массив, который поддерживают все оболочки: список аргументов, иначе
"$@"
. Если вы закончили со списком аргументов, с которым вы были вызваны (например, вы скопировали все «позиционные параметры» (аргументы) в переменные или обработали их иным образом), вы можете использовать список аргументов в виде массива:"$@"
Конструкция (которая исторически пришел первым) имеет ту же семантику, - она расширяется каждый аргумент (т.е. каждый элемент списка аргументов) в отдельное слово, как если бы вы ввели ."${name[@]}"
"$1" "$2" "$3" …
Выдержка из спецификации языка команд оболочки POSIX , в разделе 2.5.2 Специальные параметры ,
Полный текст несколько загадочный; Ключевым моментом является то, что он указывает, что
"$@"
должен генерировать нулевые поля, когда нет позиционных параметров. Историческая справка: когда"$@"
он впервые был представлен в оболочке Bourne (предшественник bash) в 1979 году, в нем была ошибка, которая"$@"
заменялась одной пустой строкой, когда не было позиционных параметров; Посмотрите, что${1+"$@"}
означает сценарий оболочки и чем он отличается"$@"
? , Традиционная семья Bourne Shell , Что${1+"$@"}
означает ... и где это необходимо? и"$@"
против${1+"$@"}
.Массивы помогают и в первой ситуации:
____________________
PS (csh)
Это должно быть само собой разумеющимся, но для блага новых пользователей: csh, tcsh и т. Д. Не являются оболочками Bourne / POSIX. Они совсем другая семья. Лошадь другого цвета. Совсем другая игра с мячом. Другая порода кошек. Птицы другого пера. И, в частности, другая банка червей.
Часть того, что было сказано на этой странице, относится к csh; например: это хорошая идея заключать в кавычки все ваши переменные, если у вас нет веских причин не делать этого, и вы уверены, что знаете, что делаете. Но в csh каждая переменная является массивом - так уж получилось, что почти каждая переменная является массивом только из одного элемента и действует очень похоже на обычную переменную оболочки в оболочках Bourne / POSIX. И синтаксис ужасно другой (и я имею в виду ужасно ). Поэтому мы не будем больше говорить о оболочках семейства csh.
источник
csh
, вы бы предпочли использовать, а$var:q
не"$var"
как последний не работает для переменных, которые содержат символы новой строки (или если вы хотите заключить элементы массива в кавычки по отдельности, а не объединять их с пробелами в один аргумент).bitrate="--bitrate 320"
работает${bitrate:+$bitrate}
иbitrate=--bitrate 128
не будет работать,${bitrate:+"$bitrate"}
потому что нарушает команду. Безопасно ли использовать${variable:+$variable}
без"
?$@
для чего-то другого), или вы просто отказываетесь использовать массив по другим причинам, я обращаюсь Вы «Как обсуждалось в другом ответе». … (Продолжение)${variable:+$variable}
при нет"
) потерпит неудачу , если IFS содержит-
,0
,2
,3
,a
,b
,e
,i
,r
илиt
.${bitrate:+$bitrate}
Я скептически отнесся к ответу Стефана, однако можно злоупотреблять
$#
:или $?
Это надуманные примеры, но потенциал существует.
источник