Делаем собственный PAM-модуль
После теории посмотрим в глубь PAM. Попытаемся разобраться как же оно работает.
Пример1: Новый вход в систему.
Вы уже наверно во сне видите изрядно доставшие строки
login:***Password:*******
Эта схема придуманная много лет назад продолжает служить и сейчас. Но не паролем единым жив человек. Давайте сделаем что-то новенькое, необычное. Будет и самому интересно и вредителей введет в заблуждение, ибо такого не будет не на одной другой машине.
До PAM’а нам пришлось бы переписывать весь login, а теперь мы просто сделаем новый модуль, соберем его как библиотеку и припишем в сценарий аутентификации /etc/pam.d/login. Модуль будет называться просто pam_test.
Но сначала о том, что же мы будем делать. Имеется следующая интересная схема аутентификации: система выдает пользователю несколько случайных чисел. Пользователь берет определенные из них, поставляет в ТОЛЬКО ЕМУ известный многочлен, например,
(дата)*x*x 3*x-(текущий час)*y z
и вводит вместо пароля ответ. Компьютер тоже считает по этому алгоритму и если ответ правильный, пускает в систему.
Схема идеальная: повышается математический уровень сотрудников, пароль вводится всегда другой, закономерность уловить по косвенным данным практически невозможно (коэффициенты у многочлена также меняются от времени, возможных многочленов тьма).
Есть конечно очевилные недостатки: тяжело считать, можно подсмотреть как пользователь считает на бумажке, сложны вопросы хранения многочленов … Но мы не будем столь огорчаться, ведь мы прежде всего учимся, не так ли.
Как вы прочитали ранее модули различаются по классам. Наш будет класса AUTH. То есть отвечать за аутентификацию.
Это его текст.
Делаем собственное PAM-приложение
Давайте-ка теперь посмотрим как видят систему PAM-приложения. Нудную и скучную документацию вы сами потом прочитаете (или я все-таки сделаю это когда-нибудь сам), а сейчас обратимся к практике.
Пример2: избавляемся от SUID-программ.
Прочитали название? Страшно? Все в порядке, речь в этом разделе не пойдет о чем-то необычном. Просто несколько теоретических размышлений и пример их реализации.
Одним из основных архитектурных недостатков UNIX с точки зрения защищенности является наличие SUID-программ, то есть способность процесса изменять одного владельца на другого после запуска. По-умолчанию, процесс приобретает права того пользователя, который его запустил.
Зачем это нужно. Дело в том, что иногда для доступа к тем или иным системным данным прав пользователя недостаточно. Так, например, программе passwd необходимо иметь права root для доступа к файлам /etc/passwd и /etc/shadow. Но будучи запущенной пользователем (ведь может же он поменять пароль самому себе), она не в состоянии это сделать.
Тут то и происходит фокус со сменой владельца. Запускаясь, процесс passwd получает права своего владельца (root) и теперь может спокойно работать с ранее запретными данными. Получается, что пользователь как-бы не получает прав администратора, а программа спокойно с ними работает.
Все гениально, просто и безопасно… Стоп, вот тут мы и не правы. Большинство атак, направленных на «переполнение буфера» как раз и пользуются этой возможностью процессов расширять свои возможности. Ну и что, что права получает только процесс, если его надлежащим образом накачать, то он запустит нам оболочку.
Что же делать. Давайте обратимся к опыту других операционных систем, не наступивших на грабли UNIX. В Windows NT нет SUID-программ. А что же делать, если кому-то нужно, скажем, поменять себе пароль? Очень просто. Есть программа пользователя, желающая поменять пароль и работающая с правами оного.
А есть сервер, работающий с правами администратора и готовый помочь всякому правильному клиенту. Вот пользовательский процесс делает запрос, сервер проверяет тот ли это пользователь и радостно выполняет поручение. При таком решении неизбежно возникают две проблемы:
Клиент-серверное взаимодействие должно быть грамотно написано (что бы не напороться на грабли несанкционированного превишения полномочий) и падает производительность системы на всех подобных операциях (в Windows это решается применением кеширования запросов).
А что если и в UNIX попытаться сделать , например, клиент-серверную авторизацию. Я понимаю, что для аккуратного решения этой проблемы надо очень крепко подумать и возможно даже изменить архитектуру, но разве это не интересно попробовать изменить как-то свою ОС?
Сразу оговорюсь, что в примерах не описан процесс аутентификации клиента. Понятно, что он должен быть достаточно хитроумным, желательно с применением криптографии. Кроме того, сервер обрабатывает только один запрос. В идеале он должен или постоянно висеть в системе как daemon, или вызываться через какой-нибудь wrapper. Все программы ниже будут выглядить максимально упрощенными. Лучше понимание, чем уважение без оного.
Итак, сказано-сделано. Пишем клиент (собственно passwd который будет вызывать пользователь).
Аргументы pam.
PAM использует аргументы для передачи информации подключаемому модулю
во время аутентификации. Аргументы позволяют в различных
конфигурационных файлах PAM использовать модули по-разному.
Аудит входа в linux через slack. разбираемся с pam

