Как защитить скрипт sudo

Решение для выполнения корневых команд как непривилегированного скрипта пользователя – sudo 'ing – легко открывает возможность нарушения безопасности и неожиданного поведения и результатов (это также верно для любого другого решения, такого как бинарная оболочка с setuid );

Понятно, что скрипты, добавленные с помощью visudo, вроде этого

 www-data ALL=(ALL) NOPASSWD: /usr/local/sbin/mycommand 
  • должен быть доступен для чтения , записи и выполнения только root – который является как владельцем, так и группой ( chown root:root mycommand; chmod 700 mycommand )
  • должен иметь свой родительский root:root каталог root:root с режимом 755
  • должен проверять входные аргументы и stdin – и отклонять и прерывать выполнение при любых недопустимых / непредвиденных данных
  • должен использовать абсолютный вместо относительных путей / псевдонимов (?)
  • Defaults env_reset по Defaults env_reset в /etc/sudoers должны быть установлены (здесь нужна помощь)

Что еще можно сделать, чтобы защитить скрипт sudo?

Я бы немного изменил ваш список критериев для защиты скрипта. Учитывая это – или аналогичную запись в /etc/sudoers :

 www-data ALL=(ALL) NOPASSWD: /usr/local/sbin/mycommand 

мы можем указать, что сценарий:

  • должна быть записана только пользователем root
  • должны быть удобочитаемыми и исполняемыми пользователем root
  • должен быть в иерархии каталогов, которые могут быть записаны только root
  • должен обосновать свой вклад «достаточно» для использования и отвергать все остальное
  • должен иметь наименьший набор привилегий, необходимых для выполнения его задачи (не обязательно setuid root)
  • должен определить его PATH перед использованием любых внешних команд
  • должен установить все переменные на известное значение перед их использованием
  • должен генерировать аудиторский след, чтобы показать не только, когда и как он был вызван, но и итоговое действие (think logger )

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

Для сред SELinux может быть возможно создать политику, которая предотвращает непредвиденный сценарий. Такие возможности, как CAP_NET_ADMIN, более мелкозернистые, чем обычные привилегии root, и их также стоит рассмотреть.

В конкретном случае, который вы указали, где вы хотите проверить один IPv4-адрес и передать его в iptables , вы можете уйти с проверкой IP-адреса как серии неспецифических октетов. В этом случае 444.555.666.999 может считаться правдоподобным, зная, что сам iptables отклонит все, что не является реальным IP-адресом. С одной стороны вы можете решить, что соответствие RE /^[0-9.]+$/ достаточно, чтобы быть счастливым, передавая значение iptables . С другой стороны, есть много ответов на StackExchange и в других местах, которые затрагивают проблему проверки IP-адреса. Некоторые лучше других.

Особыми случаями рассмотрения являются адреса RFC1918, адреса многоадресной рассылки и собственный диапазон внешних IP-адресов. О, и зарезервированный блок, ранее известный как Class E. Нужна ли вам поддержка IPv6?

Что произойдет, если ваш скрипт называется сотни раз в минуту? Вам нужно подготовиться к этому событию? Будет ли переполнение вашей iptables ? Если вы думаете, что вам понадобятся сотни правил в вашей цепочке, будет [более эффективно использовать расширение ipset для iptables а не для линейного списка. Вот хороший учебник . С точки зрения защиты, он позволяет создавать наборы тысяч (если не десятки тысяч ) аналогичных правил, которые могут выполняться без значительного замедления трафика, проходящего через ваши наборы правил.

Внезапно ваше очевидное прямое требование довольно сложно.

Именованный подход к трубе. Как root, запустите

 mkfifo -m 666 /tmp/foo /tmp/readpipe.sh & 

И может, как пользователь www-data, затем записать в трубу

 echo test >>/tmp/foo 

readpipe.sh в его простейшей форме (perl с taint будет лучше):

 #!/bin/sh while read A </tmp/foo do echo received $A done 

Вещи, которые могут повлиять на программу setuid

Давайте рассмотрим некоторые способы, которыми вызывающий пользователь может повлиять на поведение процесса setuid. Я разберу вещи, которые нужно рассмотреть в трех группах: 1) сама программа, 2) вход в программу и 3) среда, в которой он работает.

