Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 

Repository files navigation

Ubuntu Hardening Script — VPS/VDS Edition (v2)

Скрипт автоматизированного харденинга Ubuntu 22.04 / 24.04 / 26.04 для виртуальных серверов. Запускается от root сразу после получения чистого VPS/VDS и проводит полную настройку безопасности в один проход.

Основан на практиках CIS Ubuntu Linux Benchmark с поправками на специфику VPS: физического доступа нет, консоль — только через панель провайдера, поэтому все меры, способные «уронить» сервер в офлайн (паника ядра при сбое аудита, пароль GRUB, агрессивный blacklist драйверов), либо смягчены, либо сознательно исключены. Каждое такое решение задокументировано в разделе Сознательные отклонения от CIS.


Содержание


Что нового в v2

Исправление Суть
ssh.socket Ubuntu 22.10+ запускает SSH через socket-активацию, при которой Port из sshd_config игнорируется. Скрипт отключает ssh.socket — это истинная причина проблемы «порт не поменялся»
Контрольная точка входа Проверка SSH-входа под admin теперь до блокировки root, а не в конце скрипта
Auditd: причина backlog exceeded Удалены правила аудита socket/connect — они генерировали тысячи событий/сек. Увеличение backlog лечило симптом, а не причину
Auditd 3.x Убраны dispatcher = /sbin/audispd и disp_qos — удалены из auditd 3.x (Ubuntu 22.04+), с ними демон мог не стартовать
Auditd: arch=b32 Ключевые правила продублированы для 32-битных syscalls — без этого аудит обходился 32-битными вызовами
/etc/ssh 755 Было chmod 700 /etc/ssh — это ломало исходящие ssh/scp/git для непривилегированных пользователей (нет доступа к ssh_config)
logrotate для audit удалён auditd ротирует логи сам (max_log_file_action=ROTATE); параллельный logrotate давал двойную ротацию и разрывы аудита
Тип ключа на выбор ed25519 (рекомендуется) или ecdsa-521
passwd в цикле 3 ошибки ввода пароля больше не роняют скрипт из-за set -e
chrony minsources При одном пользовательском NTP-сервере ставится minsources 1 — иначе время никогда не считалось бы синхронизированным
UFW: опрос портов Перед ufw reset показываются слушающие порты и можно открыть дополнительные (80, 443 и т.д.)
noexec /tmp и apt Временный каталог apt перенесён в /var/lib/apt/tmp — иначе ночные unattended-upgrades могли падать
userns под ядро kernel.unprivileged_userns_clone пишется только если существует в ядре; на 24.04+/26.04 используется apparmor_restrict_unprivileged_userns
Повторный запуск При immutable-правилах auditd (-e 2) секция 13 пропускается с предупреждением вместо аварийного завершения
usermod при повторном запуске Существующий пользователь теперь гарантированно добавляется в sudo
rsyslog в зависимостях Минимальные образы 24.04+ могут идти без rsyslog, на котором построено логирование sudo
Лог выполнения + trap Весь вывод дублируется в /root/hardening_<дата>.log; при ошибке trap показывает номер строки
Drop-in c Port Конфликтующие файлы /etc/ssh/sshd_config.d/* с директивой Port перемещаются в /root/sshd_config.d.bak
История root Симлинк .bash_history → /dev/null убран — это антифорензика; аудит команд ведёт auditd (priv_exec)
Мелочи dpkg -s вместо dpkg -l; фикс приоритета операторов в bind-mount /var/tmp; валидация NTP-серверов; AddressFamily в переменной

Требования

Параметр Значение
ОС Ubuntu 22.04 / 24.04 / 26.04 LTS
Платформа VPS / VDS (KVM, XEN, VMware, Hyper-V)
Пользователь root
Сеть Доступ в интернет для установки пакетов
Доступ VNC или консоль провайдера на случай потери SSH

Версия ОС проверяется при старте по /etc/os-release: для 22.04 / 24.04 / 26.04 скрипт продолжает без вопросов, для любой другой — предупреждает и запрашивает подтверждение.

⚠️ Перед запуском убедитесь, что у вас открыта консоль провайдера (VNC/KVM). Если SSH-сессия оборвётся в процессе, вы сможете продолжить через неё. Особенно это важно на этапах смены порта SSH и блокировки root.


Быстрый старт

# Скачать скрипт на сервер
wget -O ubuntu_hardening.sh https://raw.githubusercontent.com/borross/ubuntu_hardening/refs/heads/main/ubuntu_hardening.sh

# Проверить содержимое перед запуском
less ubuntu_hardening.sh

# Запустить
bash ubuntu_hardening.sh

Скрипт интерактивный — задаёт вопросы по порту SSH, типу ключа, имени хоста, FQDN, DNS, часовому поясу, NTP, дополнительным портам UFW и паролю ключа. Всё, что можно пропустить, пропускается нажатием Enter.

Весь вывод дублируется в лог /root/hardening_<дата>_<время>.log — при разборе проблем это первое, куда стоит смотреть. При аварийной остановке trap печатает номер строки, на которой произошёл сбой.


Переменные конфигурации

Настраиваются в начале файла скрипта:

ADMIN_USER="admin"               # имя создаваемого пользователя
SSH_PORT="20202"                 # порт по умолчанию (будет переспрошен интерактивно)
KEY_PATH="/root/admin_ssh_key"   # путь для сохранения ключевой пары

# Семейство адресов для SSH:
#   any  — IPv4 + IPv6 (безопасное значение по умолчанию)
#   inet — только IPv4 (НЕ ставьте на IPv6-only VPS — потеряете доступ!)
SSH_ADDRESS_FAMILY="any"

# Форвардинг — отключён по умолчанию, включайте осознанно
TCP_FORWARDING="no"
GATEWAY_PORTS="no"
X11_FORWARDING="no"

Интерактивные вопросы и контрольные точки

Порядок диалогов при выполнении:

Этап Вопрос По умолчанию
Предстарт Порт SSH (1024–65535) 20202
Предстарт Тип ключа: ed25519 / ecdsa-521 ed25519
Секция 1 Имя хоста, FQDN, DNS (2 адреса), часовой пояс, NTP-серверы текущие значения / пулы Ubuntu
Секция 2 Пароль приватного ключа (ssh-keygen) — обязателен
Секция 2 Пауза «скачайте приватный ключ»
Секция 3 Пароль пользователя admin (для sudo) — обязателен, в цикле
Секция 4 Контрольная точка: «вход под admin работает?» y/n n прерывает скрипт до блокировки root
Секция 6 Дополнительные TCP-порты для UFW только SSH
Секция 20 «Удалить приватный ключ с сервера?» N (оставить)

Контрольная точка в секции 4 — главный предохранитель: root блокируется только после того, как вы из нового терминала подтвердили, что ключевой вход под admin на новом порту работает.


Что делает скрипт

Нумерация секций соответствует выводу скрипта.

0. Предстартовые проверки и обновление системы

Проверки: запуск от root, версия Ubuntu, наличие /etc/os-release.

Расчёт лимитов логирования — две независимые метрики, посчитанные до начала изменений:

Квота логов  = свободное место на разделе /var/log ÷ 10   (10%, зажато 50–2048 МБ)
               делится на 5 файлов ротации auditd
Audit backlog = RAM_МБ × 8                                 (зажато 8192–65536)
RAM VPS Backlog
512 МБ – 1 ГБ 8192 (минимум)
2 ГБ 16384
4 ГБ 32768
8 ГБ+ 65536 (максимум)

Backlog — буфер в оперативной памяти ядра, поэтому считается от RAM, а не от диска. Расчёт выводится на экран при старте.

Обновление и пакеты: apt-get upgrade в неинтерактивном режиме (--force-confdef/--force-confold — не задавать вопросов о конфигах) и установка:

ufw  fail2ban  auditd  audispd-plugins  libpam-pwquality
unattended-upgrades  apt-listchanges  acl  curl  wget  gnupg2  chrony  rsyslog

rsyslog указан явно: минимальные cloud-образы Ubuntu 24.04+ могут поставляться без него, а на нём построено логирование sudo (секция 15). Гипервизор определяется через systemd-detect-virt.

1. Имя хоста, FQDN, DNS, часовой пояс, NTP

Единственный полностью интерактивный раздел; каждый пункт пропускается через Enter.

Имя хостаhostnamectl set-hostname, валидация по RFC 1123 (только a-z, 0-9, дефис, ≤63 символов). /etc/hosts обновляется автоматически — без этого sudo выдаёт раздражающее unable to resolve host.

FQDN — валидация по RFC 1123 (метки через точки, TLD ≥2 символов), запись в /etc/hosts:

127.0.1.1    server01.example.com  server01

Благодаря такой записи hostname --fqdn и hostname -s работают корректно без реального DNS.

DNS — основной и резервный IPv4-адрес, каждый валидируется (формат + октеты 0–255):

Провайдер Основной Резервный
Cloudflare 1.1.1.1 1.0.0.1
Google 8.8.8.8 8.8.4.4
Quad9 9.9.9.9 149.112.112.112
Yandex 77.88.8.8 77.88.8.1

Если активен systemd-resolved — настройка через drop-in /etc/systemd/resolved.conf.d/custom-dns.conf с DNS-over-TLS (opportunistic) и DNSSEC (allow-downgrade). Иначе — прямая запись в /etc/resolv.conf с защитой от перезаписи DHCP-клиентом (chattr +i).

Часовой поясtimedatectl set-timezone, значение проверяется по timedatectl list-timezones перед применением.

NTP через chrony. Chrony предпочтительнее systemd-timesyncd на VPS:

systemd-timesyncd chrony
Точность средняя высокая
После долгого простоя VPS медленно догоняет быстрый makestep
Нестабильная сеть хуже maxdistance отсекает плохие источники
Логирование минимальное measurements/statistics/tracking

По умолчанию — пулы 0-3.ubuntu.pool.ntp.org + time.cloudflare.com с NTS (аутентифицированный NTP). Пользовательские серверы валидируются (IPv4 или hostname по RFC 1123); невалидные пропускаются, при нуле валидных — откат на серверы по умолчанию. minsources подстраивается автоматически: 1 при единственном сервере, иначе 2. chronyc слушает только localhost. После запуска — принудительный chronyc makestep и вывод статуса синхронизации.

systemd-timesyncd отключается, чтобы не конфликтовать с chrony.

2. Генерация SSH-ключа

# ed25519 (по умолчанию, рекомендуется)
ssh-keygen -t ed25519 -f /root/admin_ssh_key -C "admin@<host>-<дата>"

# ecdsa-521 (по выбору — совместимость со старыми требованиями)
ssh-keygen -t ecdsa -b 521 -f /root/admin_ssh_key -C "admin@<host>-<дата>"

Ключ генерируется с паролемssh-keygen запрашивает его интерактивно. Ed25519 предложен по умолчанию: быстрее, компактнее, без зависимости от NIST-кривых; ECDSA-521 оставлен для сред, где он требуется явно.

Файл Права Назначение
/root/admin_ssh_key 600 приватный ключ — скачать и удалить в секции 20
/root/admin_ssh_key.pub 644 публичный ключ — остаётся на сервере

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

# из второго терминала:
scp root@<IP>:/root/admin_ssh_key ./admin_ssh_key

3. Создание пользователя admin

useradd -m -s /bin/bash admin      # с -g admin, если группа уже существует
usermod -aG sudo admin             # выполняется ВСЕГДА, и при повторном запуске

Особенности:

  • на Ubuntu группа admin существует по умолчанию (историческая, до появления группы sudo) — в этом случае она используется как primary group через -g, а не создаётся заново;
  • usermod -aG sudo вынесен из ветки создания: если пользователь существовал, но не был в sudo, раньше он туда не попадал;
  • passwd выполняется в цикле until — три ошибки ввода не прерывают скрипт (пароль нужен только для sudo, не для SSH);
  • публичный ключ добавляется в ~/.ssh/authorized_keys, дубликаты убираются через sort -u; права 700 на каталог и 600 на файл; владелец — реальная primary group пользователя (id -gn), а не имя пользователя вслепую.

4. Настройка SSH

Файл: /etc/ssh/sshd_config. Перед изменением — резервная копия sshd_config.bak.<timestamp>.

Права на каталог и файлы (CIS 5.1.1–5.1.2):

/etc/ssh                    755   (700 ломало ssh-КЛИЕНТ для обычных пользователей —
                                   им нужен доступ к /etc/ssh/ssh_config)
/etc/ssh/sshd_config        600
ssh_host_*_key              600
ssh_host_*_key.pub          644

Ключевые параметры:

Параметр Значение CIS Обоснование
Port выбранный Нестандартный порт снижает шум автосканеров
AddressFamily any Переменная SSH_ADDRESS_FAMILY; inet опасен на IPv6-only VPS
PermitRootLogin no 5.1.20 Прямой вход root запрещён
PasswordAuthentication no Только ключевая аутентификация
AuthenticationMethods publickey Явное требование ключа
PermitEmptyPasswords no 5.1.19
LoginGraceTime 30 5.1.13 0 означало бы «без лимита» — баг оригинальной конфигурации
MaxAuthTries 3 5.1.16 Максимум попыток на соединение
MaxSessions 5 5.1.17
MaxStartups 5:30:20 5.1.18 Защита от медленных атак на handshake
ClientAliveInterval / CountMax 300 / 2 5.1.7 Разрыв при 10 мин простоя
AllowUsers admin 5.1.4 Только указанный пользователь
AllowTcpForwarding no 5.1.5 Туннели обходят firewall; переменная в начале скрипта
GatewayPorts / X11Forwarding no 5.1.22
LogLevel VERBOSE 5.1.14 Fingerprint ключа в каждой записи лога
Banner /etc/ssh/my_banner 5.1.3 Юридическое предупреждение
DebianBanner / VersionAddendum no / none Скрыть версию ОС от сканеров

Криптография (CIS 5.1.6 / 5.1.12 / 5.1.15) — только современные алгоритмы:

HostKeyAlgorithms : ecdsa-sha2-nistp521, ssh-ed25519, rsa-sha2-512/256
KexAlgorithms     : curve25519-sha256, ecdh-sha2-nistp521
Ciphers           : chacha20-poly1305, aes256-gcm, aes128-gcm
MACs              : hmac-sha2-512-etm, hmac-sha2-256-etm

Порядок применения — важен, здесь были главные баги прошлых версий:

  1. Drop-in файлы: если в /etc/ssh/sshd_config.d/* есть директива Port (cloud-образы кладут туда свои конфиги), такие файлы перемещаются в /root/sshd_config.d.bak — иначе они молча переопределяли наш порт;
  2. sshd -t — валидация синтаксиса до рестарта;
  3. Отключение ssh.socket: Ubuntu 22.10+ по умолчанию запускает SSH через socket-активацию systemd, при которой порт задаёт сокет, а Port из sshd_config игнорируется. Это и было истинной причиной проблемы «порт не поменялся». Скрипт переключает на классический ssh.service;
  4. Имя службы (ssh/sshd) определяется по реально запущенным юнитам;
  5. Рестарт и подтверждение порта через ss -tlnp;
  6. Контрольная точка: скрипт останавливается и требует подтвердить вход под admin из нового терминала. Ответ n прерывает выполнение — root остаётся разблокированным, можно чинить и перезапускать.

5. Блокировка root

passwd -l root

Дополнительно su ограничивается членами группы sudo через pam_wheel.so в /etc/pam.d/su (CIS 5.2.7) — непривилегированный пользователь не сможет даже пытаться подобрать пароль root через su.

6. Брандмауэр UFW

Перед сбросом правил скрипт показывает все слушающие порты (ss -tlnp) и предлагает перечислить дополнительные TCP-порты — раньше ufw --force reset молча закрывал порты уже работающих сервисов (веб-сервер и т.п.).

ufw default deny incoming      # CIS 4.2.7
ufw default allow outgoing
ufw default deny forward
ufw limit <PORT>/tcp           # rate-limit iptables: 6 попыток за 30 сек → блок
ufw allow <extra>/tcp          # порты, указанные пользователем (с валидацией)

ufw limit вместо ufw allow для SSH — встроенная защита от брутфорса, работающая ещё до Fail2ban.

💡 VPS-специфика: Hetzner / DigitalOcean / Vultr предоставляют собственный firewall в панели — продублируйте правила и там. Это защита в глубину: UFW внутри гостя, firewall провайдера снаружи.

7. Fail2ban

Файл: /etc/fail2ban/jail.local

Параметр Значение Комментарий
backend systemd чтение journald напрямую, не зависит от файлов логов
banaction ufw баны через тот же firewall
port выбранный SSH-порт подставляется автоматически
maxretry / bantime (sshd) 3 / 24h
findtime 10m окно подсчёта попыток

Итоговая эшелонированная защита SSH: ufw limit (сеть) → Fail2ban (журнал) → MaxAuthTries/MaxStartups (сам sshd).

8. Защита cron

chown root:root /etc/crontab /etc/cron.{hourly,daily,weekly,monthly,d}/
chmod og-rwx    /etc/crontab /etc/cron.{hourly,daily,weekly,monthly,d}/

Создаются /etc/cron.allow и /etc/at.allow (600), содержащие только root; файлы cron.deny / at.deny удаляются (CIS 2.4.1). При наличии обоих файлов allow имеет приоритет, но явное удаление deny исключает неоднозначность. Обработка at — только если он установлен.

9. Защита разделов

Файл: /etc/fstab (резервная копия перед изменением). CIS 1.1.2.x.

Раздел Флаги От чего защищает
/tmp nosuid,nodev,noexec,relatime Выполнение залитых скриптов/бинарников из /tmp
/dev/shm nosuid,nodev,noexec Исполнение из разделяемой памяти
/var/tmp bind → /tmp Наследует все флаги /tmp
/home nosuid,nodev,noexec Исполнение из домашних каталогов

Детали реализации:

  • seclabel в /dev/shm убрана — опция работает только с SELinux, Ubuntu использует AppArmor;
  • применение через mount -o remount без перезагрузки; исправлен баг приоритета операторов (a || b && c), из-за которого для /var/tmp выводилось «примонтирован», даже когда mount не выполнялся;
  • если /home не отдельный раздел (типично для VPS) — создаётся systemd unit remount-home.service, который перемонтирует /home с нужными флагами при каждой загрузке;
  • совместимость noexec /tmp с apt: часть maintainer-скриптов dpkg выполняется из временного каталога. Скрипт переносит его в /var/lib/apt/tmp (/etc/apt/apt.conf.d/99tempdir) — без этого ночные unattended-upgrades могли падать с Permission denied.

10. Параметры ядра (sysctl)

Файл: /etc/sysctl.d/99-hardening.conf, применение через sysctl --system.

Сетевая безопасность (CIS 3.3.x):

Параметр Значение Защита от
rp_filter (all/default) 1 IP-спуфинга
ip_forward, ipv6 forwarding 0 использования как роутера
tcp_syncookies + syn_retries=2, synack_retries=2, max_syn_backlog=4096 1 SYN-flood
accept_redirects, secure_redirects (v4+v6) 0 ICMP MITM
send_redirects 0 перенаправления трафика
icmp_echo_ignore_broadcasts, icmp_ignore_bogus_error_responses 1 Smurf-атак
log_martians 1 (логирование подозрительных источников)
accept_ra (v6) 0 Router Advertisement атак
tcp_rfc1337 1 TIME-WAIT assassination

Безопасность ядра:

Параметр Значение CIS Описание
kernel.yama.ptrace_scope 1 1.5.2 ptrace только от родителя; не 2 — иначе на VPS нельзя диагностировать через strace/gdb
kernel.dmesg_restrict 1 dmesg недоступен обычным пользователям
kernel.kptr_restrict 2 адреса ядра скрыты из /proc и /sys
kernel.sysrq 0 Magic SysRq отключён (есть консоль провайдера)
kernel.randomize_va_space 2 1.5.1 ASLR максимальный
fs.suid_dumpable 0 1.5.3 core dump setuid-процессов запрещён
kernel.core_pattern |/bin/false третий уровень блокировки dump (см. секцию 12)
fs.protected_{hardlinks,symlinks} 1 классические /tmp-атаки через ссылки
fs.protected_{fifos,regular} 2 атаки через FIFO/файлы в sticky-каталогах

User namespaces — подбирается под ядро автоматически:

  • kernel.unprivileged_userns_clone = 0 — пишется только если параметр существует (/proc/sys/kernel/...). Это Debian/Ubuntu-патч, отсутствующий на новых ядрах — раньше sysctl молча выдавал ошибку;
  • kernel.apparmor_restrict_unprivileged_userns = 1 — механизм ядер Ubuntu 24.04+/26.04, пишется при наличии.

⚠️ Ограничение userns ломает rootless Docker/Podman. Если используете контейнеры без root — закомментируйте соответствующие строки в /etc/sysctl.d/99-hardening.conf и выполните sysctl --system.

11. Blacklist модулей ядра

Файл: /etc/modprobe.d/hardening-blacklist.conf. Каждый модуль блокируется парой blacklist + install <mod> /bin/false — вторая директива не даёт подгрузить модуль даже явным modprobe.

Файловые системы с историей уязвимостей (CIS 1.1.1.x): cramfs, freevxfs, jffs2, hfs, hfsplus, udf. Вектор атаки — автоматическое монтирование специально созданных образов через udisks2/automount.

Сетевые протоколы с историей CVE (CIS 3.2.1–3.2.4): dccp, sctp, rds, tipc. На VPS практически никогда не нужны.

VPS-специфика: USB-storage и Firewire не блокируются — на виртуальной машине этих устройств нет, а агрессивный blacklist может задеть virtio-драйверы некоторых провайдеров. ext*/vfat/overlay/squashfs не трогаются — это сломало бы корневую ФС, EFI, snap и контейнеры.

