Укрепление Debian-сервера от установки до продакшена для веб-студии ВебПроект

Задача клиента

Веб-студия «ВебПроект» арендовала 8 VPS у разных хостеров для клиентских проектов. Серверы настраивались «по памяти» — каждый администратор делал по-своему: на одном root-доступ по паролю, на другом SSH на стандартном порту без ограничений, на третьем swap не настроен вовсе. После того как два сервера попали в ботнет через брутфорс SSH, руководство обратилось к нам в itfresh.ru с запросом: создать унифицированный чеклист укрепления и привести все серверы к единому стандарту безопасности.

Целевая ОС — Debian 12 (Bookworm) на VPS с 2 vCPU, 4 GB RAM и 80 GB NVMe. Мы задокументировали каждый шаг, чтобы студия могла воспроизвести процесс на новых серверах самостоятельно.

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

Первое правило: устанавливаем только то, что нужно. Стандартный образ хостера содержит лишние пакеты — от Exim до Bluetooth-стека. Начинаем с обновления и очистки:

apt update && apt full-upgrade -y
apt autoremove --purge -y
apt install -y sudo curl wget gnupg2 apt-transport-https \
  ca-certificates software-properties-common

Создаём рабочего пользователя и запрещаем прямой вход под root:

# Создаём пользователя с sudo-правами
adduser deploy
usermod -aG sudo deploy

# Проверяем, что sudo работает
su - deploy -c 'sudo whoami'   # должно вернуть root

Настраиваем локаль и часовой пояс — банально, но пропуск этого шага приводит к нечитаемым логам и неверным timestamp-ам в мониторинге:

timedatectl set-timezone Europe/Moscow
locale-gen ru_RU.UTF-8 en_US.UTF-8
update-locale LANG=en_US.UTF-8

SSH hardening: закрываем главную дверь

SSH — точка входа номер один для атак. На серверах «ВебПроект» мы обнаружили до 15 000 попыток брутфорса в сутки на стандартном 22-м порту. Конфигурация /etc/ssh/sshd_config.d/hardening.conf:

# Меняем порт (не панацея, но отсекает 95% ботов)
Port 2222

# Только протокол 2
Protocol 2

# Запрещаем root-логин
PermitRootLogin no

# Только ключевая аутентификация
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey

# Ограничиваем пользователей
AllowUsers deploy

# Таймауты и лимиты
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 3
ClientAliveInterval 300
ClientAliveCountMax 2

# Отключаем ненужное
X11Forwarding no
AllowTcpForwarding no
PermitEmptyPasswords no
UseDNS no

Перед перезапуском sshd обязательно проверяем конфигурацию и держим запасную сессию открытой:

# Проверка синтаксиса
sshd -t

# Перезапуск (только после успешной проверки!)
systemctl restart sshd

# В отдельном терминале проверяем подключение по ключу
ssh -p 2222 deploy@server-ip

На двух серверах из восьми мы обнаружили авторизованные ключи неизвестного происхождения в /root/.ssh/authorized_keys — следы предыдущей компрометации. Ключи были немедленно удалены, а серверы добавлены в приоритет для полной переустановки.

Файрвол: UFW и nftables

Для простоты управления мы выбрали UFW как фронтенд к nftables (в Debian 12 nftables — бэкенд по умолчанию). Политика: запрещено всё, что не разрешено явно.

# Политики по умолчанию
ufw default deny incoming
ufw default allow outgoing

# Разрешаем SSH на нестандартном порту
ufw allow 2222/tcp comment 'SSH'

# Веб-сервер
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'

# Rate limiting на SSH (автоматически блокирует при 6+ подключениях за 30 сек)
ufw limit 2222/tcp

# Включаем
ufw enable
ufw status verbose

Для серверов с нестандартными требованиями (например, Node.js на порту 3000, доступ только из офиса) добавляли точечные правила:

# Доступ к приложению только из IP офиса
ufw allow from 203.0.113.50 to any port 3000 proto tcp comment 'Office-NodeJS'