Бинарный: если непривилегированный пользователь может изменить исполняемый файл, это будет простой способ изменить привилегированный процесс. Сюда относятся случаи, когда путь к файлу программы проходит через каталоги и символические ссылки, доступные для записи. Убедитесь, что файл программы и путь к ней недоступны для записи неавторизованными пользователями. (И sudo , похоже, не проверяет.)

Бинарные файлы Setuid на самом деле более невосприимчивы к модификации таким образом, поскольку бит setuid является свойством inode, а не path, поэтому symlink tricking не будет работать. (Кроме того, похоже, что некоторые Linuxes удаляют бит setuid, если другой пользователь пишет в файл, но я бы не стал рассчитывать на это.)

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

Если программе требуется какой-либо ввод от пользователя (вместо того, чтобы делать только одно, всегда), мы не можем этого избежать. Сетевые службы должны быть осторожны с их вводом таким же образом. (Например, примеры инъекций для свидетелей и т. Д.). Я считаю, что обработка ввода особенно сложна для таких вещей, как shell-скрипты, где все данные являются текстовыми, и весь текст является одной цитатой от превращения в команду, так сказать.

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

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

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

(Хотя многие из последних не могут быть изменены не-привилегированными пользователями, поэтому они могут не быть серьезной проблемой. Исполняемый файл setuid, доступный из ограниченного окружения, может по-прежнему нуждаться в правильном рассмотрении ограниченной среды.)

Что с этим делать

По крайней мере, вы должны

  • Убедитесь, что исполняемый файл доступен для записи только авторизованным пользователям, включая как сам файл, так и путь, ведущий к нему. Сделать его нечитаемым необязательно, если программа не содержит встроенные секреты.
  • Сбросьте среду при запуске, включая настройку PATH на известное значение. Использование абсолютных путей кажется безопасным, но я не могу сказать, какой эффект он мог бы иметь, если известен путь поиска. env_reset в sudo должен это сделать.
  • ( LD_* обрабатываются динамическим компоновщиком, прежде чем вы получите возможность их отменить, но компоновщик должен игнорировать их для процессов setuid.)
  • Подтвердите свой ввод. Конечно. Хотя это также может быть проще сказать, чем сделать, особенно если вы рассматриваете возможность ввода пользователем сценария оболочки.
  • Возьмите чувствительный вход (пароли) через трубу или stdin.
  • Сбросьте ограничения ресурсов или риск сбоя в неожиданной точке.

Теоретически, в традиционной среде должно быть возможно сделать «безопасную» программу setuid или sudo-run, но менее обоснованные системные функции могут быть сложнее проверить.

(И, кстати, я могу сказать, что программа setuid не всегда будет легко открывать нарушение безопасности, поскольку во многих системах есть такие вещи, как passwd , su и sudo , которые являются setuid, но не считаются относительно безопасными.)

Альтернатива setuid

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

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

У сокетов нет этой проблемы, но к ним также не могут обращаться обычные функции файловой системы. Вместо этого они должны быть доступны через функции сокетов. (т. е. echo foo > /my/socket не будет работать. socat может быть полезен в скриптах).

Одна из лучших заключается в использовании возможности «Digest_Spec» в файле sudoers , для проверки контрольной суммы вашего исполняемого файла

Экстракт страницы руководства:

Если имя команды имеет префикс Digest_Spec, команда будет успешно соответствовать только в том случае, если ее можно проверить, используя указанный SHA-2 дайджест.

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

 $ openssl dgst -sha224 /usr/local/sbin/mycommand SHA224(/usr/local/sbin/mycommand)= 52246fd78f692554c9f6be9c8ea001c9131c3426c27c88dbbad08365 

Затем в файле sudoers (в той же строке):

  www-data ALL=(ALL) NOPASSWD: ssha224:52246fd78f692554c9f6be9c8ea001c9131c3426c27c88dbbad08365 /usr/local/sbin/mycommand 

Что еще можно сделать, чтобы защитить скрипт с sudo-powered: используйте PAM-подключаемые модули аутентификации.

Руководство администратора PAM: http://www.linux-pam.org/Linux-PAM-html/Linux-PAM_SAG.html

Руководство для авторов модуля PAM: http://www.linux-pam.org/Linux-PAM-html/Linux-PAM_MWG.html

Руководство разработчика приложений PAM: http://www.linux-pam.org/Linux-PAM-html/Linux-PAM_ADG.html