После записи — update-initramfs -u -k all; полная сила blacklist — после перезагрузки.

12. Отключение core dump

Тройная блокировка (CIS 1.5.3) — три механизма покрывают разные классы процессов:

Уровень Файл Покрывает
hard core 0 /etc/security/limits.conf PAM-сессии (интерактивные входы)
Storage=none, ProcessSizeMax=0 /etc/systemd/coredump.conf.d/disable.conf процессы под systemd
kernel.core_pattern = |/bin/false sysctl (секция 10) всё остальное

Core dump процесса может содержать пароли, ключи и другие секреты из памяти — на харденированном сервере они не нужны.

13. Auditd

Защита от повторного запуска: если правила уже immutable (auditctl -senabled 2), секция пропускается с предупреждением — изменить правила можно только после reboot, а попытка рестарта auditd уронила бы скрипт.

/etc/audit/auditd.conf — лимиты рассчитаны в секции 0:

max_log_file        = <квота ÷ 5> МБ        num_logs = 5
max_log_file_action = ROTATE                 log_format = ENRICHED
space_left_action   = SYSLOG                 admin_space_left_action = ROTATE
disk_full_action    = ROTATE  ← не halt/single: VPS без консоли ушёл бы в офлайн
flush = INCREMENTAL_ASYNC     freq = 50