# Блокируем весь трафик из подозрительной подсети
ufw deny from 45.148.10.0/24 comment 'Known-scanner'

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

# /etc/nftables.d/rate-limit.conf
table inet filter {
  chain input {
    tcp dport 80 ct state new limit rate 50/second accept
    tcp dport 80 ct state new drop
  }
}

Swap, sysctl и тюнинг ядра

На трёх серверах swap отсутствовал полностью, и при пиковой нагрузке OOM killer убивал процессы PHP-FPM. Создаём swap-файл (не раздел — для гибкости):

# Создаём swap 4 GB
fallocate -l 4G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

# Добавляем в fstab
echo '/swapfile none swap sw 0 0' >> /etc/fstab

# Проверяем
swapon --show

Оптимизация параметров ядра через /etc/sysctl.d/99-hardening.conf:

# === Безопасность ===
# Защита от SYN-flood
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 4096

# Запрет ICMP-редиректов (MITM-защита)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv6.conf.all.accept_redirects = 0

# Защита от IP-спуфинга
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Игнорируем broadcast ICMP (Smurf-атаки)
net.ipv4.icmp_echo_ignore_broadcasts = 1

# ASLR
kernel.randomize_va_space = 2

# === Производительность ===
# Swappiness — используем swap только при реальной нехватке RAM
vm.swappiness = 10
vm.vfs_cache_pressure = 50

# Расширяем диапазон эфемерных портов
net.ipv4.ip_local_port_range = 1024 65535

# TCP буферы для высоконагруженных серверов
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# Очередь сетевых пакетов
net.core.netdev_max_backlog = 5000
net.core.somaxconn = 4096

# Быстрая утилизация TIME_WAIT сокетов
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
# Применяем без перезагрузки
sysctl --system

Автоматические обновления безопасности и fail2ban

Главная причина компрометации серверов «ВебПроект» — непатченные CVE. Администраторы обновляли серверы вручную раз в 2-3 месяца. Настраиваем автоматические обновления безопасности:

apt install -y unattended-upgrades apt-listchanges

# Включаем автоматику
dpkg-reconfigure -plow unattended-upgrades

Конфигурация /etc/apt/apt.conf.d/50unattended-upgrades:

Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
};

// Автоматически перезагружаться при обновлении ядра (в 4 утра)
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

// Отправлять отчёт на почту
Unattended-Upgrade::Mail "admin@webproekt.ru";
Unattended-Upgrade::MailReport "on-change";

// Удалять неиспользуемые зависимости
Unattended-Upgrade::Remove-Unused-Dependencies "true";

Fail2ban — второй рубеж обороны. Устанавливаем и настраиваем для SSH и Nginx:

apt install -y fail2ban

# /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
banaction = nftables-multiport

[sshd]
enabled = true
port = 2222
maxretry = 3
bantime = 86400

[nginx-botsearch]
enabled = true
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 5

[nginx-badbots]
enabled = true
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 604800
systemctl enable --now fail2ban
fail2ban-client status

За первую неделю после внедрения fail2ban заблокировал 2 400 уникальных IP-адресов на SSH и 850 — на веб-атаках. Количество попыток брутфорса сократилось с 15 000 до 200 в сутки.

Logrotate, certbot и systemd hardening

На двух серверах диск был заполнен на 94% — access.log Nginx весил 18 GB за полгода. Настраиваем ротацию:

# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
    endscript
}

SSL-сертификаты через Let's Encrypt с автопродлением:

apt install -y certbot python3-certbot-nginx

# Выпускаем сертификат
certbot --nginx -d example.com -d www.example.com \
  --non-interactive --agree-tos -m admin@webproekt.ru

# Проверяем автопродление
certbot renew --dry-run

# Cron для certbot уже создан автоматически, проверяем:
systemctl list-timers | grep certbot

Финальный штрих — hardening systemd-юнитов для веб-приложений. Ограничиваем каждый сервис по принципу наименьших привилегий:

# Пример: /etc/systemd/system/webapp.service
[Unit]
Description=WebApp Service
After=network.target

[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5

# Hardening
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
NoNewPrivileges=true
ReadWritePaths=/var/www/app/uploads /var/log/webapp
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictSUIDSGID=true
RemoveIPC=true
RestrictNamespaces=true
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
MemoryDenyWriteExecute=true

[Install]
WantedBy=multi-user.target
# Проверяем уровень безопасности юнита
systemd-analyze security webapp.service
# Цель — получить рейтинг ниже 4.0 (MEDIUM)

Мониторинг и результаты

Устанавливаем node_exporter для сбора метрик Prometheus:

# Скачиваем и устанавливаем
wget https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz
tar xzf node_exporter-1.7.0.linux-amd64.tar.gz
cp node_exporter-1.7.0.linux-amd64/node_exporter /usr/local/bin/

# Systemd-юнит с hardening
cat > /etc/systemd/system/node_exporter.service << 'EOF'
[Unit]
Description=Prometheus Node Exporter
After=network.target

[Service]
Type=simple
User=node_exporter
Group=node_exporter
ExecStart=/usr/local/bin/node_exporter \
  --web.listen-address=":9100" \
  --collector.systemd \
  --collector.processes
Restart=on-failure
ProtectSystem=strict
ProtectHome=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
EOF

useradd -rs /bin/false node_exporter
systemctl daemon-reload
systemctl enable --now node_exporter

Метрики собираем в центральный Prometheus и визуализируем в Grafana (дашборд Node Exporter Full — ID 1860).

Результаты проекта:

  • Все 8 серверов приведены к единому стандарту за 3 рабочих дня
  • Количество инцидентов безопасности за 3 месяца: 0 (было 2 компрометации за предыдущий квартал)
  • Попытки брутфорса SSH снизились с 15 000 до 200 в сутки благодаря смене порта и fail2ban
  • Среднее время реакции на уязвимость: 4 часа (unattended-upgrades) вместо 60-90 дней (ручные обновления)
  • Утилизация дисков: с 94% до 45% после настройки logrotate
  • Все сервисы получили рейтинг безопасности systemd ниже 4.0

Специалисты itfresh.ru подготовили для студии Ansible-плейбук, который автоматизирует весь процесс для будущих серверов — один запуск ansible-playbook harden.yml приводит свежий Debian к production-стандарту за 12 минут.

Часто задаваемые вопросы

Смена порта — не защита, а фильтрация шума. Она отсекает до 95% автоматических сканеров, но целенаправленная атака найдёт любой порт за минуты через nmap. Реальная защита — ключевая аутентификация, fail2ban и ограничение AllowUsers.
UFW — это удобная обёртка для типовых задач: открыть порт, ограничить rate, добавить whitelist. Для 90% серверов его достаточно. Nftables напрямую нужен для сложных сценариев: кастомные цепочки, маркировка трафика, NAT с условиями.
Для VPS с веб-приложениями — да, если перезагрузка происходит в низконагруженное время (4:00) и настроен мониторинг доступности. Для серверов с критичными stateful-процессами (базы данных, очереди) лучше перезагружать вручную в рамках maintenance window.
Для серверов с достаточным объёмом RAM (4+ GB) оптимально vm.swappiness=10 — ядро будет использовать swap только при реальной нехватке памяти. Значение 0 опасно: OOM killer начнёт убивать процессы, когда RAM закончится, без попытки использовать swap.
Три способа: systemd-analyze security для оценки юнитов, Lynis (apt install lynis && lynis audit system) для комплексного аудита, и внешний скан nmap/OpenVAS для проверки открытых портов и известных уязвимостей. После нашего чеклиста Lynis показывает рейтинг 80+ баллов.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#debian#hardening#ssh#ufw#nftables#fail2ban#sysctl#unattended-upgrades
Комментарии 0

Оставить комментарий

загрузка...