также проверьте /usr/share/doc/packages/pam/pdf в вашей системе, для этих документов, которые будут уместны для версии PAM в вашей системе.

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

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

  • pam_env: вызывать настройку или отмену переменных среды при вызове, использовать это в ваших интересах при создании вашего модуля pam

  • pam_localuser: помощь в реализации политик входа на сайт, где они обычно включают подмножество пользователей сети и несколько учетных записей, которые являются локальными для конкретной рабочей станции. Использование pam_localuser и pam_wheel или pam_listfile является эффективным способом ограничения доступа к локальным пользователям и / или подмножеству пользователя сети.

  • список можно найти в руководстве администратора sys и использовать по мере необходимости.

вы также можете найти это полезным: http://freecode.com/projects/pam_script/

Отсюда вам нужно будет прочитать руководство разработчика и руководство по разработке приложений для модуля. Это цель PAM … подключаемых модулей аутентификации. Вы эффективно пишете код, и если вы еще не станете программистом, когда закончите. Это не «ИТ», когда вы нажимаете на некоторые флажки, включенные / отключенные, как в Microsoft Windows. Причина для PAM заключается в том, что системный администратор может подключать модуль (иногда легко, иногда не нужно писать) по соображениям безопасности, чтобы делать что угодно. Например, пользователь, использующий passwd для изменения своего пароля, использует PAM для безопасности, и вы не вводите «sudo passwd», чтобы изменить свой пароль. В зависимости от того, что должен делать ваш скрипт, вам может не понадобиться sudo, если вы правильно используете PAM и задаете право собственности и SUID / GUID вашего скрипта, точно так же, как /usr/bin/passwd .

Когда вы спросите [себя], что еще я могу сделать, чтобы обеспечить это … может показаться, что это не лучший способ сделать что-то. В этом случае использование sudo для предоставления повышенных привилегий для запуска скрипта / исполняемого файла. Вместо этого посмотрите на установленные механизмы того, как linux уже обрабатывает такие вещи … случай использования пользователем /usr/bin/passwd для редактирования файлов /etc/passwd и /etc/shadow которые принадлежат root, и для этого необходимы права root быть изменен. И с помощью PAM вы можете ограничить, кто может запускать ваш скрипт на основе идентификатора пользователя, идентификатора группы, локальной учетной записи пользователя, в течение заданного времени и т. Д. Идем дальше, убедившись, что вы можете создать отдельную учетную запись группы с собственным паролем, с единственной целью запустить ваш скрипт только с этими пользователями, а затем вы не выдадите пароль root для запуска вашего скрипта. Вы бы следовали схеме колесной группы только для тех пользователей, которые могут su для root. надеюсь это поможет.

Отказываясь от того, является ли ваш подход хорошей идеей или нет, если mycommand является интерактивным, вы должны препятствовать экранированию оболочки с помощью директивы noexec . Это предотвращает выполнение других команд путем предварительной настройки системных вызовов exec() чтобы ничего не делать. (Это достигается с помощью механизмов вокруг LD_PRELOAD, см. Sudo.conf (5).) Предостережение: это не пуленепробиваемый – если mycommand статически связана, это не работает.

Я добавлю следующее:

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

 #!/bin/bash [ "$USER" != "www-data" ] && exit set -a sudo realscript &>/var/log/myscript.log 

и realscript:

 [ "$USER" != "root" ] && exit 

В sudo поместите realscript как sudo разрешено, а не вызываемый скрипт, который вы используете.

И коснитесь myscript.log как принадлежащих www-данным, но 222 разрешения (да, только запись возможна!). Сделайте резервную копию журнала в cron.

Не позволяйте www-данным видеть содержимое myscript, но просто для его выполнения!

 chmod 111 mylogscript chmod 111 myrealscript 

Разрешения 111 хороши, потому что вы не хотите, чтобы пользователи читали скрипт, а просто выполняли его.

Если возможно, попытайтесь скомпилировать сценарий оболочки:

 shc mylogscript shc myrealscript 

Да shc – это компилятор оболочки, поэтому вы можете преобразовать свой скрипт в двоичный файл, и он будет более безопасным, но в некоторых случаях неустойчивым, поэтому будьте осторожны!

Попытайтесь не использовать параметры внутри сценария оболочки, например $ 1 $ 2 , это можно использовать для ввода сценария. Вместо этого попробуйте экспортировать переменные среды, которые могут просматривать оболочки или временные файлы. Параметр или не использовать все в каком-либо шифровании openssl (посмотрите на это сообщение: как сделать openssl шифровать пароли, такие как php через командную строку