Устаревшие dispatcher = /sbin/audispd и disp_qos отсутствуют — они удалены в auditd 3.x (Ubuntu 22.04+), с ними демон мог не стартовать.

Правила /etc/audit/rules.d/99-hardening.rules:

Категория Что отслеживается Ключ CIS
Идентификация /etc/passwd, shadow, group, gshadow identity 6.2.3.8
Привилегии /etc/sudoers, /etc/sudoers.d/ sudoers 6.2.3.1
SSH sshd_config, ~admin/.ssh/ sshd_config, ssh_keys
Cron все cron-каталоги + /var/spool/cron/ cron
Права файлов chmod/chown и варианты, b64 + b32, фильтр auid>=1000 file_perm 6.2.3.9
Повышение привилегий setuid/setgid (b64+b32); execve при euid=0 от реальных пользователей priv_esc, priv_exec 6.2.3.6
Модули ядра insmod/rmmod/modprobe + syscalls *_module kernel_modules 6.2.3.19
Сеть только bind реальных пользователей net_bind
Загрузчик /boot/ boot

Принципиальные решения:

  • правила socket/connect намеренно отсутствуют — аудит каждого сетевого вызова генерировал тысячи событий в секунду и был первопричиной ошибки audit: backlog limit exceeded; увеличение буфера лечило симптом. Новый слушающий сокет (bind) — редкое и действительно значимое событие, он оставлен;
  • дублирование b64 + b32: без правил для arch=b32 аудит тривиально обходился 32-битными syscalls;
  • фильтр -F auid>=1000 -F auid!=unset отсекает шум системных демонов, оставляя действия реальных пользователей;
  • -b <backlog> — рассчитан из RAM; --backlog_wait_time убран (значение 1 из старой версии уменьшало ожидание и увеличивало потери — комментарий был неверным);
  • -f 1 — при сбое аудита писать в syslog, не паника ядра (-f 2): на VPS без физической консоли паника означает недоступный сервер;
  • -e 2 в конце — правила неизменяемы до перезагрузки: злоумышленник не отключит аудит без reboot, который сам оставит след.

