Set-User-ID и Set-Group-ID
08/10/2018
Благодаря великолепной книге “Advanced Programming in the UNIX Environment” (W. Richard Stevens, Stephen A. Rago) я бы хотел рассказать о малоизвестных битах режима исполняемых файлов set-user-ID и set-group-ID.
Привилегии UNIX систем, такие как возможность изменения системных настроек (текущей даты) или управление доступом чтения/записи конкретных файлов, базируются на UID (user ID) и GID (group ID).
Каждый процесс имеет несколько ассоциированных идентификаторов пользователей и групп, которые определяеются текущим пользователем, владельцем и режимом запуска исполняемого файла:
ID | Описание |
---|---|
real user/group ID | Кто мы на самом деле, берутся из /etc/passwd при логине текущего пользователя, обычно не меняются в течении всей сессии, однако процесс с привелегиями суперпользователя может их изменить. |
effective user/group ID | Фактические текущие идентификаторы пользователя и группы, который определяют привелегии использующиеся для проверки разрешений доступа данного процесса к ресурсам. |
saved set-user-ID/set-group-ID | Функция exec копирует (сохраняет) значения эффективных идентификаторов при старте выполнения программы на случай, если потребуется их восстановить позже. Существуют системные вызовы setuid /setgid , которые могут меня привелегии в процессе выполнения программы. |
Когда мы запускаем на исполнение файл программы, effective user ID процесса обычно равен real user ID, а effective group ID соответственно равен real group ID. Однако, исполняемому файлу можно установить специальные флаги (биты режима файла) set-user-ID и set-group-ID, которые говорят следующее: “Когда этот файл будет запущен на исполнение, нужно выставить его effective user/group ID равный UID/GID владельца данного файла”. Другими словами это возможность застолбить привелегии владельца программы независимо от того, кто будет запускать ее в будущем. Луше всего это понять на примере. Сначала создадим самый обыкновенный исполняемый файл (ну если быть точнее, то интерпретируемый исполняемый файл для оболочки) и посмотрим, какие идентификаторы получит процесс во время запуска:
$ id # Кем я залогинен?
uid=501(tensho) gid=20(staff) ...
$ cat <<EOT > prog.rb # Пишем исходный код
#!/usr/bin/ruby
include Process
puts "Process uid=(#{uid}) gid=(#{gid}) euid=(#{euid}) egid=(#{egid})"
EOT
$ chmod +x prog.rb # Делаем файл исполняемым
$ ls -lah prog.rb
-rwxr-xr-x 1 tensho staff 106B Oct 8 19:10 prog.rb
$ ./prog.rb # Запускаем программу
Process uid=(501) gid=(20) euid=(501) egid=(20)
Как видно реальный и эффективный идентификаторы совпадают с владельцем файла. Очевидно, что при смене пользователя мы увидим другие идентификаторы:
$ sudo -u nobody ./prog.rb # Запускаем программу с привелегиями пользователя nobody
Process uid=(4294967294) gid=(4294967294) euid=(4294967294) egid=(4294967294)
$ sudo ./prog.rb # Запускаем программу с привелегиями суперпользователя root
Process uid=(0) gid=(0) euid=(0) egid=(0)
Теперь посмотрим, как все будет выглядет при использовании set-user-ID/set-group-ID:
$ sudo chown root:wheel prog.rb # Меняем владельца файла на root:wheel
$ sudo chmod +s prog.rb # Выставляем set-user-id/set-group-id
$ ls -lah prog.rb # Буква 's' в отображаемом режиме файла указывает на установленный set-user-id/set-group-id
-rwsr-sr-x 1 root wheel 106B Oct 8 19:10 prog.rb
$ ./prog.rb # Запускаем программу
Process uid=(501) gid=(20) euid=(501) egid=(20)
Как видно, идентификаторы не поменялись, хотя мы этого ожидали. Дело в том, что в Unix существуте ограничение, которое заставляет ядро не принимать во внимание set-user-ID/set-group-ID выставленные для скриптов оболочки в виду уязвимостей безопасности. Поэтому мы переведем Ruby скрипт на C и скомпилируем самый настоящий бинарник:
$ cat <<EOT > prog.c # Пишем исходный код
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int
main(void)
{
printf("Process uid=(%d) gid=(%d) euid=(%d) egid=(%d)\n", getuid(), getgid(), geteuid(), getegid());
exit(0);
}
EOT
$ clang prog.c -o prog # Компилируем
$ sudo chown root:wheel prog # Меняем владельца файла на root:wheel
$ sudo chmod +s prog # Выставляем set-user-id/set-group-id
$ ls -lah prog # Буква 's' в отображаемом режиме файла указывает на установленный set-user-id/set-group-id
-rwsr-sr-x 1 root wheel 8.5K Oct 8 19:20 prog
$ ./prog # Запускаем программу
Process uid=(501) gid=(20) euid=(0) egid=(0)
Теперь все в порядке ^_^
set-user-ID/set-group-ID биты режима файла используется во многих системных утилитах, т.к. им нужно иметь привелегии работать с системными файлами будучи запущенными любым пользователем. Например passwd
требуется привилегия изменять /etc/passwd
файл, который принадлежит root
пользоавтелю, даже если саму команду запускает непосредственно пользователь tensho
.
Дополняшки
- Когда нашей программе требуются дополнительные привелегии для доступа к какому-либо неразрешенному ресурсу, то ей нужно изменить свой идентификатор на UID или GID обладающий соответствующими привелегиями. В процессе проектирования приложений нужно применять практику наимешних привелегий (least-privilege), когда определяется минимальный уровень привелегий для выполнения поставленной задачи, все остальное отсекается бритвой Оккама. Такая модель уменьшает риск манипулирования привелегиями программ недоброжелательным пользователем и в целом повышает безопасность системы.
su
(switch user) – утилита, которая меняет real IDs, effective IDs и saved IDs на идентификаторы целевого пользователя.sudo
(switch user + do command) – помимо смены пользователя выполняет заданную команду, по умолчанию без опции-u
переключается на пользователяroot
.