Разрешить setuid для скриптов оболочки

Разрешить setuid для скриптов оболочки

Бит разрешения setuidсообщает Linux о необходимости запустить программу с эффективным идентификатором пользователя владельца, а не исполнителя:

> cat setuid-test.c

#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv) {
    printf("%d", geteuid());
    return 0;
}

> gcc -o setuid-test setuid-test.c
> ./setuid-test

1000

> sudo chown nobody ./setuid-test; sudo chmod +s ./setuid-test
> ./setuid-test

65534

Однако это применимо только к исполняемым файлам; скрипты оболочки игнорируют бит setuid:

> cat setuid-test2

#!/bin/bash
id -u

> ./setuid-test2

1000

> sudo chown nobody ./setuid-test2; sudo chmod +s ./setuid-test2
> ./setuid-test2

1000

Википедияговорит:

Из-за возросшей вероятности возникновения уязвимостей безопасности многие операционные системы игнорируют атрибут setuid при его применении к исполняемым скриптам оболочки.

Если предположить, что я готов пойти на эти риски, есть ли способ заставить Linux обрабатывать бит setuid в скриптах оболочки так же, как и в исполняемых файлах?

Если нет, есть ли общее решение этой проблемы? Мое текущее решение — добавить запись, sudoersпозволяющую ALLзапускать данный скрипт от имени пользователя, от имени которого я хочу его запустить, чтобы NOPASSWDизбежать запроса пароля. Главные недостатки этого — необходимость записи sudoersкаждый раз, когда я хочу это сделать, и необходимость для вызывающего sudo some-scriptвместо простоsome-script

решение1

Linux игнорирует бит setuid¹ во всех интерпретируемых исполняемых файлах (т. е. исполняемых файлах, начинающихся со #!строки).Вопросы и ответы по comp.unix.объясняет проблемы безопасности с setuid shell-скриптами. Эти проблемы бывают двух видов: shebang-related и shell-related; я расскажу подробнее ниже.

Если вас не волнует безопасность и вы хотите разрешить скрипты setuid, в Linux вам нужно будет пропатчить ядро. Что касается ядер 3.x, я думаю, вам нужно добавить вызовinstall_exec_credsвload_scriptфункция, перед вызовом open_exec, но я не проверял.


Setuid shebang

Обычно при реализации shebang() возникает состояние гонки #!:

  1. Ядро открывает исполняемый файл и обнаруживает, что он начинается с #!.
  2. Ядро закрывает исполняемый файл и вместо него открывает интерпретатор.
  3. Ядро вставляет путь к скрипту в список аргументов (как argv[1]) и запускает интерпретатор.

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

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

Несколько систем Unix (в основном OpenBSD, NetBSD и Mac OS X, все из которых требуют включения настройки ядра) реализуютбезопасный setuid shebangс использованием дополнительной функции: путь ссылается на файл, уже открытый в файловом дескрипторе/dev/fd/NН(поэтому открытие примерно эквивалентно ). Многие системы Unix (включая Linux) имеют, но не имеют скриптов setuid./dev/fd/Ndup(N)/dev/fd

  1. Ядро открывает исполняемый файл и обнаруживает, что он начинается с #!. Допустим, дескриптор файла для исполняемого файла — 3.
  2. Ядро открывает интерпретатор.
  3. Ядро вставляет /dev/fd/3список аргументов (как argv[1]) и запускает интерпретатор.

Страница-шебанг Свена Машекаимеет много информации о shebang на разных unice, включаяподдержка setuid.


Интерпретаторы Setuid

Предположим, вам удалось запустить программу как root, либо потому, что ваша ОС поддерживает setuid shebang, либо потому, что вы использовали собственную бинарную оболочку (такую ​​как sudo). Вы открыли дыру в безопасности?Может быть. Проблема здесь в том,нето интерпретируемых и компилируемых программах. Вопрос в том, является ли вашсистема выполненияведет себя безопасно, если выполняется с привилегиями.

  • Любой динамически связанный собственный двоичный исполняемый файл некоторым образом интерпретируетсядинамический загрузчик(например /lib/ld.so), который загружает динамические библиотеки, необходимые программе. На многих unice-системах вы можете настроить путь поиска динамических библиотек через среду ( LD_LIBRARY_PATHявляется общим именем для переменной среды), и даже загрузить дополнительные библиотеки во все исполняемые двоичные файлы ( LD_PRELOAD). Вызывающий программу может выполнить произвольный код в контексте этой программы, поместив специально созданный libc.soin $LD_LIBRARY_PATH(среди прочих тактик). Все разумные системы игнорируют LD_*переменные в исполняемых файлах setuid.

  • Вракушкитакие как sh, csh и производные, переменные окружения автоматически становятся параметрами оболочки. С помощью таких параметров, как PATH, IFS, и многих других, вызывающий скрипт имеет много возможностей выполнить произвольный код в контексте скриптов оболочки. Некоторые оболочки устанавливают эти переменные в разумные значения по умолчанию, если они обнаруживают, что скрипт был вызван с привилегиями, но я не знаю, есть ли какая-то конкретная реализация, которой я бы доверял.

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

  • Перлявляется заметным исключением. Этоявно поддерживает скрипты setuidбезопасным способом. Фактически, ваш скрипт может запустить setuid, даже если ваша ОС проигнорировала бит setuid в скриптах. Это происходит потому, что perl поставляется с корневым помощником setuid, который выполняет необходимые проверки и повторно вызывает интерпретатор для нужных скриптов с нужными привилегиями. Это объясняется вперлсекруководство. Раньше считалось, что setuid perl scripts нужен #!/usr/bin/suidperl -wTвместо #!/usr/bin/perl -wT, но на большинстве современных систем #!/usr/bin/perl -wTдостаточно.

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

Собственная двоичная оболочка может сделать скрипт оболочки безопасным, если оболочкадезинфицирует окружающую среду. Скрипт должен позаботиться о том, чтобы не делать слишком много предположений (например, о текущем каталоге), но это допустимо. Вы можете использовать sudo для этого, при условии, что он настроен на очистку среды. Внесение переменных в черный список подвержено ошибкам, поэтому всегда вносите их в белый список. При использовании sudo убедитесь, что опция env_resetвключена, эта setenvвыключена, а эта env_fileи env_keepсодержит только безобидные переменные.


ТЛ,ДР:

  • Setuid shebang небезопасен, но обычно игнорируется.
  • Если вы запускаете программу с привилегиями (через sudo или setuid), напишите машинный код или perl или запустите программу с оболочкой, которая очищает среду (например, sudo с опцией env_reset).

¹ Это обсуждение в равной степени применимо, если вы замените «setgid» на «setuid»;они оба игнорируются ядром Linux в скриптах

решение2

Один из способов решения этой проблемы — вызвать скрипт оболочки из программы, которая может использовать бит setuid.
Это что-то вроде sudo. Например, вот как это можно сделать в программе на языке C:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    setuid( 0 );   // you can set it at run time also
    system( "/home/pubuntu/setuid-test2.sh" );
    return 0;
 }

Сохраните его как setuid-test2.c.
скомпилируйте
Теперь выполните setuid для этого двоичного файла программы:

su - nobody   
[enter password]  
chown nobody:nobody a.out  
chmod 4755 a.out  

Теперь вы сможете запустить его, и вы увидите, что ваш скрипт выполняется без прав доступа nobody.
Но здесь вам также нужно либо жестко закодировать путь к скрипту, либо передать его как аргумент командной строки в указанный выше exe.

решение3

Я ставлю префикс нескольким скриптам, которые находятся в этой лодке, следующим образом:

#!/bin/sh
[ "root" != "$USER" ] && exec sudo $0 "$@"

Обратите внимание, что это не использует, setuidа просто выполняет текущий файл с расширением sudo.

решение4

Если по каким-то причинам sudoэто невозможно, можно написать тонкий скрипт-обертку на языке C:

#include <unistd.h>
int main() {
    setuid(0);
    execle("/bin/bash","bash","/full/path/to/script",(char*) NULL,(char*) NULL);
}

И как только вы его скомпилируете, установите его setuidкак chmod 4511 wrapper_script.

Это похоже на другой опубликованный ответ, но запускает скрипт в чистой среде и явно использует /bin/bashвместо оболочки, вызываемой system(), и таким образом закрывает некоторые потенциальные дыры в безопасности.

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

Очевидно, вы хотите убедиться, что сам скрипт доступен для записи только пользователю root.

Связанный контент