В свете уже поднятой и рассмотренной здесь темы встаёт закономерный вопрос: всё это конечно хорошо и замечательно, но как можно наделить конкретными привилегиями не отдельный процесс, а пользователя, чтобы можно было создавать своих root'ов и при этом ограничить множество доступных пользователю привилегий? Ответ на этот вопрос - Pluggable Authentication Modules, также известные, как подключаемые модули аутентификации или попросту PAM, а именно, модуль pam_cap.so. Вместо того, чтобы ассоциировать разрешения с конкретными файлами, мы могли бы наделить конкретного пользователя нужной привилегией. При успешной авторизации шелл пользователя получит необходимое разрешение в множество наследуемых разрешений, которое будет унаследовано всеми его дочерними процессами, а это значит всеми процессами данного пользователя.
Сперва добавим модуль авторизации:
# cat /etc/pam.d/system-login auth required pam_cap.so
Теперь определим, что и кому мы разрешаем:
# cat /etc/security/capability.conf cap_kill,cap_setpcap luser none *
Пользователь luser получает право на привилегии cap_kill - обход проверок безопасности при посылке сигналов другим процессам, не принадлежащим данному пользователю, и разрешение на манипуляции привилегиями. По умолчанию модуль pam_cap.so обычно не подключается, но если бы подключался, то обычный "бесправный" пользователь описывался бы строкой "none *".
Итак, luser получает свои разрешения cap_kill и cap_setpcap, а все остальные не будут иметь никаких разрешённых привилегий. Кстати, особенность состоит в том, что присваиваемые таким образом привилегии не являются активными. Они лишь наследуются и могут быть разрешены - далее мы увидим, как, а пользовательский процесс, требующий, допустим, привилегию cap_net_raw, сможет её активировать. Изначально же она будет присутствовать лишь во множестве наследуемых (inheritable) привилегий. Чем-то это напоминает Windows, где пользователи из группы администраторов могут иметь в токене безопасности привилегию отладки процесса (т.е., практически полного доступа к нему, включая операции над содержимым его памяти), что ещё не означает автоматически возможность каждого администраторского процесса воспользоваться ею, ведь её ещё надо активировать (что обычно происходит после принятия консента на системах с UAC - т.е. всей линейки Vista+). Однако здесь у нас всё чуть-чуть сложнее.Проверим, что у нас получилось:
$ grep ^Cap /proc/$$/status CapInh: 0000000000000120 CapPrm: 0000000000000000 CapEff: 0000000000000000 CapBnd: 0000001fffffffff $ capsh --decode=0000000000000120 0x0000000000000120=cap_kill,cap_setpcap
Вроде бы всё работает. Следует заметить, что само по себе это ещё мало что значит, ведь разрешённые привилегии только лишь наследуются, но они даже не входят в множество доступных привилегий (CapPrm). Рассмотрим небольшой самодельный пример:
#include <errno.h> #include <stdio.h> #include <string.h> #include <sys/capability.h> #include <sys/prctl.h> #include <sys/types.h> #include <unistd.h> int cap_enable (cap_value_t cap) { cap_t _caps; _caps = cap_get_proc (); if (_caps == NULL) return -1; if (cap_set_flag (_caps, CAP_EFFECTIVE, 1, &cap, CAP_SET) != 0) return -1; if (cap_set_proc (_caps) != 0) return -1; return 0; } void dump_caps (const char* img_name, const char* m) { if (m) printf (m); printf (" file caps %s\n", cap_to_text (cap_get_file (img_name), NULL)); printf (" process caps %s\n", cap_to_text (cap_get_proc (), NULL)); return; } int main(int argc, char ** argv) { dump_caps (argv [0], "before enabling...\n"); sleep (30); cap_enable (CAP_KILL); dump_caps (argv [0], "after enabling...\n"); sleep (60); }Его сборка и выполнение должны дать такие результаты:
$ ./cap_test before enabling... file caps (null) process caps = cap_kill,cap_setpcap+i after enabling... file caps (null) process caps = cap_kill,cap_setpcap+iТо есть, привилегии недоступны. Чтобы понять, в чём дело, вспомним уже упоминавшиеся формулы, по которым вычисляются разрешения:
P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & cap_bset) P'(effective) = F(effective) ? P'(permitted) : 0 P'(inheritable) = P(inheritable)где F - разрешения, ассоциированные с файлом, а P - с процессом. Штрихом обозначены разрешения после exec(). Иными словами, для того, чтобы наша программа получила привилегию в свой набор доступных разрешений, необходимо либо чтобы для файла было установлено наследование сообветствующей привилегии, либо, чтобы она изначально входила в множество доступных привилегий файла. Т.е., либо cap_kill+i, либо cap_kill+p. Остановимся на первом варианте:
# setcap cap_kill+i ./cap_test $ ./cap_test before enabling... file caps = cap_kill+i process caps = cap_kill+ip cap_setpcap+i after enabling... file caps = cap_kill+i process caps = cap_kill+eip cap_setpcap+i [ в другом терминале между паузами ] $ grep ^Cap /proc/`pgrep cap_test`/status CapInh: 0000000000000120 CapPrm: 0000000000000020 CapEff: 0000000000000000 CapBnd: 0000001fffffffff $ grep ^Cap /proc/`pgrep cap_test`/status CapInh: 0000000000000120 CapPrm: 0000000000000020 CapEff: 0000000000000020 CapBnd: 0000001fffffffffНа первый взгляд ненамного удобнее. Более того, мы всё ещё зависим от разрешений, ассоциированных с файлами. Однако именно в этом и состоит гибкость. Во-первых, при распределении привилегий среди пользователей, мы наделяем их лишь некоторыми полномочиями. И именно пользователей. Кто-то сможет посылать сигналы чужим процессам, а кто-то нет. Во-вторых, с помощью дисковых разрешений, мы определяем, что пользователям можно запускать с данными привилегиями, а что нет, потому что позволять пользователю запускать вообще всё, что попало при данном повышенном уровне привилегий - это тоже не лучшее решение. Так что наделив пользователя небольшим кусочком власти, мы всё ещё контролируем, как он сможет этой властью воспользоваться. Однако приведённый пример имеет одну небольшую особенность. Программа в курсе относительно привилегий и сама изменяет свои разрешения на основе заданных значений. Для того, чтобы применять привилегии к любым программам, в том числе таким, которые могут ничего не знать о привилегиях, необходимо снова использовать форсированные разрешения, которые ассоциированы с файлами на диске. Например, так setcap cap_kill+ei ./cap_test (при логине пользователь получает унаследованное разрешение cap_kill, для файла cap_test cap_kill тоже помечено, как наследуемое разрешение - в соответствие с правилом, приведённом выше, при запуске cap_test разрешение cap_kill будет занесено в его множество доступных привилегий - P'(permitted) = P(inheritable) & F(inheritable); если для данного файла данное разрешение входит в множество эффективных разрешений, то оно заносится и в множество эффективных, или действительных, разрешений выполняемого процесса: P'(effective) = F(effective) ? P'(permitted) : 0). Допустим, что наш cap_test имеет такие разрешения:
$ getcap ./cap_test ./cap_test = cap_kill,cap_net_admin+eiОднако, процесс не получит привилегию cap_net_admin в какое-либо из своих множеств, если эта привилегия не была предоставлена пользователю через PAM:
$ ./cap_test before enabling... file caps = cap_kill,cap_net_admin+ei process caps = cap_kill+eip cap_setpcap+i after enabling... file caps = cap_kill,cap_net_admin+ei process caps = cap_kill+eip cap_setpcap+iТаким образом, это скорее механизм ограничения и разграничения привилегий, чем наделения ими :) На первый взгляд всё несколько запутанно, но на деле, привилегии не так страшны. Напоследок, если вдруг по какой-то причине вы не можете воспользоваться библиотекой libcap, активировать привилегию можно вот так, напрямую используя сисколлы (не совсем напрямую, ибо libc, но уже без посредничества libcap):
#include <sys/capability.h> int main (int argc, char **argv) { struct __user_cap_header_struct hdr; struct __user_cap_data_struct data; memset (&hdr, 0, sizeof(hdr)); hdr.version = _LINUX_CAPABILITY_VERSION; if (capget (&hdr, &data) < 0) return 1; data.effective |= CAP_TO_MASK(CAP_KILL); data.effective |= CAP_TO_MASK(CAP_SETPCAP); if (capset (&hdr, &data) < 0) return 2; return 0; }Но разумеется, лучше пользоваться libcap, как более портабельным API.
No comments:
Post a Comment