Ротация: только средствами auditd. Отдельный /etc/logrotate.d/audit не создаётся и удаляется, если был — auditd держит файл открытым и ротирует сам; параллельный logrotate с рестартом демона давал двойную ротацию и разрывы аудита.

Просмотр событий:

ausearch -k identity   | tail -20   # изменения passwd/shadow
ausearch -k sudoers    | tail -20   # изменения sudoers
ausearch -k ssh_keys   | tail -20   # изменения SSH-ключей
ausearch -k priv_exec  | tail -20   # команды от root реальными пользователями
ausearch -k net_bind   | tail -20   # новые слушающие сокеты
aureport --summary                  # сводка
auditctl -s                         # backlog, lost, enabled

14. AppArmor

apparmor/apparmor-utils доустанавливаются при отсутствии (проверка через dpkg -s). Все профили из /etc/apparmor.d/ переводятся в режим enforce (CIS 1.3.1.4): в отличие от complain, нарушения не только логируются, но и блокируются. Проверка: aa-status.

15. Настройка sudo

Файл: /etc/sudoers.d/99-hardening (права 440, visudo -c до применения):

Настройка Значение Назначение
timestamp_timeout=5 5 минут сессия sudo истекает быстро
!visiblepw пароль не отображается при вводе
secure_path фиксированный защита от подмены системных утилит через PATH

