Разрешить пользователю выполнять сценарий оболочки, не видя его содержимого?
Я хотел бы иметь 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 параметра: (строка), (массив строк) и (еще один массив строк). По соглашению это «то же самое», что и , но это лишь приближение; он может пропустить или изменить путь и/или может иметь
Параметры и обычно передаются без изменений в и
Однако если файл программы начинается с
- Оригинал
будет отброшено и заменено на , тогда - аргументу присвоено имя файла интерпретатора .
- еще один или два элемента вставляются спереди
, имя файла интерпретатора и параметр интерпретатора (если задан).
Затем начинается все сначала с этими новыми аргументами.
Это означает, что при запуске интерпретатора имя файла сценария передается в его
Это означает, что существует небольшой интервал между проверкой бита во время системного вызова и моментом, когда интерпретатор сценария открывает сценарий для чтения. При этом злоумышленник может подменить
Подождите, вы сказали «почти все» системы. А что насчет остальных?
Некоторые системы открывают сценарий во время