Hardening ядра Linux через sysctl в 2026: защита от exploit'ов, сетевых атак и privilege escalation

Один файл /etc/sysctl.d/99-hardening.conf ломает большинство публичных kernel-эксплойтов, SYN flood и IP spoofing. Готовая конфигурация, аудит через Lynis и совместимость с контейнерами.

Hardening ядра Linux: sysctl 2026

Обновлено: 20 июня 2026 г.

Hardening ядра Linux через sysctl — это настройка runtime-параметров через интерфейс /proc/sys и файлы /etc/sysctl.d/*.conf, которая ломает большинство публичных эксплойтов локального privilege escalation и закрывает классические сетевые векторы вроде SYN flood, IP spoofing и source routing. На реальных пентестах я регулярно вижу серверы с дефолтным ядром: kernel.kptr_restrict=0, kernel.unprivileged_bpf_disabled=0, ptrace без ограничений. Один правильно собранный sysctl-файл из ~30 строк убивает большую часть моего арсенала за пять минут. Этот гайд показывает атаки и сразу же тот контроль, который их останавливает. Я уже лет десять делаю Linux hardening на банковских и SaaS-инфраструктурах, и опыт всё тот же: правильный sysctl съедает половину работы атакующего ещё до того, как поднимется SELinux.

  • Дефолтные значения sysctl в большинстве дистрибутивов 2026 года (Ubuntu 24.04, RHEL 9, Debian 12) оставляют ядро уязвимым к утечкам адресов, kernel pointer leaks и сетевым атакам.
  • Параметр kernel.kptr_restrict=2 прячет адреса символов ядра от непривилегированных процессов и ломает 90% эксплойт-цепочек, требующих известного смещения.
  • Связка kernel.yama.ptrace_scope=2 и kernel.unprivileged_userns_clone=0 блокирует процесс-инъекции и большинство контейнерных побегов через user namespaces.
  • Сетевые параметры net.ipv4.tcp_syncookies=1, rp_filter=1 и отключение source routing закрывают SYN flood, IP spoofing и redirect-атаки на L3.
  • Изменения вносятся через /etc/sysctl.d/99-hardening.conf и применяются systemd-sysctl при загрузке (никаких ручных echo в /proc).
  • Базой для аудита служат рекомендации Kernel Self Protection Project (KSPP) и CIS Benchmark for Linux, проверяемые Lynis.

Что такое sysctl и почему дефолты опасны

Интерфейс sysctl — это пользовательская точка доступа к runtime-параметрам ядра Linux, экспонируемая через виртуальную файловую систему /proc/sys. Каждый файл в этом дереве это отдельный параметр: /proc/sys/kernel/kptr_restrict, /proc/sys/net/ipv4/tcp_syncookies и так далее. Запись в файл меняет поведение ядра без перезагрузки. То же самое делает команда sysctl -w kernel.kptr_restrict=2, а файлы в /etc/sysctl.d/ загружает юнит systemd-sysctl.service при старте системы.

Проблема в том, что мейнтейнеры дистрибутивов выбирают дефолты, которые не ломают совместимость, а не те, что выдерживают атакующего. На свежей Ubuntu 24.04 LTS я вижу kernel.kptr_restrict=1 (адреса видны процессам с CAP_SYSLOG), kernel.unprivileged_bpf_disabled=2 (наконец-то ужесточили в 2023), но kernel.unprivileged_userns_clone=1, kernel.dmesg_restrict=1 только для не-root. На Debian 12 и многих дешёвых VPS-образах ситуация ещё хуже. Это значит, что после первого RCE атакующий из обычного web-юзера читает kallsyms, запускает локальный эксплойт под актуальное ядро и за минуты получает root.

Sysctl-hardening — самый дешёвый контроль в матрице защиты Linux: ноль зависимостей, ноль накладных расходов на CPU, мгновенный эффект. Поэтому я ставлю его раньше любых SELinux-политик и любого мониторинга. Подробнее про последующие слои читайте в моём руководстве по защите сервисов systemd и песочницам.

Защита ядра: kptr_restrict, dmesg_restrict, ptrace_scope

Большинство современных kernel-эксплойтов начинается с утечки адресов: символ commit_creds, адрес init_cred, смещение какой-нибудь функции. Без этих чисел ROP-цепочка не строится. Все три источника утечки контролируются sysctl.

kernel.kptr_restrict: прячем kallsyms

# Проверяем дефолт
$ cat /proc/kallsyms | head -3
ffffffffa1000000 T startup_64
ffffffffa1000030 T secondary_startup_64
ffffffffa1000110 T verify_cpu

# Применяем kptr_restrict=2, прячем для всех, включая CAP_SYSLOG
$ sudo sysctl -w kernel.kptr_restrict=2
$ cat /proc/kallsyms | head -3
0000000000000000 T startup_64
0000000000000000 T secondary_startup_64
0000000000000000 T verify_cpu

Значение 2 заменяет указатели нулями для всех процессов. Значение 1 (дефолт во многих дистрибутивах) разрешает чтение процессам с CAP_SYSLOG. Этого хватает любому пользователю в группе adm на Ubuntu. На атакующего это влияет так: без kallsyms он либо тащит собственный эксплойт с захардкоженными смещениями (которые редко совпадают), либо вынужден искать info-leak через другие каналы. На моих недавних энгейджментах это удлиняет LPE-цепочку с минут до часов.

kernel.dmesg_restrict: закрываем kernel log

Параметр kernel.dmesg_restrict=1 требует CAP_SYS_ADMIN для чтения dmesg. Это важно потому, что kernel log сливает адреса при паниках, oops, BPF JIT-ошибках и сообщениях драйверов. Я регулярно вижу адреса стека ядра в dmesg на серверах с включённым kernel.printk_devkmsg=on.

kernel.yama.ptrace_scope: запрет инъекций

Модуль безопасности Yama добавляет ограничения на PTRACE_ATTACH. По умолчанию любой ваш процесс может прицепиться к другому вашему процессу. Этим пользуются gdb, strace и реверс-инженерные инструменты. И этим же пользуются ворованные SSH-агенты, инжекторы шелл-кода в sshd-форки и постэксплойт-фреймворки вроде linikatz.

# 0 = классический режим (опасно), 1 = только потомки, 2 = только root, 3 = запрещено навсегда
kernel.yama.ptrace_scope = 2

Защита от privilege escalation через userns и BPF

Две подсистемы ядра, которые в 2024–2026 годах принесли больше всего CVE с локальным повышением привилегий, это user namespaces и eBPF. Обе задумывались для добра: контейнеры без root, расширяемое observability. Обе раз за разом ломают модель привилегий ядра.

kernel.unprivileged_userns_clone

User namespaces позволяют непривилегированному процессу получить «root» внутри отдельного namespace. Этот root ограничен, но он даёт доступ к огромной поверхности атаки: новые сетевые namespaces, mount-операции, capability-эмуляция. CVE-2022-0185 (heap overflow в fs_context), CVE-2023-32233 (Netfilter use-after-free), CVE-2024-1086 (nf_tables UAF). Все они требовали CLONE_NEWUSER для запуска от обычного юзера.

kernel.unprivileged_userns_clone = 0

На Debian/Ubuntu это полностью запрещает непривилегированный clone(CLONE_NEWUSER). Если вы запускаете rootless-контейнеры (Podman, rootless Docker, Bubblewrap, Flatpak), оставьте значение 1 и компенсируйте hardening seccomp-фильтрами и LSM. Если на сервере только nginx и postgres, выключайте без раздумий.

Честно говоря, на VPS под typical web-стек я выключаю userns в первый же день. Ни разу не пожалел об этом решении.

kernel.unprivileged_bpf_disabled

eBPF это самая активная атакоповерхность ядра последних лет. Сотни CVE по верификатору, JIT-багам, helper-функциям. Если на хосте не используются непривилегированные BPF-программы (а в продакшене они почти никогда не нужны), запрещайте:

kernel.unprivileged_bpf_disabled = 2
net.core.bpf_jit_harden = 2

Значение 2 для первого параметра запрещает любые bpf()-сисколы без CAP_BPF. Это сразу обрубает CVE класса «непривилегированный BPF → kernel mem corruption». Параметр bpf_jit_harden=2 включает константные blinding-преобразования в JIT, ломая JIT spraying.

kernel.kexec_load_disabled и kernel.modules_disabled

После того как root получен, классический способ закрепиться это загрузить злое ядро через kexec или вставить руткит через insmod. Оба механизма выключаются sysctl необратимо:

kernel.kexec_load_disabled = 1
# kernel.modules_disabled = 1   # включайте после загрузки всех нужных модулей

modules_disabled=1 блокирует загрузку любых модулей до перезагрузки. Атакующий с root не сможет внедрить LKM-руткит. Это критично на bare-metal боксах с долгим uptime. Включайте через systemd-сервис после старта всех ваших модулей (например, после multi-user.target).

Сетевые параметры sysctl против DDoS и спуфинга

Сетевой стек Linux настраивается через ветку net.ipv4, net.ipv6 и net.core. Дефолты тут лучше, чем для kernel-параметров, но всё ещё далеки от оптимума. Я разделяю сетевой hardening на три блока: anti-spoofing, anti-DDoS и redirect-защита.

Anti-spoofing: reverse path filter

# Strict mode RFC 3704, пакет, чей source IP не маршрутизируется обратно через тот же интерфейс,
# дропается. Ломает большинство классических IP-spoofing атак.
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

Значение 1 это strict mode. 2 это loose mode, который разрешает асимметричную маршрутизацию (нужно на хостах с multi-homing). Если у вас один аплинк, ставьте 1.

Anti-DDoS: SYN cookies и backlog

# Включает SYN cookies при переполнении SYN backlog, критично против SYN flood
net.ipv4.tcp_syncookies = 1

# Увеличиваем backlog half-open соединений
net.ipv4.tcp_max_syn_backlog = 4096

# Сокращаем число SYN-ACK ретрансмиссий, быстрее освобождаем ресурсы
net.ipv4.tcp_synack_retries = 2

# Лимит на TIME_WAIT сокетов
net.ipv4.tcp_max_tw_buckets = 1440000

Стек nftables и SYN cookies дают комплементарные слои. О первом подробно в гайде по nftables, rate limiting и защите от DDoS. Sysctl-параметры включаются раньше, чем правила firewall успевают принять решение, и спасают, когда nftables-таблица случайно сгрузилась.

Redirect и source routing

# ICMP redirect, устаревший механизм, регулярно злоупотребляемый в MITM
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0

# Source routing, IP-пакеты с заданным маршрутом. Никогда не нужно в продакшене
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0

# Мы не маршрутизатор, не отправляем redirects сами
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# Логировать подозрительные пакеты (Martians)
net.ipv4.conf.all.log_martians = 1

Логирование martian-пакетов попадает в dmesg и через journald дальше в SIEM. Это дешёвый источник IoC: если видите IPv4: martian source ... from 10.0.0.5, значит кто-то на L2 пытается слать пакеты с чужим source IP.

Hardening файловой системы и виртуальной памяти

Эта группа параметров закрывает классические локальные эскалации через symlink-, hardlink- и FIFO-race conditions в /tmp, плюс защищает от NULL-pointer dereference в kernel space.

# Запрещаем следовать симлинкам в world-writable директориях, если владелец не совпадает
fs.protected_symlinks = 1

# То же для hardlink, нельзя создать hardlink на файл, который не читаешь
fs.protected_hardlinks = 1

# Защита FIFO и regular files в sticky-директориях (актуально с ядра 4.19+)
fs.protected_fifos = 2
fs.protected_regular = 2

# Минимальный адрес, который может mmap'ить непривилегированный процесс.
# Ломает NULL pointer dereference exploits в kernel space.
vm.mmap_min_addr = 65536

# Запрещаем дамп core файлов от suid-программ
fs.suid_dumpable = 0

# Аудит-лимит на открытые файлы (мера против file descriptor exhaustion)
fs.file-max = 2097152

Параметр protected_fifos=2 особенно ценен против атак на CI/CD-раннеры, где разные пайплайны делят /tmp. До его появления (Linux 4.19) был класс уязвимостей, когда вредонос подсовывал FIFO в /tmp/build-XXXX, и привилегированный процесс висел на чтении, отдавая стек атакующему.

Готовый конфиг 99-hardening.conf для продакшена

Собираем всё в один файл, который кладётся в /etc/sysctl.d/99-hardening.conf. Имя начинается с 99-, чтобы перебивать дистрибутивные дефолты (они обычно 10- или 50-).

# /etc/sysctl.d/99-hardening.conf
# Felix Lindqvist | kernel hardening baseline | ред. 2026-06

# === Kernel info leaks ===
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
kernel.printk = 3 3 3 3
kernel.printk_devkmsg = off

# === Ptrace / process inspection ===
kernel.yama.ptrace_scope = 2

# === User namespaces / BPF ===
kernel.unprivileged_userns_clone = 0
kernel.unprivileged_bpf_disabled = 2
net.core.bpf_jit_harden = 2

# === Module / kexec lockdown ===
kernel.kexec_load_disabled = 1

# === Performance counters (perf_event_paranoid) ===
kernel.perf_event_paranoid = 3

# === SysRq (только sync/reboot) ===
kernel.sysrq = 4

# === Anti-spoof / redirect ===
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.log_martians = 1

# === SYN flood / DDoS ===
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 4096
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_max_tw_buckets = 1440000

# === ICMP ===
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1

# === IPv6 (отключаем router advertisements на серверах со статикой) ===
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0

# === Filesystem hardening ===
fs.protected_symlinks = 1
fs.protected_hardlinks = 1
fs.protected_fifos = 2
fs.protected_regular = 2
fs.suid_dumpable = 0

# === Virtual memory ===
vm.mmap_min_addr = 65536
vm.unprivileged_userfaultfd = 0

Применяем без перезагрузки:

sudo sysctl --system        # перечитывает все /etc/sysctl.d/*.conf
# или явно
sudo sysctl -p /etc/sysctl.d/99-hardening.conf

Если параметр невалиден (опечатка, отсутствующий модуль), sysctl выведет ошибку cannot stat /proc/sys/.... Это нормально: некоторые ключи появляются только при загруженных модулях LSM/Yama. Установите kernel.yama.* только если в /sys/module/ есть yama.

Проверка, аудит через Lynis и откат изменений

После применения hardening я всегда прогоняю две проверки: дифф фактических значений против ожидаемых и независимый аудит. Дифф пишется одной строкой:

sudo sysctl -a 2>/dev/null | grep -E 'kptr_restrict|ptrace_scope|tcp_syncookies|rp_filter|protected_symlinks'

Для независимого аудита запускаем Lynis, open-source аудитор от CISOfy,, который проверяет hardening по сотням контролей CIS:

sudo apt install lynis           # Debian/Ubuntu
sudo dnf install lynis           # RHEL/Fedora
sudo lynis audit system --quick
sudo lynis audit system --tests-from-group kernel,networking

В отчёте смотрите секции KRNL-* и NETW-*. Hardening Index должен подняться с типичных 60–65 до 80+ после применения нашего конфига. Дополнительно сверяйтесь с актуальным CIS Benchmark for Distribution Independent Linux, это де-факто стандарт для compliance-аудитов.

Базовая методология сетевых параметров формализована в RFC 3704 (BCP 84), а полный референс по защите ядра ведёт Kernel Self Protection Project. KSPP-таблица параметров обновляется под каждое крупное ядро и должна быть закладкой у любого, кто отвечает за hardening.

Откат при проблемах

Все изменения runtime обратимы перезагрузкой, если файл 99-hardening.conf вы ещё не положили. Если конфиг уже сохранён, и после перезагрузки сервис не запускается:

# Загрузиться в single-user из GRUB (добавить systemd.unit=rescue.target),
# затем:
sudo mv /etc/sysctl.d/99-hardening.conf /root/99-hardening.conf.bak
sudo systemctl reboot

Для аудита постфактум смотрите, какой именно параметр конфликтует, и убирайте только его. Чаще всего ломается kernel.unprivileged_userns_clone=0 на хостах с Snap/Flatpak/rootless Podman.

Частые ошибки и совместимость с контейнерами

За годы работы я насмотрелся, как админы ломают прод через слишком агрессивный sysctl. Топ-5 ошибок:

  1. Применить modules_disabled=1 до загрузки сетевого модуля. Сервер остаётся без сети, лечится только живым консольным доступом.
  2. Установить vm.swappiness=0 на хосте с MySQL. OOM-killer убивает InnoDB вместо свопа. Используйте 1 или 10.
  3. Выключить userns на хосте с rootless Podman. Все контейнеры юзера падают с clone failed: Operation not permitted.
  4. Хардкодить rp_filter=1 на роутере с asymmetric routing. Пакеты возврата дропаются и получаются обрывы соединений, которые невозможно диагностировать tcpdump'ом на одном интерфейсе.
  5. Забыть net.ipv6.*. Включить anti-redirect для IPv4 и оставить IPv6 это половина защиты.

Контейнеры и sysctl

Docker и Podman частично прокидывают /proc/sys внутрь контейнера, но почти всё read-only. Network-namespace sysctls (net.*) часто доступны на запись только для --privileged или с явным --sysctl net.ipv4.ip_forward=1. Kubernetes 1.27+ разрешает unsafe sysctls в спецификации pod через securityContext.sysctls, но только из явного allowlist в kubelet.

Правило: hardening применяется на хосте, не внутри образа. Если контейнерному приложению нужен другой net.core.somaxconn, это пересматривают через kubelet-флаг --allowed-unsafe-sysctls, а не через RUN sysctl в Dockerfile (это вообще не работает на build-этапе).

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

Какие параметры sysctl важнее всего для безопасности Linux?

Топ-5 параметров с максимальным влиянием на снижение атакоповерхности: kernel.kptr_restrict=2, kernel.yama.ptrace_scope=2, kernel.unprivileged_bpf_disabled=2, net.ipv4.tcp_syncookies=1, net.ipv4.conf.all.rp_filter=1. Они закрывают утечки адресов ядра, инъекции в процессы, эксплойты в eBPF, SYN flood и IP spoofing. Всё за пять строк конфига.

Чем sysctl-hardening отличается от SELinux и AppArmor?

Sysctl-hardening меняет глобальное поведение ядра и сети, это превентивные runtime-параметры. SELinux и AppArmor реализуют Mandatory Access Control: они ограничивают, какие файлы и сисколы доступны конкретному процессу по политике. Слои дополняют друг друга: sysctl закрывает классы уязвимостей, MAC ограничивает blast radius при успешной эксплуатации.

Безопасно ли применять hardening sysctl на работающем продакшен-сервере?

Большинство параметров применяются мгновенно и обратимо: kptr_restrict, ptrace_scope, сетевые anti-spoofing, без downtime. Опасные параметры: modules_disabled (до перезагрузки не вернуть), kexec_load_disabled (необратимо в текущей сессии) и unprivileged_userns_clone=0 на хостах с rootless-контейнерами. Применяйте поэтапно: сначала kernel info-leak, потом сетевые, потом FS, в последнюю очередь необратимые lockdown-параметры.

Как проверить, что sysctl-параметры применились после перезагрузки?

После загрузки выполните sudo systemctl status systemd-sysctl.service. Юнит должен быть active (exited) без ошибок. Затем сверьте фактические значения: sudo sysctl kernel.kptr_restrict kernel.yama.ptrace_scope net.ipv4.tcp_syncookies. Для полного аудита запустите sudo lynis audit system и посмотрите Hardening Index в финальном отчёте.

Где лежат и как загружаются конфиги sysctl при старте?

Юнит systemd-sysctl.service при загрузке читает файлы из /etc/sysctl.d/, /run/sysctl.d/, /usr/lib/sysctl.d/ и устаревший /etc/sysctl.conf. Порядок лексикографический по имени; 99-*.conf загружается последним и перебивает всё остальное. Команда sudo sysctl --system повторяет ту же логику без перезагрузки.

Felix Lindqvist
Об авторе Felix Lindqvist

Penetration tester and OSCP holder. Reverse engineers misconfigured servers for a living and writes about what he finds.