Логирование — через rsyslog (/etc/rsyslog.d/50-sudo.conf): все события sudo дублируются в /var/log/sudo.log (права 600). Формат записи:

Jul 14 03:12:44 server sudo: admin : TTY=pts/0 ; PWD=/home/admin ; USER=root ; COMMAND=/bin/bash

Опции log_output, logfile, requiretty сознательно не используются: первые две требуют I/O-плагин sudo, не входящий в пакет Ubuntu, третья удалена из sudo 1.9.x — с ними sudo -i падал с unknown setting.

16. Политика паролей

Файл: /etc/security/pwquality.conf (CIS 5.3.3.2.x):

Параметр Значение Смысл
minlen 16 минимальная длина
dcredit/ucredit/lcredit/ocredit -1 минимум 1 цифра / заглавная / строчная / спецсимвол
maxrepeat 3 не более 3 одинаковых символов подряд
dictcheck 1 проверка по словарю cracklib
gecoscheck 1 запрет слов из GECOS (имени пользователя)

SSH-вход по паролю отключён — политика защищает пароль для sudo и любые будущие локальные УЗ.

17. Права на системные файлы

chmod 600 /etc/shadow /etc/gshadow      # CIS 7.1.3–7.1.4
chmod 644 /etc/passwd /etc/group        # CIS 7.1.1–7.1.2
chmod 700 /boot