Библиотеки PAM (Pluggable Authentication Module) используются для добавления сложного сценария проверки учетных данных и выполнения дополнительных действий при аутентификации пользователя и доступе к службам. В этой статье мы разберемся с внутренней архитектурой PAM, особенностями конфигурации и сделаем простой модуль для отправки уведомлений в Slack при входе пользователя в систему.
PAM впервые появился в Solaris в 1995 году и в дальнейшем был реализован для других POSIX-совместимых систем (например, PAM-Linux). В отличии от NSS, который мы рассмотрели в предыдущей статье, PAM имеет дело с процессом аутентификации, контроля срока действия пароля и его обновления, процессом создания сеанса пользователя. Библиотека PAM представляет для системных приложений интерфейс для запроса аутентификации, проверки учетной записи и замены пароля, а также для организации диалога с пользователем (например, при входе в систему с вводом пароля с клавиатуры) и сохранения данных модуля. Интерфейс может использоваться системными программами (пример можно посмотреть в исходном коде login в util-linux) и кодом модулей PAM, для этого доступны следующие заголовочные файлы:
Конфигурация PAM собирается из нескольких источников: файла /etc/pam.conf, файлов в каталоге /etc/pam.d, системной конфигурации из файлов в /usr/lib/pam.d.
Для PAM поддерживается 4 группы действий:
Приложение запрашивает начало сеанса вызовом функции pam_start с передачей имени аутентифицируемого пользователя, названия сеанса и conversation-функции, которая будет использоваться для отображения сообщений в процессе диалога с пользователем. Для Linux-PAM часто в качестве conversation-функции используется misc_conv, для OpenPAM — openpam_ttyconv. При отправке запроса в conversation-функции указывается способ отображения и взаимодействия с пользователем (PAM_PROMPT_ECHO_OFF — ввод без эха, PAM_PROMPT_ECHO_ON — ввод с эхом, PAM_ERROR_MSG -—сообщение об ошибке, PAM_TEXT_INFO — вывод информации). Результатом вызова pam_start будет код состояния выполнения, который равен PAM_SUCCESS при успешном выполнении или соответствует коду ошибки (например, PAM_USER_UNKNOWN, если пользователь неизвестен). Также pam_start записывает в последний аргумент указатель pam_handle_t для управления сеансом (он также будет использовать в pam_end при завершении сеанса). Кроме pam_start доступна функция pam_start_confdir, которая позволяет переопределить каталог с конфигурационными файлами.
Следующие методы инициируют запуск последовательности модулей (согласно файлам конфигурации):
pam_authenticate(pam_handle_t *pamh, int flags)— выполняет проверку возможности аутентификации пользователя. Флаги могут содержать PAM_SILENT, если приложение хотело бы исключить взаимодействие с пользователем (неинтерактивный режим), а также PAM_DISALLOW_NULL_AUTHTOK (вернуть ошибку аутентификации, если нет сохраненного токена). Для выполнения вызывается функцияpam_sm_authenticateиз модулей. Модуль может получить имя пользователя через вызовpam_get_user(если было определено — вернется сразу, иначе отобразится подсказка). Также полезно использовать функцииpam_strerror(преобразует код ошибки в строку),pam_getenv,pam_getenvlist,pam_putenvдля работы с окружением модуля иpam_get_data,pam_set_dataдля долговременного сохранения данных модуля. Также может быть получены настройки PAM, например для получения conversation-функции можно запроситьpam_get_item(pamh, PAM_CONV, &conv). Также черезpam_get_itemмогут быть запрошены другие общие переменные:Может быть возращен один из следующих кодов ответа (кроме этого могут быть коды, связанные с недостатком памяти и внутренними ошибками):
pam_setcred(pam_handle_t *pamh, int flags)— обновление или удаление данных входа (может использоваться для перегенерации токена в активном сеансе), использует функциюpam_sm_setcredмодулей: Флаги могут содержать PAM_SILENT и одно из действий:Результатом выполнения может быть один из кодов возврата:
pam_acct_mgmt(pam_handle_t *pamh, int flags)— валидация учетной записи (например, срока действия пароля). Flags может быть установлен в PAM_SILENT. Использует функциюpam_sa_acct_mgmtмодуля. Может вернуть один из кодов ответа (а также общие ошибки):pam_open_session(pam_handle_t *pamh, int flags)— открыта сессия после успешного входа (чаще всего вызывается после pam_authenticate), использует функциюpam_sm_open_sessionмодуля. Flags может быть установлен в PAM_SILENT. Может вернуть один из кодов ответа (а также общие ошибки):pam_close_session(pam_handle_t *pamh, int flags)— завершить сессию, использует функциюpam_sm_close_sessionмодуля. Flags может быть установлен в PAM_SILENT. Может вернуть один из кодов ответа (а также общие ошибки):pam_chauthtok(pam_handle_t *pamh, int flags)— смена токена (например, пароля), использует функциюpam_sm_chauthtokмодуля. Flags может быть установлен в PAM_SILENT или PAM_CHANGE_EXPIRED_AUTHTOK (замена пароля только, если он устарел) и PAM_NO_AUTHTOK_CHECK (не проверять соответствие пароля установленным правилам). Может вернуть один из кодов ответа (а также общие ошибки):
Название модуля (несмотря на то, что он является библиотечным файлом) создается без префикса lib, например может называться pam_slack.so. Для реализации модуля создадим соответствующий файл для сборки через cmake:
cmake_minimum_required(VERSION 3.22)
project(pam_slack C)
set(CMAKE_C_STANDART 17)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}")
# подключить pam
find_package(PAM REQUIRED)
# не использовать префикс lib для библиотеки
set(CMAKE_SHARED_LIBRARY_PREFIX "")
target_link_libraries(${library_name} ${PAM_LIBRARIES})
include_directories( ${PAM_INCLUDE_DIR} ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
)
add_library(pam_slack SHARED library.c)Также добавим конфигурацию для обнаружения PAM в каталог проекта: https://invent.kde.org/plasma/kscreenlocker/-/raw/master/cmake/FindPAM.cmake
Определим заголовочный файл для библиотеки (мы будем реализовывать функции для работы с сессией):
#include <security/pam_misc.h>
int pam_sm_open_session(pam_handle_t *pamh, int flags, int args, const char **argv);
int pam_sm_close_session(pam_handle_t *pamh, int flags, int args, const char **argv);И реализуем простой вариант с отправкой сообщения в syslog:
#include "library.h"
#include <security/pam_ext.h>
#include <security/pam_modules.h>
int pam_sm_open_session(pam_handle_t *pamh, int flags, int args, const char **argv) { pam_syslog(pamh, 1, "Session is opened"); char* service_name = malloc(128); pamh->get_item(pamh, PAM_SERVICE, &service_name); pam_syslog(pamh, 1, service_name); free(service_name); return PAM_SUCCESS;
}
int pam_sm_close_session(pam_handle_t *pamh, int flags, int args, const char **argv) { pam_syslog(pamh, 1, "Session is closed"); char* service_name = malloc(128); pamh->get_item(pamh, PAM_SERVICE, &service_name); pam_syslog(pamh, 1, service_name); free(service_name); return PAM_SUCCESS;
}Как можно увидеть, модули принимают список аргументов, подобно консольным приложениям, они извлекаются из файла конфигурации и указываются после названия библиотеки.Выполним сборку библиотеки и добавим правило к конфигурации pam.
cmake .
cmake --build .
cp pam_slack.so /usr/lib/x86_64-linux_gnu/securityПуть к каталогу для Debian-систем, может быть другим, можно поискать по размещению файла pam_unix.so. Посмотрим теперь на пример записи файла конфигурации:
session optional pam_motd.so motd=/run/motd.dynamicПервое слово указывает на группу функций, к которым применяется модуль:
Второе слово определяет реакцию на код ответа, отличный от PAM_SUCCESS:
Альтернативно вместо реакции может указываться группа правил в квадратных скобках, определяющая связь между кодом ответа (слева, записано прописными буквами), справа результат, например:
[success=ok ignore=ignore module_unknown=ignore default=bad]Далее в строке указываются называется .so-файлов и параметры, которые передаются в соответствующие функции через argc / argv. В нашем случае создадим новый файл в /etc/pam.d:
#%PAM-1.0
session optional pam_slack.soДля отладки добавим в /etc/syslog.conf строку:
*.debug /var/log/debug.logТеперь при успешном входе пользователя (например, можно запустить login user) в файл будут добавляться сообщения об успешном входе. Также можно увидеть, что название сервиса будет указано как login (при удаленном входе сервис будет обозначен как remote). Это поможет нам для разработки второй части нашего приложения — отправки уведомления в Slack через web-hook.
Для выполнения запроса будем использовать libcurl. Для Slack поддерживает форма web hook, в котором содержание встраивается в параметры POST-запроса. Например, при исполнении запроса в консоли он может выглядить так:
curl -s -X POST https://hooks.slack.com/services/WORKSPACEID/CHANNELID/token -H "Content-Type: application/json" --data "{"text":"Пользователь зашел в систему"}" Для отправки уведомления создадим отдельную функцию, которая будет принимать имя пользователя и отправлять сообщение на адрес, полученный в параметрах вызова модуля:
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
int notify(char* user_name, char* url)
{ CURL *curl; CURLcode res; curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); if(curl) { curl_easy_setopt(curl, CURLOPT_URL, url); char* message = strcat(strcat(""text":"User", user_name), " is logged in"}"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, message); res = curl_easy_perform(curl); if(res != CURLE_OK) fprintf(stderr, "curl_easy_perform() failed: %sn", curl_easy_strerror(res)); curl_easy_cleanup(curl); } curl_global_cleanup(); return 0;
}И теперь выполним вызов при открытии сессии от сервиса login (локальный вход в систему):
#include "library.h"
#include <security/pam_ext.h>
#include <security/pam_modules.h>
int pam_sm_open_session(pam_handle_t *pamh, int flags, int args, const char **argv) { pam_syslog(pamh, 1, "Session is opened"); char* service_name = malloc(128); pamh->get_item(pamh, PAM_SERVICE, &service_name); if (strcmp(service_name, "login")==0) { char* user_name = malloc(128); pam->get_item(pamh, PAM_USER, &user_name); notify(user_name, argv[0]); free(user_name); } pam_syslog(pamh, 1, service_name); free(service_name); return PAM_SUCCESS;
}
int pam_sm_close_session(pam_handle_t *pamh, int flags, int args, const char **argv) { pam_syslog(pamh, 1, "Session is closed"); char* service_name = malloc(128); pamh->get_item(pamh, PAM_SERVICE, &service_name); pam_syslog(pamh, 1, service_name); free(service_name); return PAM_SUCCESS;
}Расширим CMakeLists.txt:
cmake_minimum_required(VERSION 3.22)
project(pam_slack C)
set(CMAKE_C_STANDART 17)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}")
# подключить pam
find_package(PAM REQUIRED)
find_package(CURL REQUIRED)
# не использовать префикс lib для библиотеки
set(CMAKE_SHARED_LIBRARY_PREFIX "")
target_link_libraries(${library_name} ${PAM_LIBRARIES} ${CURL_LIBRARIES})
include_directories( ${PAM_INCLUDE_DIR} ${CURL_INCLUDE_DIR} ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
)
add_library(pam_slack SHARED library.c)И повторим сборку и публикацию, но теперь укажем дополнительно адрес webhook в Slack:
session optional pam_slack.so https://hooks.slack.com/services/WORKSPACEID/CHANNELID/tokenТеперь при входе пользователя будет отправляться сообщение в подключенный канал в Slack. По умолчанию в системе установлено и настроено большое количество модулей, которые отвечают за аутентификацию пользователей (например, pam_unix.so использует файлы shadow, pam_wheel.so требует принадлежности пользователя к группе администраторов wheel, pam_rootok.so будет успешным только для пользователя root), проверки пароля (например, pam_passwdqc.so или pam_pwquality.so), настройки лимитов (pam_limits.so) и значения umask (pam_umask.so), ограничения повторного входа (pam_faildelay.so), ограничения по времени входа (pam_time.so), расшифровки раздела после аутентификации (pam_ecryptfs), контроля корректности оболочки (pam_shells, проверяет shell на соответствие списку из /etc/shells), проверки пароля на сложность ко взлому (pam_cracklib), вход с использованием сертификата или смарт-карты (pam_pkcs11.so). Есть возможность подсчитывать количество логинов (pam_tally.so) и запрещать вход при наличии файла /etc/nologin (pam_nologin.so). Также есть универсальные «заглушки» pam_permit.so (всегда возвращает PAM_SUCCESS) и pam_deny.so (всегда отказ доступа), их можно использовать в конце списка, чтобы определить поведение по умолчанию. Дополнительную информацию выводит pam_mail.so (сообщение о новой локальной почте), pam_motd.so (содержимое файла /etc/motd), pam_lastlog.so (время последнего входа в систему).
Для использования внешних способов аутентификации может использоваться pam_ldap (для применения LDAP-совместимого сервера как источника учетных данных и способа проверки пароля) или pam_userdb (использование базы данных для проверки входа). Для отладки также можно использовать pam_debug, который возвращает значения из функций, предопределенные в аргументах модуля.
Также существует большое количество модулей для использования аппаратных средств аутентификации (например, Yubikey), Bluetooth-устройств (пакет libpam-blue), биометрических данных (libpam-biometric), двухфакторной аутентификации (например, пакеты libpam-u2f, libpam-otpw, libpam-google-authenticator или libpam-oath), SSH-ключей (libpam-ssh), серверов RADIUS (libpam-radius), базы данных PostgreSQL (libpam-pgsql), MySQL (libpam-mysql). Можно также установить обертку для разработки PAM-модулей на Python (libpam-python). Для защиты от подбора пароля полезно использовать libpam-abl и libpam-shield. Ну и конечно, всегда есть возможность разработать собственный PAM-модуль и в этой статье мы рассмотрели общие принципы их создания и установки в систему.
Скоро состоится открытый урок «Docker. Работа с данными и сетью (Storage и Network drivers)», на котором мы:
— Рассмотрим особенности Docker по работе с данными и сетями.
— Разберем, как устроены и какие различают способы выгрузки данных с помощью механизма volumes на примере запуска docker-контейнеров и docker-compose.
— Познакомимся со Storage и Network драйверами.
— Рассмотрим, какие бывают Storage драйверы и с какими типами хранилищ они работают, рассмотрим совместимые backing filesystem.
— Разберемся, какие docker поддерживает Network-драйверы, какие сетевые топологии они позволяют организовать и какие бывают особенности и исключения для разных ОС.
Регистрируйтесь по ссылке.
Имена сервисов pam.
Имя сервиса каждого приложения использующего PAM — это имя его файла
конфигурации в /etc/pam.d. Каждое приложение, использующее PAM
определяет собственное имя сервиса.
Например, программа login определяет имя login, программа ftpd —
ftpd, и так далее.
В общем, имя сервиса — это имя программы использующей сервис, а не
программы его предоставляющей.
Конфигурационные файлы pam.
Каталог /etc/pam.d содержит в себе конфигурационные файлы PAM. В
ранних версиях PAM использовался файл pam.conf. Этот файл будет
использоваться если не было найдено каталога /etc/pam.d, однако его
использование нежелательно.
Каждое приложение (или сервис, используемый многими пользователями)
имеет собственный файл. Каждый файл имеет пять разных элементов:
имя сервиса, тип модуля, флаги управления, путь к модулю и аргументы.
Преимущества pam
PAM предоставляет множество полезностей
системному администратору, таких как:
- Общая схема аутентификации, которая может быть использована многими
приложениями. - С PAM могуть работать разнообразные приложения, которые не
нуждаются в перекомпиляции для поддержки PAM. - Невероятная гибкость управления аутентификацией для администраторов
и разработчиков ПО. - Разработчикам не нужно привязывать свои приложения к конкретной
схеме аутентификации. Вместо этого они могут сосредоточиться
непосредственно на разработке самого приложения.
Примеры конфигурационного файла pam.
Конфигурационный PAM файл приложения может выглядеть так:
| #%PAM-1.0 auth required /lib/security/pam_securetty.so auth required /lib/security/pam_unix.so shadow nullok auth required /lib/security/pam_nologin.so account required /lib/security/pam_unix.so password required /lib/security/pam_cracklib.so password required /lib/security/pam_unix.so shadow nullok use_authtok session required /lib/security/pam_unix.so |
Пути к pam модулям.
Пути к модулям говорят PAM, где искать подключаемый модуль. Обычно,
это представляет собой полный путь к модулю, такой как
/lib/security/pam_stack.so. Если путь к модулю не будет указан (или
он начинается не с ‘/’, то по-умолчанию считается, что модуль
расположен в /lib/security.
Флаги управления pam.
Каждый модуль PAM возвращает результат успешного выполнения или
неудачи. Флаги управления говорят PAM, что нужно делать с
результатом. Когда модули расположены в определенной
последовательности, флаги дают вам возможность установить «важность»
модуля по отношению к следующему за ним.
Вернемся к нашему конфигурационному файлу PAM для xlogin:
auth required /lib/security/pam_nologin.so auth required /lib/security/pam_securetty.so auth required /lib/security/pam_env.so auth sufficient /lib/security/pam_rhosts_auth.so auth required /lib/security/pam_stack.so service=system-auth account required /lib/security/pam_stack.so service=system-auth password required /lib/security/pam_stack.so service=system-auth session required /lib/security/pam_stack.so service=system-auth |
Четыре типа флагов, стандартных для PAM.
- required. Такой модуль должен быть успешно пройден. Лишь только в
этом случае пользователь будет допущен к проверке следующим
модулем. - requisite. Чтобы получить аутентификацию, этот модуль должен также
быть успешно пройден. Однако, если requisite модуль не будет
пройден, управление передается непосредственно приложению. - sufficient. При проверке, завершившейся неудачей, этот модуль будет
просто проигнорирован. Но, если sufficient модуль успешно пройден,
и не было неудач при прохождение вышестоящих required модулей, то
все остальные модули этого типа больше не будут рассматриваться и
будут считаться успешно пройденными. - optional. Успешное или неудачное прохождение модулей этого типа не
является критичным для общей картины аутентификации. Модули этого
типа играют роль только тогда, когда нет модулей других типов.
В настоящий момент в PAM доступны новые флаги управления. Смотрите
документацию PAM в /usr/share/doc/pam?(version-number) для получения
информации.
Выводы.
Не правда ли PAM это здорово… :))
Вход в личный кабинет