MAATRIX / Блог / Файлы изменились, а вы их не трогали: контроль целостности без агента

Файлы изменились, а вы их не трогали: контроль целостности без агента

MAATRIX

Однажды вы заходите на сервер и видите, что у /etc/crontab изменилось время модификации, а вы точно его не редактировали. Или dpkg -l показывает пакет, который вы не ставили. Первый вопрос — «что случилось?» — упирается в то, что сравнивать не с чем: вы не знаете, как файл выглядел вчера. Полноценные системы контроля целостности (FIM) решают это готовым агентом, но для одного-двух серверов ставить отдельный демон часто избыточно. Разберём, как получить рабочий контроль целостности голыми руками — find, sha256sum, rpm/dpkg и cron — и где у этого подхода честный потолок.

Когда самодельного контроля достаточно, а когда сразу нужен агент

Специализированный FIM-агент типа AIDE или Tripwire даёт то, что руками не собрать: перехват изменений на лету, защищённую базу с подписью, интеграцию с SIEM, готовые политики под конкретный дистрибутив. Но у этого есть цена — процесс, который постоянно ест дисковый I/O и CPU на сканирование, конфигурация, которую нужно поддерживать, и ещё один компонент, который может сломаться сам.

Ручной подход через find + sha256sum разумен, если у вас:

  • один-два сервера, а не парк из полусотни машин;
  • нет требований комплаенса, которые явно требуют сертифицированный FIM (PCI DSS, например, такое требование ставит прямо);
  • есть человек, который реально посмотрит на алерт, а не проигнорирует письмо от cron;
  • задача — заметить *факт* изменения важных файлов постфактум, а не поймать атаку в момент её развития.

Если у вас продакшн с деньгами клиентов, требование аудита или команда безопасности, которая спрашивает про непрерывный мониторинг файловой системы — не тратьте время на самоделку, сразу ставьте специализированный инструмент. Ручной вариант ниже — это не замена полноценному FIM, а рабочий минимум для тех, у кого его пока нет и не будет в обозримом будущем.

Снимок контрольных сумм: find + sha256sum

Идея простая: один раз, пока сервер точно «чистый» (сразу после установки и базовой настройки), вы снимаете хэш каждого важного файла и сохраняете список в надёжном месте. Потом периодически считаете хэши заново и сравниваете со списком.

Список файлов для контроля — не «всё подряд», а то, что реально критично и редко меняется:

# каталог, где будем хранить эталоны
sudo mkdir -p /var/lib/fim-manual
sudo chmod 700 /var/lib/fim-manual

# снимаем контрольные суммы критичных файлов и каталогов
sudo find /etc /usr/bin /usr/sbin /usr/lib/systemd \
  -type f \
  -exec sha256sum {} \; > /var/lib/fim-manual/baseline-$(date +%F).sha256

sudo chmod 400 /var/lib/fim-manual/baseline-*.sha256

Для более узкого и осмысленного списка — конкретные файлы, которые обычно и есть точка входа атакующего или источник «тихих» изменений:

FILES="/etc/passwd /etc/shadow /etc/group /etc/sudoers /etc/ssh/sshd_config \
       /etc/crontab /etc/hosts /etc/resolv.conf \
       /root/.ssh/authorized_keys /etc/pam.d/sshd \
       $(find /etc/cron.d /etc/cron.daily /etc/systemd/system -type f)"

sha256sum $FILES > /var/lib/fim-manual/baseline-core.sha256

Пара нюансов, которые ловят на практике:

  • find /etc -type f цепляет и конфиги, которые меняются регулярно (/etc/resolv.conf при смене DNS, /etc/hostname при переименовании) — такие файлы либо исключайте из списка, либо будьте готовы перегенерировать baseline после плановых изменений.
  • sha256sum не видит права доступа, а chmod 4755 на бинарнике — уже сам по себе инцидент. Для прав отдельно снимайте stat:
find /usr/bin /usr/sbin -type f -exec stat -c '%n %a %U:%G' {} \; \
  > /var/lib/fim-manual/perms-baseline.txt

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Сверка снимков и разбор расхождений

Сверка — это просто повторный прогон с сравнением:

sha256sum -c /var/lib/fim-manual/baseline-core.sha256 2>&1 | grep -v ": OK"

sha256sum -c печатает статус по каждому файлу; фильтр grep -v ": OK" оставляет только проблемы: FAILED (хэш не совпал — файл изменился) и строки No such file or directory (файл удалили). Если нужен ещё и список *новых* файлов, которых не было в baseline, сравнение хэшей не поможет — придётся сравнивать сами списки путей:

find /etc -type f | sort > /tmp/current-etc-files.txt
awk '{print $2}' /var/lib/fim-manual/baseline-core.sha256 | sort > /tmp/baseline-etc-files.txt
diff /tmp/baseline-etc-files.txt /tmp/current-etc-files.txt

Строки с < — файлы, которые были и пропали; строки с > — новые файлы, которых не было при снятии baseline.

Когда sha256sum -c находит FAILED, дальше три шага, а не паника:

  1. Посмотрите stat <файл> — время изменения (mtime) и изменения метаданных (ctime) часто расходятся с вашими воспоминаниями о плановых работах. Если правки были час назад, а вы вчера обновляли систему — это, скорее всего, ожидаемое изменение от apt upgrade или dnf update.
  2. Сверьте с логом планового обслуживания — если у вас есть журнал изменений (даже просто заметки в тикет-системе), это первый источник правды.
  3. Если объяснения нет — переходите к rpm/dpkg-верификации ниже, она скажет, отличается ли файл от эталона дистрибутива, или он вообще не принадлежит ни одному пакету.

rpm -Va и dpkg --verify: проверка пакетных файлов против эталона дистрибутива

У самодельных baseline есть слабое место — они верны ровно с момента снятия и не знают, «нормальное» ли это изменение (после apt upgrade, например) или нет. Пакетные менеджеры решают именно это: хранят контрольные суммы файлов пакета с момента установки и могут сверить текущее состояние с тем, что должно быть.

На системах на базе RPM (AlmaLinux, RHEL, CentOS):

# полная проверка всех установленных пакетов — может занять минуты
rpm -Va > /var/log/rpm-verify-$(date +%F).log

# проверка одного пакета
rpm -V openssh-server

# только файлы конфигурации (без общих ошибок вроде locale)
rpm -Va --nogroup --nouser

Формат вывода rpm -V — восьмисимвольный код перед именем файла, каждый символ отвечает за свой атрибут:

СимволЧто означает
Sизменился размер файла
Mизменились права доступа (mode)
5не совпадает MD5-контрольная сумма
Dизменилось major/minor устройства
Lизменился путь symlink
Uизменился владелец
Gизменилась группа
Tизменилось время модификации
cфайл конфигурационный (пометка, не ошибка)

Строка S.5....T c /etc/ssh/sshd_config — это изменённый конфиг (размер, хэш и время не совпадают с пакетом), но пометка c говорит, что это конфигурационный файл, и его изменение — обычно ваша осознанная правка, а не взлом. А вот ..5....T /usr/sbin/sshd без пометки c на бинарнике — уже повод разбираться немедленно: бинарники пакетов в норме не должны отличаться от того, что поставил пакетный менеджер.

На системах на базе Debian/Ubuntu аналог — debsums (в части дистрибутивов надо доставить отдельно) и встроенная проверка dpkg:

# debsums сверяет md5sum файлов пакета с базой dpkg
sudo apt install debsums
sudo debsums -c   # покажет только файлы с расхождением
sudo debsums -ce  # то же самое, но включая конфиги (обычно шумно)

# без debsums — более грубая проверка через dpkg -V (доступна не во всех версиях)
dpkg -V

dpkg -V в свежих версиях выводит похожий на rpm формат: ? означает, что контрольная сумма для файла отсутствует в базе (нормально для части конфигов), а 5 — расхождение хэша.

Важный нюанс: обе проверки видят только файлы, которые принадлежат пакетам. Бэкдор, положенный отдельным файлом в /usr/local/bin или /opt, ни rpm -Va, ни dpkg -V/debsums не найдут — это зона ответственности вашего собственного baseline из первого раздела.

Автоматизация: скрипт с cron и алертами

Ручные прогоны быстро забываются, поэтому весь смысл — завернуть проверку в скрипт и запускать по расписанию. Минимальный вариант:

#!/usr/bin/env bash
# /usr/local/sbin/fim-check.sh
set -euo pipefail

BASELINE=/var/lib/fim-manual/baseline-core.sha256
LOG=/var/log/fim-check.log
MAIL_TO="admin@example.com"

RESULT=$(sha256sum -c "$BASELINE" 2>&1 | grep -v ": OK" || true)

echo "$(date -Is) — проверка целостности" >> "$LOG"

if [ -n "$RESULT" ]; then
  echo "$RESULT" >> "$LOG"
  {
    echo "Обнаружены расхождения контрольных сумм на $(hostname):"
    echo "$RESULT"
    echo
    echo "rpm/dpkg-статус для справки:"
    if command -v rpm >/dev/null; then rpm -Va --nogroup --nouser | head -50; fi
    if command -v debsums >/dev/null; then debsums -c | head -50; fi
  } | mail -s "[FIM] Расхождения на $(hostname)" "$MAIL_TO"
else
  echo "изменений не найдено" >> "$LOG"
fi
sudo chmod 700 /usr/local/sbin/fim-check.sh

Задача в cron — раз в сутки ночью, чтобы не мешать нагрузке:

# crontab -e (от root)
17 3 * * * /usr/local/sbin/fim-check.sh

Если у вас уже настроен внешний мониторинг доступности задач (healthchecks-подобный сервис, который ждёт сигнал «я отработал»), добавьте curl с пингом в конец скрипта — так вы узнаете не только о расхождениях, но и о том, что проверка вообще не запустилась (например, из-за упавшего диска или сломанного cron).

Раз в неделю или месяц имеет смысл отдельно логировать полный rpm -Va / debsums -c без фильтрации — расхождения по конфигам вроде /etc/fstab после планового изменения диска ожидаемы, но полная картина иногда показывает то, что точечная проверка core-файлов пропускает.

Где хранить эталоны, чтобы их тоже не подделали

Слабое место всей схемы — если атакующий получил root, он может пересчитать baseline под изменённые файлы и подчистить лог, и следующая проверка честно скажет «всё ОК». Несколько мер, которые снижают этот риск без отдельного агента:

  • Храните копию baseline вне сервера. Скопируйте файл со списком хэшей на другой сервер сразу после снятия (scp, лучше через ключ с правом только на запись). Сравнивать стоит не только локально, но и «снаружи» — с эталона, до которого у скомпрометированного сервера доступа на запись нет.
  • Подписывайте baseline GPG-ключом, приватная часть которого не хранится на этом же сервере: gpg --detach-sign --armor baseline-core.sha256, при сверке — gpg --verify baseline-core.sha256.asc baseline-core.sha256.
  • Держите baseline в git-репозитории на отдельном сервере, куда этот сервер сам пушить не может. История коммитов — заодно готовый журнал: видно, когда и что поменялось между снимками.
  • Не давайте сервисному пользователю права на baseline. Каталог /var/lib/fim-manual должен быть читаем только root'ом, а не тем аккаунтом, от которого крутятся веб-приложения.

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

Ограничения подхода и когда переходить на полноценный FIM

Стоит честно проговорить, чего эта схема не делает:

  • Это не защита в реальном времени. Между изменением файла и прогоном cron проходят часы — при запуске раз в сутки у атакующего есть до суток, чтобы сделать своё дело и откатить файл обратно. Специализированные FIM-агенты (тот же класс, что AIDE и Tripwire) умеют подключаться к механизмам ядра вроде inotify/fanotify и видеть изменение почти мгновенно — руками такое собрать сложно и ненадёжно.
  • Она не видит память и работающие процессы. Бэкдор, живущий только в памяти запущенного сервиса, или подмена поведения через LD_PRELOAD без изменения файлов на диске контролем по хэшам не заметны вообще.
  • Она требует дисциплины человека. Скрипт пришлёт письмо — но если письма от cron сгружаются в папку, которую никто не читает, толку ноль. Тот же паттерн, что и с любым мониторингом: если алерты никто не смотрит — какая разница, сработали они или нет.
  • Она не масштабируется на много серверов. Один сервер — переносимо, десять — уже неудобно синхронизировать baseline вручную, полсотни — без централизованной системы это станет вашей второй работой.
  • rpm -Va/dpkg -V проверяют только пакетные файлы и полагаются на локальную базу пакетного менеджера — если атакующий модифицировал саму базу (для этого нужен root), проверка тоже соврёт.

Переходить на выделенный инструмент стоит, если появилось хотя бы одно из: требование комплаенса с явным пунктом про FIM; больше 3-5 серверов под одной командой; данные, чья утечка или подмена означает юридические или финансовые последствия; или просто усталость руками разбирать письма от cron каждое утро. Если рассматриваете новый сервер под это, стоит сразу подготовить его к аудиту безопасности и продумать логирование через auditd, который перехватывает системные вызовы ядра и закрывает часть слепых зон ручной схемы — в первую очередь задержку между изменением и обнаружением.

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

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли использовать md5sum вместо sha256sum?

Технически да, но не стоит — MD5 уязвим к коллизиям, и хотя для честной задачи «просто заметить нечаянное изменение» это не критично, sha256sum ничего не стоит по производительности на масштабе одного сервера, а привычка использовать более слабый алгоритм переносится и туда, где это уже важно.

Как часто снимать новый baseline?

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

rpm -Va показывает расхождения по файлам, которые я точно не трогал — это норма?

Да, это частая ситуация: постинсталляционные скрипты пакетов сами меняют часть файлов после установки (генерируют ключи, пишут PID и подобное), и rpm -Va про такие файлы честно скажет «отличается от базы», хотя это штатное поведение пакета. Смотрите на пометку c (конфиг) и здравый смысл, а не тревожьтесь по каждой строке.

Стоит ли гнать проверку каждый час вместо раза в сутки?

Можно, если сервер не нагружен и файлов для проверки немного — sha256sum по паре сотен файлов из /etc займёт доли секунды. Но чем чаще, тем больше шум от легитимных изменений и тем быстрее вы начнёте игнорировать алерты — лучше сузить список файлов и повысить частоту, чем гонять полный find /etc каждый час.

Заменяет ли эта схема бэкапы?

Нет. Контроль целостности говорит, что что-то изменилось, но не восстанавливает исходное состояние. Восстановление — отдельная задача, для неё нужны настоящие бэкапы с версионированием, а не просто список хэшей.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →