Как явно и безопасно форсировать использование встроенной команды в bash

19

Есть похожий вопрос, который касается сценария «обтекания», где вы хотите заменить, например, cdкоманду, которая вызывает встроенную функцию cd.

Тем не менее, в свете shellshock и др. И зная, что bash импортирует функции из среды, я провел несколько тестов и не могу найти способ безопасного вызова встроенной функции cdиз моего скрипта.

Учти это

cd() { echo "muahaha"; }
export -f cd

Любые скрипты, вызываемые в этой среде, cdбудут ломаться (учитывайте эффекты чего-то вроде cd dir && rm -rf .).

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

builtin() { "$@"; }
command() { "$@"; }
type() { echo "$1 is a shell builtin"; }

Даст следующее:

$ type cd
cd is a shell builtin
$ cd x
muahaha
$ builtin cd x
muahaha
$ command cd x
muahaha

Есть ли способ безопасно заставить bash использовать встроенную команду или, по крайней мере, обнаружить, что команда не является встроенной, без очистки всей среды?

Я понимаю, что если кто-то контролирует вашу среду, вы, вероятно, все равно испорчены, но, по крайней мере, для псевдонимов у вас есть возможность не вызывать псевдоним, вставив \перед ним.

falstro
источник
Вы можете запустить свой сценарий с помощью envкоманды раньше, например так:env -i <SCRIPT.sh>
Аркадиуш Драбчик
Только если envне переопределено как функция. Это ужасно Сначала я подумал, что помогут специальные символы - вызов с указанием полного пути /, использование .источника и т. Д. Но они также могут быть использованы для имен функций! Вы можете переопределить любую функцию, которую хотите, но трудно вернуться к вызову исходной команды.
Орион
1
Да, но если вы собираетесь запустить какой-либо скрипт на скомпрометированной машине, вы все равно испорчены. Либо напишите свой скрипт, #/bin/shесли это не интерактивная оболочка по умолчанию.
Аркадиуш Драбчик

Ответы:

16