SUID снимается с редко используемых утилит chfn, chsh, newgrp — каждая проверяется на существование и наличие бита.

Изменение v2: симлинк ~root/.bash_history → /dev/null убран. Отключение истории — антифорензика: при инциденте она помогает атакующему скрыть следы больше, чем защищает владельца. Команды с правами root аудирует auditd (ключ priv_exec) — этот журнал, в отличие от истории shell, защищён immutable-правилами.

18. Удаление телеметрии

Пакет Назначение
ubuntu-report отчёты об использовании системы
popularity-contest статистика установленных пакетов
apport сбор и отправка crash-репортов
whoopsie отправщик crash-репортов в Canonical

Проверка через dpkg -s (не dpkg -l, который возвращал успех даже для удалённых пакетов в статусе rc), затем purge + autoremove + autoclean. Сервисы apport/whoopsie отключаются на случай остатков; при наличии snap отключаются его метрики.

19. Автоматические обновления

/etc/apt/apt.conf.d/50unattended-upgrades:

Разрешённые источники — -security, ESM Apps/Infra (Ubuntu Pro), -updates (многие исправления безопасности приходят именно через -updates, а не -security).

Blacklist — пакеты критичных сервисов, обновление которых должно быть ручным и контролируемым:

docker*  kube*  postgresql*  mysql*  mariadb*  nginx*  apache2*  redis*  mongodb*
Параметр Значение Причина
AutoFixInterruptedDpkg true исправлять прерванные установки
MinimalSteps true атомарные шаги — безопасное прерывание
Remove-Unused-Dependencies true без orphan-пакетов
Automatic-Reboot false неожиданный reboot продакшн-VPS недопустим
Mail / MailOnlyOnError root / true письмо только при ошибках, а не после каждого прогона

Расписание — systemd drop-in (не перезапись unit-файла, чтобы обновления пакетов не затирали настройку):

/etc/systemd/system/apt-daily.timer.d/override.conf           →  03:00 ± 10 мин
/etc/systemd/system/apt-daily-upgrade.timer.d/override.conf   →  03:30 ± 10 мин

Перед новым OnCalendar дефолтные значения сбрасываются пустыми строками — иначе systemd объединяет расписания и таймер срабатывает по обоим. RandomizedDelaySec=10m — чтобы VPS одного провайдера не стучались в репозитории одновременно. Проверка: systemctl list-timers apt-daily*.

20. Удаление приватного ключа

Финальный шаг — предложение надёжно затереть приватный ключ:

shred -vuz /root/admin_ssh_key    # многократная перезапись + удаление

Только после явного подтверждения y. Если ключ ещё не скачан — ответ N, ключ остаётся, скрипт напоминает команду для ручного удаления. Публичный ключ (.pub) остаётся на сервере в любом случае.


Сознательные отклонения от CIS

Эти решения оправданы спецификой VPS/VDS — это не пробелы, а осознанный выбор:

Отклонение CIS Причина
disk_full_action = ROTATE (не halt/single) 6.2.2.3 halt = недоступный VPS без физической консоли
auditd -f 1 (syslog), не -f 2 (паника) паника ядра при сбое аудита уронит VPS в офлайн
Нет аудита socket/connect тысячи событий/сек, переполнение backlog; bind покрывает значимые случаи
USB/Firewire не в blacklist 1.1.1 на виртуалке устройств нет; риск задеть virtio-драйверы
chrony вместо systemd-timesyncd 2.3 точнее и быстрее восстанавливается на VPS
ptrace_scope = 1, не 2 1.5.2 сохранить strace/gdb для диагностики без физического доступа
kernel.sysrq = 0 SysRq не нужен (есть консоль провайдера) и является вектором атаки
Automatic-Reboot = false неконтролируемый reboot продакшн-VPS недопустим
Пароль GRUB не ставится 1.4.1 без IP-KVM усложняет восстановление через консоль провайдера
userns-ограничения легко отключаемы не ломать rootless-контейнеры без ведома администратора
История root не отключается отключение — антифорензика; аудит ведёт auditd

Подключение после настройки

# Прямое подключение (пароль ключа при каждом входе)
ssh -p <PORT> -i ./admin_ssh_key admin@<IP>

# Через ssh-agent (пароль ключа один раз за сессию)
eval $(ssh-agent)
ssh-add ./admin_ssh_key
ssh -p <PORT> admin@<IP>

~/.ssh/config на клиентской машине:

Host myserver
    HostName      <IP>
    Port          <PORT>
    User          admin
    IdentityFile  ~/.ssh/admin_ssh_key
    AddKeysToAgent yes

После этого достаточно: ssh myserver


Диагностика и мониторинг

# Лог выполнения самого харденинга
ls -la /root/hardening_*.log

# SSH
journalctl -u ssh -u sshd -f              # живой лог
ss -tlnp | grep <PORT>                    # слушает ли порт
systemctl status ssh.socket               # должен быть disabled/inactive!
grep -r "^Port" /etc/ssh/sshd_config /etc/ssh/sshd_config.d/ 2>/dev/null

# Fail2ban
fail2ban-client status sshd               # забаненные IP
fail2ban-client set sshd unbanip <IP>     # разбанить вручную

# UFW
ufw status verbose

# Auditd
auditctl -s                               # backlog, lost (должно быть 0), enabled
ausearch -k identity   | tail -20
ausearch -k ssh_keys   | tail -20
ausearch -k priv_exec  | tail -20
ausearch -k net_bind   | tail -20
aureport --summary

# AppArmor
aa-status

# NTP / Chrony
chronyc tracking                          # источник и погрешность
chronyc sources -v
timedatectl show

# Разделы
findmnt /tmp /var/tmp /dev/shm /home      # проверка флагов noexec/nosuid/nodev

# sudo
tail -20 /var/log/sudo.log

# Автообновления
systemctl list-timers apt-daily*
cat /var/log/unattended-upgrades/*.log

Что требует перезагрузки

  • Blacklist модулей ядра — DCCP, SCTP, RDS, TIPC и редкие ФС
  • Ограничения user namespaces — на части ядер применяются только после reboot
  • /var/tmp bind-mount — если не применился в текущей сессии
  • Любые изменения правил auditd — при действующем immutable-режиме (-e 2)
reboot
# затем проверка доступности:
ssh -p <PORT> -i ./admin_ssh_key admin@<IP>

Восстановление при потере доступа

Все изменяемые конфиги бэкапятся с меткой времени. Через консоль провайдера (VNC/KVM):

# SSH: откат конфига
cp /etc/ssh/sshd_config.bak.<timestamp> /etc/ssh/sshd_config
systemctl restart ssh

# SSH: вернуть перемещённые drop-in (если они были нужны)
cp /root/sshd_config.d.bak/* /etc/ssh/sshd_config.d/ 2>/dev/null

# Разблокировать root (для входа через консоль провайдера)
passwd -u root && passwd root

# fstab: откат (если система не грузится из-за раздела — режим recovery)
cp /etc/fstab.bak.<timestamp> /etc/fstab

# UFW: экстренно выключить
ufw disable

# Fail2ban: снять бан со своего IP
fail2ban-client set sshd unbanip <ваш_IP>

# Разбор причин: лог харденинга
less /root/hardening_*.log

Если SSH не поднимается после правок — смотреть journalctl -xe -u ssh -u sshd и проверить, не включился ли обратно ssh.socket (systemctl status ssh.socket).


Повторный запуск скрипта

Скрипт можно запускать повторно, с оговорками:

  • auditd: пока правила immutable (enabled 2), секция 13 пропускается с предупреждением; файлы в /etc/audit/ при этом уже содержат нужную конфигурацию и применятся после reboot;
  • root заблокирован — запускайте через sudo -i из-под admin;
  • дубликаты не плодятся: authorized_keys дедуплицируется через sort -u, записи 127.0.1.1 в /etc/hosts заменяются, fstab-строки перезаписываются, usermod -aG sudo идемпотентен;
  • ufw --force reset снова сбросит правила — дополнительные порты придётся указать заново в диалоге секции 6.

Ручное включение форвардинга

Если сервер используется как VPN-сервер или прокси:

# Вариант 1: в переменных в начале скрипта перед запуском
TCP_FORWARDING="yes"
GATEWAY_PORTS="yes"     # только если нужен проброс портов наружу

# Вариант 2: после запуска — отредактировать sshd_config
sed -i 's/^AllowTcpForwarding no/AllowTcpForwarding yes/' /etc/ssh/sshd_config
systemctl restart ssh

# Плюс разрешить форвардинг в UFW и sysctl:
ufw route allow in on eth0
sysctl -w net.ipv4.ip_forward=1
# и закрепить в /etc/sysctl.d/99-hardening.conf (net.ipv4.ip_forward = 1)

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages