Программирование для Linux. Профессиональный подход - Марк Митчелл
Шрифт:
Интервал:
Закладка:
■ заменять эффективный идентификатор реальным;
■ заменять реальный идентификатор эффективным;
■ переставлять местами значения реального и эффективного идентификаторов.
Первый вариант будет использован серверным процессом, когда он закончит работать от имени пользователя mitchell и захочет снова "стать" пользователем root. Второй вариант используется программой аутентификации после того, как она сделала эффективный идентификатор равным пользовательскому. Изменение реального идентификатора необходимо для того, чтобы пользователь не смог стать обратно пользователем root. Последний, третий, вариант в современных программах не встречается.
В качестве любого из аргументов функции setreuid() можно указать значение -1. Это означает, что соответствующий идентификатор нужно оставить без изменений. Есть также вспомогательная функция seteuid(), которая меняет эффективный идентификатор, но не трогает реальный. Например, следующие две строки эквивалентны:
seteuid(id);
setreuid(-1, id);
10.4.1. Программы с установленным битом SUID
Выше было показано, как процесс пользователя root может временно принять на себя права другого пользователя или отказаться от специальных привилегий, изменив свои реальный и эффективный идентификаторы. Но вот загадка: может ли непривилегированный пользователь стать суперпользователем? Это кажется невозможным, но следующий пример свидетельствует об обратном:
% whoami
mitchell
% su
Password: ...
% whoami
root
Команда whoami аналогична команде id, но отображает только эффективный идентификатор пользователя. Команда su позволяет вызвавшему ее пользователю стать суперпользователем, если введен правильный пароль.
Как же работает команда su? Ведь мы знаем, что интерпретатор команд был запущен с реальным и эффективным идентификаторами, равными mitchell. Функция setreuid() не позволит ему поменять ни один из них.
Дело в том, что у программы su установлен бит смены идентификатора пользователя (SUID, set user identifier). Это значит, что при запуске ее эффективным идентификатором станет идентификатор владельца (реальный идентификатор останется тем же, что у пользователя, запустившего программу). Для установки бита SUID предназначены команда chmod +s и флаг S_SUID функции chmod().[34]
В качестве примера рассмотрим программу, показанную в листинге 10.3.
Листинг 10.3. (setuid-test.c) Проверка идентификаторов#include <stdio.h>
#include <unistd.h>
int main() {
printf("uid=%d euid=%dn", (int)getuid(), (int)geteuid());
return 0;
}
Теперь предположим, что у программы установлен бит SUID и она принадлежит пользователю root. В этом случае вывод команды ls будет примерно таким:
-rwsrws--x 1 root root 11931 Jan 24 18:25 setuid-test
Буквы s в строке режима означают, что этот файл не только является исполняемым, но для него установлены также биты SUID и SGID. Результат работы программы будет таким:
% whoami
mitchell
% ./setuid-test
uid=501 euid=0
Обратите внимание на то, что эффективный идентификатор стал равным нулю. Устанавливать биты SUID и SGID позволяют команда chmod u+s и chmod g+s соответственно. Приведем пример:
% ls -l program
-rwxr-xr-x 1 samuel csl 0 Jan 30 23:38 program
% chmod g+s program
% ls -l program
-rwxr-sr-x 1 samuel csl 0 Jan 30 23:38 program
% chmod u+s program
% ls -l program
-rwsr-sr-x 1 samuel csl 0 Jan 30 23:38 program
Аналогичным целям служат флаги S_ISUID и S_ISGID функции chmod().
Именно так работает команда su. Ее эффективный идентификатор пользователя равен нулю. Если введенный пароль совпадает с паролем пользователя root, команда меняет свой реальный идентификатор на root, после чего запускает новый интерпретатор команд. В противном случае ничего не происходит.
Рассмотрим атрибуты программы su:
% ls -l /bin/su
-rwsr-xr-x 1 root root 14188 Mar 7 2000 /bin/su
Как видите, она принадлежит пользователю root и для нее установлен бит SUID. Обратите внимание на то, что команда su не меняет идентификатор интерпретатора команд, в котором она была вызвана, а запускает новый интерпретатор с измененным идентификатором. Первоначальный интерпретатор будет заблокирован до тех пор, пока пользователь не введет exit.
10.5. Аутентификация пользователей
Программы, у которых установлен бит SUID, не должны запускаться кем попало. Например, программа su, прежде чем менять идентификатор пользователя, заставляет его ввести пароль. Это называется аутентификацией — программа проверяет, получил ли пользователь права суперпользователя.
Администраторам высоконадежных систем недостаточно, чтобы пользователи просто вводили пароли. У пользователей есть вредная привычка записывать свои пароли на бумажке, приклеенной к монитору, или выбирать пароли, в которых закодирован день рождения, имя любимой собаки, жены и т.п. Все это облегчает задачу злоумышленникам, пытающимся незаконно проникнуть в систему.
Во многих организациях требуется использовать "одноразовые" пароли, генерируемые специальными электронными карточками, которые пользователи хранят при себе. Одни и тот же пароль не может встретиться дважды, а прежде чем получить пароль, требуется ввести личный код. Следовательно, для взлома системы хакеру требуется раздобыть электронную карточку и узнать соответствующий личный код. В сверхсекретных учреждениях используются устройства сканирования сетчатки глаза или другие биометрические приборы.
При написании аутентификационной программы важно позволить системному администратору использовать тот механизм аутентификации, который он считает приемлемым. В Linux этой цели служат подключаемые модули аутентификации (РАМ, pluggable authentication modules). Рассмотрим простейшее приложение (листинг 10.4).
Листинг 10.4. (pam.c) Пример использования модулей РАМ#include <security/pam_appl.h>
#include <security/pam_misc.h>
#include <stdio.h>
int main() {
pam_handle_t* pamh;
struct pam_conv pamc;
/* Указание диалоговой функции. */
pamc.conv = &misc_conv;
pamc.eppdata_ptr = NULL;
/* Начало сеанса аутентификации. */
pam_start("su", getenv("USER"), &pamc, &pamh);
/* Аутентификация пользователя. */
if (pam_authenticate(pamh, 0) != PAM_SUCCESS)
fprintf(stderr, "Authentication failed!n");
else
fprintf(stderr, "Authentication OK.n");
/* Конец сеанса. */
pam_end(pamh, 0);
return 0;
}
Чтобы скомпилировать эту программу, необходимо подключить к ней две библиотеки: libpam и libpam_misс:
% gcc -о para pam.c -lpam -lpam_misc
Сначала программа создает объект диалога, который используется библиотекой РАМ, когда ей требуется запросить у пользователя данные. Функция misc_conv(), адрес которой записывается в объект, — это стандартная диалоговая функция, осуществляющая терминальный ввод-вывод. Можно написать собственную функцию, отображающую всплывающее окно, использующую голосовой ввод-вывод или реализующую другие способы общения с пользователем.
Затем вызывается функция pam_start(), которая инициализирует библиотеку РАМ. Первый аргумент функции — это имя сервиса. Оно должно уникальным образом идентифицировать приложение. Программа не будет работать, пока системный администратор не настроит систему на использование указанного сервиса. В данном случае задействуется сервис su, при котором программа аутентифицирует пользователей так же, как это делает команда su. В реальных программах так поступать не следует. Выберите реальное имя сервиса и создайте сценарий инсталляции, который позволит системному администратору правильно настраивать механизм аутентификации.
Второй аргумент функции — это имя пользователя, которого требуется аутентифицировать. В данном примере имя пользователя берется из переменной среды USER (обычно это имя соответствует эффективному идентификатору текущего процесса, но так бывает не всегда). В большинстве реальных программ в данном месте выдается запрос на ввод имени. Третьим аргументом является ссылка на объект диалога. В четвертом аргументе указывается переменная, в которую функция pam_start() запишет дескриптор сеанса. Этот дескриптор должен передаваться всем последующим функциям библиотеки РАМ.