Чужая задача в cron: где ещё смотреть, кроме crontab
Когда возникает подозрение, что на сервере кто-то посторонний, первое, что проверяют почти все — вывод crontab -l. И почти всегда получают чистый ответ: заданий нет, всё спокойно. Проблема в том, что crontab -l показывает только личный crontab текущего (или явно указанного) пользователя — а периодическая задача в Linux может жить минимум в шести-семи других местах, ни одно из которых эта команда не покажет. Ниже — полная карта, куда смотреть, чтобы не пропустить чужую задачу, замаскированную под системную рутину.
Содержание
- Где вообще может жить периодическая задача — полная карта
- Свой crontab — это только один адрес, и не только свой пользователь
- /etc/crontab и /etc/cron.d: то, что не покажет ни один crontab -l
- Каталоги run-parts: cron.hourly, cron.daily и anacron
- systemd timers: параллельный механизм, который часто вообще не проверяют
- Как сверить найденное с ожидаемым состоянием
Где вообще может жить периодическая задача — полная карта
Прежде чем идти по каждому пункту отдельно, стоит увидеть список целиком — именно его отсутствие в голове и создаёт ложное чувство безопасности после одного crontab -l.
| Место | Что это | Кто обычно проверяет |
|---|---|---|
/var/spool/cron/crontabs/<user> (Debian/Ubuntu) или /var/spool/cron/<user> (RHEL/AlmaLinux) | Личные crontab всех пользователей на диске | Почти никто — смотрят только crontab -l для себя |
/etc/crontab | Системный crontab с явным указанием пользователя в каждой строке | Иногда |
/etc/cron.d/* | Пакетные и ручные системные задачи, формат как у /etc/crontab | Редко |
/etc/cron.hourly, .daily, .weekly, .monthly | Скрипты, которые запускает run-parts через /etc/crontab или anacron | Почти никогда |
/etc/anacrontab + /var/spool/anacron/ | Anacron — задачи для систем, которые не работают круглосуточно | Почти никогда |
at-задачи (atq, /var/spool/cron/atjobs) | Разовые отложенные задачи, не периодические, но той же природы | Почти никогда |
systemctl list-timers --all | Systemd timers — параллельный механизм, полностью независимый от cron | Часто вообще не знают, что это альтернатива |
/etc/cron.allow, /etc/cron.deny | Кто вообще имеет право пользоваться cron | Никогда |
Дальше — по каждому пункту с конкретными командами и с объяснением, почему именно там любят прятаться.
Свой crontab — это только один адрес, и не только свой пользователь
crontab -l без флага -u показывает crontab только текущего пользователя. Если вы залогинены как обычный пользователь или как root через sudo, но не через явный sudo -u, вы видите только один crontab из потенциально десятков на сервере. У каждого пользователя в системе, у которого есть shell (и даже у некоторых, у кого его нет), может быть собственный файл в /var/spool/cron/crontabs/ (Debian-семейство) или /var/spool/cron/ (RHEL-семейство).
Первое, что стоит сделать при проверке — не гадать, а посмотреть список файлов напрямую:
sudo ls -la /var/spool/cron/crontabs/ # Debian/Ubuntu
sudo ls -la /var/spool/cron/ # RHEL/AlmaLinux/CentOS
Каждый файл в этой директории — это имя пользователя, у которого есть личный crontab. Дальше проверьте по очереди содержимое каждого:
for f in /var/spool/cron/crontabs/*; do
echo "=== $f ==="
sudo cat "$f"
done
Ключевой момент — не ограничивайтесь пользователями с интерактивным логином. Служебные аккаунты вроде www-data, nginx, mysql, postgres, redis, backup, nagios, zabbix обычно имеют shell /usr/sbin/nologin или /bin/false, но это не мешает им иметь собственный crontab — механизм cron не проверяет, может ли пользователь логиниться интерактивно, он проверяет только /etc/cron.allow и /etc/cron.deny. Задача, спрятанная в crontab сервисного аккаунта, — классический ход: её не увидит ни владелец при проверке своего профиля, ни автоматизированный аудит, если он смотрит только «человеческих» пользователей.
Полный обход всех пользователей системы одной командой (от root):
for u in $(cut -f1 -d: /etc/passwd); do
out=$(crontab -l -u "$u" 2>/dev/null)
if [ -n "$out" ]; then
echo "=== $u ==="
echo "$out"
fi
done
Про то, как выглядит реальный инцидент с чужой задачей в crontab пользователя, которого никто не заводил, мы разбирали отдельно: reverse shell в cron у пользователя, которого никто не заводил — там задача маскировалась под легитимный процесс и была замечена не по алерту безопасности, а по всплескам сетевого трафика.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать сервер/etc/crontab и /etc/cron.d: то, что не покажет ни один crontab -l
Отдельный слой, не связанный с личными crontab-файлами пользователей, — системные конфиги /etc/crontab и каталог /etc/cron.d/. Формат строки в них отличается от личного crontab на одно поле: сразу после пяти полей расписания идёт имя пользователя, от которого запускается команда:
# /etc/crontab
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || run-parts --report /etc/cron.daily
0 3 * * * backup /usr/local/bin/sync-to-s3.sh
Эта разница в формате — частая причина, почему задачу в /etc/crontab не замечают: человек, привыкший читать личный crontab (5 полей + команда), видит шестое поле и на автомате читает его неправильно, либо вообще не открывает файл, считая его «системным и трогать не надо».
/etc/cron.d/ — ещё более удобное место для маскировки: легитимных файлов там обычно много, пакеты вроде sysstat, logrotate, certbot, php, панели управления оставляют там свои задачи при установке. Одна лишняя строка в существующем файле или новый файл с правдоподобным именем (php-session-cleanup, system-check, .logrotate-tmp) легко теряется среди десятка легитимных.
ls -la /etc/cron.d/
for f in /etc/cron.d/*; do
echo "=== $f ==="; cat "$f"
done
Смотрите не только на содержимое, но и на метаданные файлов: владельца, права, время изменения.
stat -c '%Y %n' /etc/cron.d/* | sort -n
Файлы в /etc/cron.d/ должны принадлежать root:root и иметь права не выше 644 — некоторые реализации cron игнорируют файлы с более широкими правами или чужим владельцем, но полагаться на это как на защиту не стоит: сам факт добавления задачи уже сигнал, независимо от того, сработает она или нет.
Каталоги run-parts: cron.hourly, cron.daily и anacron
/etc/cron.hourly/, /etc/cron.daily/, /etc/cron.weekly/, /etc/cron.monthly/ — это не расписания, а каталоги со скриптами. Их запускает run-parts — утилита, которая просто выполняет по очереди все исполняемые файлы в указанной директории. Вызов на выполнение каждого из этих каталогов прописан либо в /etc/crontab (как в примере выше), либо через anacron, если он установлен.
Проверка та же логика: посмотреть содержимое, проверить, что каждый файл — это то, что вы ожидаете увидеть.
ls -la /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/
Здесь особенно легко спрятать задачу под нейтральным именем без расширения — 0anacron, apt-compat, logrotate, man-db уже там по умолчанию, и файл dpkg-backup или sysstat-check не привлечёт внимания при беглом просмотре. Проверять нужно не только сам факт наличия файла, но и его происхождение — реально ли его поставил пакет (см. раздел про сверку с ожидаемым состоянием ниже).
Anacron — отдельный механизм, изначально придуманный для машин, которые не работают круглосуточно (ноутбуки, рабочие станции): если сервер был выключен в момент, когда должна была сработать задача cron.daily, anacron досчитает пропуск и выполнит её при следующем включении. На постоянно работающем сервере anacron часто вообще не нужен, но пакет нередко стоит по умолчанию, и его конфиг — ещё одно место, куда стоит заглянуть:
cat /etc/anacrontab
ls -la /var/spool/anacron/
/etc/anacrontab имеет свой формат (период в днях, задержка в минутах, идентификатор задачи, команда) — и снова: чем меньше человек привык туда заглядывать, тем удобнее это место для постороннего.
Отдельно стоит at — механизм для одноразовых отложенных задач, не периодических, но той же природы риска: задачу можно поставить один раз через неделю, месяц или год, и она не появится ни в одном crontab.
atq # список отложенных задач
at -c <номер задачи> # содержимое конкретной задачи
ls -la /var/spool/cron/atjobs/ # или /var/spool/cron/atspool/ в зависимости от дистрибутива
systemd timers: параллельный механизм, который часто вообще не проверяют
Это самый частый пробел в проверках — не потому, что там сложно искать, а потому, что многие администраторы просто не держат в голове, что systemd timers вообще существуют как альтернатива cron. Задача, оформленная как systemd timer, не появится ни в одном из перечисленных выше файлов — у неё полностью своя инфраструктура: unit-файл .timer (расписание) плюс связанный с ним unit-файл .service (что выполнять).
Полный список активных таймеров:
systemctl list-timers --all
Флаг --all обязателен — без него команда покажет только таймеры, у которых есть ближайший запуск в будущем; неактивные или отключённые таймеры без него не видны. Для каждого подозрительного таймера смотрите, что именно он запускает:
systemctl cat имя-таймера.timer
systemctl cat имя-таймера.service
Unit-файлы физически могут лежать в нескольких местах, часть из них — не под управлением пакетного менеджера:
find /etc/systemd/system /usr/lib/systemd/system /run/systemd/system -name "*.timer" 2>/dev/null
/usr/lib/systemd/system/ — сюда пишут пакеты, /etc/systemd/system/ — обычно пишут вручную (в том числе через systemctl edit) или через configuration management. Задача, добавленная вручную в /etc/systemd/system/, для администратора, который ищет «cron-задачи», абсолютно невидима — он просто не смотрит в эту сторону.
Ещё нюанс: OnCalendar= поддерживает расписания практически той же гибкости, что и cron, плюс OnBootSec= и OnUnitActiveSec= — запуск через интервал после загрузки или после предыдущего запуска, чего у обычного cron нет вообще. Это делает systemd timers не менее удобным инструментом для постороннего, а часто и более скрытным — именно из-за непривычки туда заглядывать.
Как сверить найденное с ожидаемым состоянием
Собрать полный список задач — это только половина работы. Вторая половина — понять, какие из них легитимны, а какие нет, потому что и /etc/cron.d/, и /etc/cron.daily/, и systemd timers в норме содержат десятки легитимных записей от установленных пакетов. Голый список без сверки с эталоном почти бесполезен.
Сверка через пакетный менеджер. Для файлов в /etc/cron.d/, /etc/cron.daily/ и подобных можно прямо спросить систему, кто владелец файла:
dpkg -S /etc/cron.d/имя-файла # Debian/Ubuntu
rpm -qf /etc/cron.d/имя-файла # RHEL/AlmaLinux
Если команда не находит пакет-владельца — файл не был установлен пакетом, значит, либо это результат ручных действий администратора (что нормально, если вы про них знаете), либо это то, что вы ищете. Аналогично для systemd unit-файлов в /usr/lib/systemd/system/.
Сверка по времени изменения. Дата установки пакета известна пакетному менеджеру, а mtime файла в cron.d или systemd system обычно совпадает с датой установки пакета — если файл «моложе» и никто из команды не помнит недавних изменений или деплоев, это повод присмотреться:
find /etc/cron.d /etc/cron.daily /etc/cron.hourly /etc/systemd/system -newer /var/log/apt/history.log -type f
Сверка с infrastructure-as-code. Если сервер поднят через Ansible, Terraform, cloud-init или просто через набор bash-скриптов в git-репозитории — в этом репозитории должен быть полный список задач, которые вы сознательно разворачивали. Diff между тем, что реально стоит на сервере, и тем, что описано в репозитории — самый надёжный способ найти лишнее, потому что не зависит от памяти человека.
Постоянный мониторинг изменений через auditd. Разовая проверка находит то, что уже есть, но не защищает от того, что появится завтра. Если на сервере настроен auditd, добавьте правила слежения именно за директориями cron и systemd — тогда любое изменение попадёт в лог сразу, а не будет обнаружено при следующей ручной проверке через месяц:
-w /etc/cron.d -p wa -k cron_change
-w /etc/crontab -p wa -k cron_change
-w /var/spool/cron -p wa -k cron_change
-w /etc/systemd/system -p wa -k systemd_change
Подробно про настройку самого auditd и чтение таких логов — в отдельной статье про настройку auditd для аудита логов сервера. Общий подход к тому, что проверять на новом сервере, чтобы подобных сюрпризов не возникало с самого начала, собран в чек-листе безопасности нового сервера, а методика быстрой ревизии доступов, которая часто идёт рука об руку с проверкой cron (потому что раз есть чужая задача — стоит проверить и чужие SSH-ключи), разобрана в статье про аудит authorized_keys за час.
Отдельно смотрите на содержимое команд, а не только на факт их наличия. Что должно настораживать в любой из перечисленных выше локаций: команда с curl/wget, пайпнутая в bash или sh; строки, декодирующие base64; обращения к IP-адресам вместо доменных имён; «неровные» интервалы расписания (раз в 7 минут вместо 5 или 10 — иногда выбирают специально, чтобы не совпадать с типичными интервалами мониторинга); задачи от пользователей без явной причины иметь автоматизацию.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Если crontab -l пустой у всех пользователей, значит, cron точно чист?
Нет. Это только один из семи-восьми проверенных пунктов. Проверьте /etc/crontab, /etc/cron.d/, каталоги cron.daily/cron.hourly, anacron и отдельно systemctl list-timers --all — задача может быть оформлена как systemd timer и вообще не касаться механизма cron.
Почему атакующий может использовать systemd timer, а не обычный cron?
По той же причине, по которой это удобно легитимным задачам: гибкое расписание, встроенное логирование через journal и, что важнее, — привычка администраторов искать проблему именно в cron, а не в systemd. Проверка по одному каналу создаёт ложное чувство завершённости.
Нужно ли проверять crontab сервисных аккаунтов вроде www-data или postgres, если у них нет интерактивного логина?
Да, обязательно. Отсутствие shell /usr/sbin/nologin никак не мешает пользователю иметь свой crontab — механизм cron проверяет только /etc/cron.allow//etc/cron.deny, а не возможность логиниться.
Как быстро понять, что файл в /etc/cron.d действительно от пакета, а не подложен вручную?
Командой dpkg -S /etc/cron.d/имя-файла (Debian/Ubuntu) или rpm -qf /etc/cron.d/имя-файла (RHEL-семейство). Если пакет-владелец не находится — это либо ручная правка администратора, либо подозрительная находка.
Достаточно ли одной ручной проверки после инцидента, или нужен постоянный контроль?
Разовая проверка находит только то, что уже есть на момент проверки. Настройте auditd на слежение за /etc/cron.d, /etc/crontab, /var/spool/cron и /etc/systemd/system — тогда новое изменение сразу попадёт в лог с указанием, кто и когда его внёс.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →