Разрешить пользователю выполнять сценарий оболочки, не видя его содержимого?

Я хотел бы иметь HG-хук, который отправляет электронную почту, используя учетную запись Gmail. Очевидно, я не хочу, чтобы кто-либо мог прочитать сценарий отправки электронной почты, кроме меня или root, поскольку в нем есть пароль, поэтому я попробовал вот что:

-rwsr-xr-x  1 james james   58 Feb 18 12:05 incoming.email.sh
-rwx--x--x  1 james james  262 Feb 18 12:04 send-incoming-email.sh

где incoming.email.sh файл выполняется как ловушка:

#! /bin/bash
/path/to/send-incoming-email.sh

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

/bin/bash: /path/to/send-incoming-email.sh: Permission denied

send-incoming-email.sh файл работает нормально, когда я работаю как я.

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

Система Ubuntu 10.04.2 LTS.

3 ответа

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

int main(){
setuid(geteuid());
system("/path/to/send-incoming-email.sh");
}

И имейте это setuid, таким образом избегая условия гонки и в то же время позволяя вам выдавать выполнение скрипта от имени root.

Это далеко не лучшее решение, но оно решит проблему, как описано.

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


"Правильный" способ отправки электронной почты в системах Unix/Linux - это настроить MTA, такой как Postfix, Exim4 или Sendmail, и позволить ему обрабатывать ошибки аутентификации SMTP. Также есть MTA "только для реле" - esmtp, msmtp, ssmtp. Все они могут выполнять SMTP-ретрансляцию ("smarthost") с аутентификацией, например, через серверы Gmail. Это становится сложнее на многопользовательской машине, но все же выполнимо.

(Когда MTA настроен, отправка электронного письма осуществляется путем передачи данных в /usr/sbin/sendmail rcpt@address.)

Почти все системы игнорируют биты и биты в сценариях. (Это не ошибка или недосмотр, это важная функция безопасности; подробнее об этом позже.)


Обычный обходной путь — использовать небольшую и/или двоичную оболочку. Базовая версия в ответе @jeremy-sturdivant — хорошее начало, но она не позволяет передавать какие-либо аргументы. Для этого вам нужно перейти к , изменив его так, чтобы он указывал на реальный скрипт:

      #include <sys/types.h>
#include <stdio.h>   /* perror */
#include <unistd.h>  /* execve, geteuid, setuid */
int main(int argc, char **argv, char **envp) {
    setuid(geteuid());
    *argv = "/real/path/to/script";
    execve(*argv, argv, envp);
    perror(*argv);
    return 127;
}

Если вы хотите вместо , просто изменитестрока:

          setgid(getegid());

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


Но почему они и игнорируются в скриптах?

Во-первых, полезно понять, что происходит при вызове сценария.

Как и любая программа, скрипт вызывается вызовом ядра, который принимает 3 параметра: (строка), (массив строк) и (еще один массив строк). По соглашению это «то же самое», что и , но это лишь приближение; он может пропустить или изменить путь и/или может иметьв предисловии к нему, чтобы указать, что это должно быть началом нового сеанса входа в систему. (содержит переменные среды; для данного обсуждения это не важно.)

Параметры и обычно передаются без изменений в ипараметры в новой программе.

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

  • Оригиналбудет отброшено и заменено на , тогда
  • аргументу присвоено имя файла интерпретатора .
  • еще один или два элемента вставляются спереди, имя файла интерпретатора и параметр интерпретатора (если задан).

Затем начинается все сначала с этими новыми аргументами.

Это означает, что при запуске интерпретатора имя файла сценария передается в его как обычный элемент , и интерпретатор просто открывает это имя файла и начинает его читать.

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


Подождите, вы сказали «почти все» системы. А что насчет остальных?

Некоторые системы открывают сценарий во времясистемный вызов и вызовите интерпретатор, указав /dev/fd/ 3 в качестве «имени» сценария. Это означает, что нет никакой угрозы безопасности со стороныилиоперации, но у нее есть тот недостаток, чтов сценарии гораздо менее полезно.

Другие вопросы по тегам