Оливье Д почти правильный, но вы должны установить POSIXLY_CORRECT=1перед запуском unset. POSIX имеет понятие специальных встроенных модулей , и bash поддерживает это . unsetявляется одним из таких встроенных. Искать SPECIAL_BUILTINв builtins/*.cв источнике Баш для списка, он включает в себя set, unset, export, evalи source.

$ unset() { echo muahaha-unset; }
$ unset unset
muahaha-unset
$ POSIXLY_CORRECT=1
$ unset unset

Мошенник unsetтеперь был удален из среды, если сбросить command, type, builtinто вы должны быть в состоянии продолжать, но unset POSIXLY_CORRECTесли вы полагаетесь на не-POSIX поведения или расширенные функции Баш.

Однако это не относится к псевдонимам, поэтому вы должны использовать его, \unsetчтобы убедиться, что он работает в интерактивной оболочке (или всегда, если expand_aliasesон действует).

Для параноика это должно все исправить, я думаю:

POSIXLY_CORRECT=1
\unset -f help read unset
\unset POSIXLY_CORRECT
re='^([a-z:.\[]+):' # =~ is troublesome to escape
while \read cmd; do 
    [[ "$cmd" =~ $re ]] && \unset -f ${BASH_REMATCH[1]}; 
done < <( \help -s "*" )

( while, do, doneИ [[являются зарезервированными словами и не нужны меры предосторожности.) Обратите внимание , что мы используем , unset -fчтобы быть уверенным в неустановленных функций, хотя переменные и функции разделяют то же пространство имен , что это возможно для обоих существовать одновременно (благодаря Этан Рейснер) , в этом случае Отмена дважды также сделает свое дело. Вы можете пометить функцию только для чтения, bash не мешает вам отключить функцию readonly вплоть до bash-4.2, bash-4.3 предотвращает вас, но она по-прежнему учитывает специальные встроенные функции, когда POSIXLY_CORRECTона установлена.

Чтение только POSIXLY_CORRECTне является реальной проблемой, это не логическое значение или флаг, при котором его наличие активирует режим POSIX, поэтому, если он существует как доступный только для чтения, вы можете полагаться на функции POSIX, даже если значение пустое или равно 0. Вам просто нужно сбрасывать проблемные функции другим способом, чем описано выше, возможно, с помощью функции вырезания и вставки:

\help -s "*" | while IFS=": " read cmd junk; do echo \\unset -f $cmd; done

(и игнорировать любые ошибки) или участвовать в некоторых других сценариях .


Другие заметки:

  • functionявляется зарезервированным словом, оно может быть псевдонимом, но не может быть переопределено функцией. (Псевдоним functionмягко неприятен, потому что \function не подходит для его обхода)
  • [[, ]]являются зарезервированными словами, они могут быть псевдонимами (которые будут игнорироваться), но не могут быть переопределены функцией (хотя функции могут быть названы так)
  • (( не является допустимым именем для функции или псевдонимом
mr.spuratic
источник
Интересно, спасибо! Мне кажется странным, что bash по умолчанию не защищает unsetet al от перезаписи.
Фальстро
Это нуждается в -fаргументе unset, не так ли? В противном случае, если определены как переменная, так и функция с именем, совпадающим со встроенным, тогда unsetсначала будет выбрана переменная для сброса. Кроме того, это не работает, если установлена ​​какая-либо из затемняющих функций readonly, не так ли? (Хотя это можно обнаружить и можно сделать фатальную ошибку.) Также это не соответствует [встроенному, как кажется.
Этан Рейснер
1
Я не думаю, что \unset -f unsetимеет смысл, учитывая, что это будет сделано в цикле чтения. Если unsetэто функция, вы \unsetобычно разрешаете ее вместо встроенной, но вы можете использовать ее \unsetбезопасно, пока действует режим POSIX. Явный вызов \unset -fна [, .и :может быть хорошей идеей , хотя, как ваши регулярных выражений исключает их. Я хотел бы также добавить -fк \unsetв петле, и будет \unset POSIXLY_CORRECTпосле цикла, а не раньше. \unalias -a(после \unset -f unalias) также позволяет безопасно избежать побега по последующим командам.
Адриан Гюнтер,
1
@ AdrianGünter Попробуйте: [[ ${POSIXLY_CORRECT?non-POSIX mode shell is untrusted}x ]]это приведет к тому, что неинтерактивный скрипт завершит работу с ошибкой, указанной, если переменная не установлена, или продолжит работу, если она установлена ​​(пусто, или любое значение).
Мистер Спуратик
1
«Вы должны использовать \unset» ... Следует отметить, что цитирование любого символа (символов) будет работать, чтобы избежать расширения псевдонима, а не только первого. un"se"tбудет работать так же хорошо, хотя и менее читабельным. «Первое слово каждой простой команды, если оно не заключено в кавычки, проверяется на наличие псевдонима». - Баш Реф
Робин А. Мид
6

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

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

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

Вы не можете безопасно запускать сценарий в среде, которая была установлена ​​ненадежным объектом. Поэтому беспокоиться об определениях функций не является продуктивным. Очистите вашу среду, и при этом переменные, которые bash будет интерпретировать как определения функций.

Жиль "ТАК - прекрати быть злым"
источник
1
Я не согласен с этим полностью. Безопасность не является бинарным предложением, сосредоточенным вокруг «санированных» или «не санированных». Речь идет об устранении возможностей для эксплуатации. И, к сожалению, сложность современных систем означает, что существует множество возможностей для эксплуатации, которые необходимо должным образом заблокировать. Многие возможности эксплуатации сосредоточены на безобидных, казалось бы, восходящих атаках, таких как изменение переменной PATH, переопределение встроенных функций и т. Д. Предложите одному человеку или программе возможность сделать и то, и другое, и вся ваша система уязвима.
Деджей Клейтон,
@DejayClayton Я полностью согласен с вашим комментарием, за исключением первого предложения, потому что я не вижу, как оно каким-либо образом противоречит моему ответу. Удаление возможности использования помогает, но не удаляет только половину, когда тривиальный твик в атаке позволяет ему продолжать работать.
Жиль "ТАК - перестань быть злым"
Я хочу сказать, что вы не можете просто «дезинфицировать свою среду» и перестать беспокоиться о различных направлениях атаки. Иногда стоит «защитить от враждебной среды изнутри сценария». Даже если вы решите проблему «определения функции», вам все равно придется беспокоиться о таких вещах, как наличие PATH, где кто-то может поместить собственный каталог с именем «cat» или «ls» в каталог, а затем любой сценарий, который вызывает «cat». "или" ls "вместо" / bin / cat "и" / bin / ls "эффективно выполняет эксплойт.
Деджей Клэйтон
@DejayClayton Очевидно, что очистка среды не помогает с векторами атак, которые не связаны с окружающей средой. (Например, скрипт, который вызывает /bin/catchroot, может вызывать что угодно.) Дезинфекция среды действительно дает вам здравый смысл PATH(если, конечно, вы делаете это правильно). Я все еще не понимаю твою точку зрения.
Жиль "ТАК - перестань быть злым"
Учитывая, что PATH является одной из самых больших возможностей для эксплойтов, и что многие сомнительные методы PATH документированы как рекомендуемые рекомендации даже в блогах по безопасности, а также учитывая, что иногда установленные приложения переупорядочивают PATH, чтобы гарантировать, что такие приложения работают, я не могу понять, как Защитное кодирование в сценарии было бы плохой идеей. То, что вы считаете нормальным ПУТИ, на самом деле может быть уязвимо для эксплойтов, с которыми вы не сталкивались.
Деджей Клейтон,
1

Вы можете использовать unset -fдля удаления встроенных функций, команды и типа.

ODC
источник
2
извини, unset() { true; }позаботится об этом.
Фальстро
1
Черт! И перечисление определенных функций также опирается на встроенные. Я сдаюсь!
odc