Содержание статьи
Безопасность и надежность программной системы обратно пропорциональны ее сложности. Но к современным Linux-дистрибутивам эту формулу применить довольно проблематично, так как, по общепринятому мнению, они безопасны сами по себе. К сожалению, это не так, но в этой статье я расскажу и покажу, как сделать Linux действительно простой и очень устойчивой к взломам системой.
INFO
Хорошей практикой будет установка утилиты rkhunter, которая сверяет контрольные суммы системы, чтобы выявить факт модификации файлов.
Чтобы запутать взломщика, можно удалить команды uname и dmesg, переименовать ядро и внести соответствующие изменения в /boot/grub/menu.lst, то есть «обезличить» дистрибутив.
Защиту операционной системы принято строить послойно. Первый, внешний слой отвечает за взаимодействие ОС с внешним миром, то есть этот слой составляют сетевые сервисы, которые слушают определенные порты и отвечают на запросы клиентов. К таким сервисам относятся любые демоны, способные принимать сетевые сообщения, например web- и ftp-сервера, сервер DNS, почтовый сервер и т. д. Защиту этого слоя обеспечивают программисты, которые следят за безопасностью кода, и администратор, своевременно накладывающий заплатки и правильно конфигурирующий сетевые сервисы. Второй слой, или рубеж обороны, — это сама система, которая не должна позволить взломщику добраться до конфиденциальных данных или изменить важные системные файлы в случае нарушения границ первого слоя.
В идеале второму слою защиты нужно уделять не меньше внимания, чем первому, однако многие сисадмины пренебрегают этим, предпочитая полагаться на разработчиков дистрибутива, которые якобы уже позаботились о надежной защите своих продуктов. Это, конечно же, не так. Современные дистрибутивы — это универсальные операционные системы, рассчитанные на применение в самых разных областях и на решение различных задач. В них входит множество компонентов, которые создают потенциальную опасность и при этом вряд ли когда-нибудь тебе понадобятся. Многие дистрибутивы по умолчанию включают в себя средства сборки приложений, различные сетевые и диагностические инструменты, каждый из которых может использовать взломщик.
Самый верный способ обезопасить сервер в этой ситуации — это самому создать дистрибутив, ориентированный на решение конкретной задачи и лишенный всех тех компонентов, которые взломщик может использовать для получения сведений о системе, повышения своих прав или установки бэкдора.
Часть 1. Постановка задачи
Итак, мы решили сделать свой профильный дистрибутив. Прежде всего мы должны определиться с функциями, которые будет выполнять ОС. Здесь можно придумать массу вариантов, но я предлагаю остановиться на самом распространенном: дистрибутив для web-сервера. Наш будущий дистрибутив будет отвечать за хостинг web-сайтов, причем не статических, а написанных с использованием фреймворка Django. Почему не PHP+Djoomla/Drupal? Да потому, что я их не люблю!
Таким образом, в дистрибутив должны входить как минимум пять сервисов:
- Сам web-сервер (к черту Apache, nginx — наше все).
- Дистрибутив Python, который будет отвечать за работу Django.
- Django, на котором будут написаны сайты.
- PostgreSQL, которая будет хранить данные.
- SSH для удаленного управления.
Все остальные компоненты типичного сервера, такие как FTP-сервер, sendmail, и прочую шелуху можно выкинуть. Чем меньше в системе лишних сервисов, тем меньше шансов у взломщика.
Хакер #157. Деньги на багах в Chrome
Часть 2. Подготовка каркаса
Существует множество способов собрать собственный дистрибутив в домашних условиях, но все они требуют времени и терпения. Мы поступим проще: возьмем обычный Linux-дистрибутив и вырежем из него все лишнее. На роль подопытного хорошо подойдет ArchLinux, один из самых простых и легких дистрибутивов (ты, конечно, можешь выбрать что-нибудь другое вроде Slackware или Gentoo, но сути это не поменяет).
Получить базовую версию ArchLinux можно, например, с сервера Yandex (прямой линк на x86_64-сборку: http://goo.gl/EZRtQ). После загрузки ISO-образ следует скормить виртуальной машине и установить дистрибутив на виртуальный жесткий диск. Проблем во время установки возникнуть не должно: запускаем инсталлятор командой /arch/setup, выбираем «Select Source», жмем , выбираем «Prepare hard drive(s)», в ответ на все вопросы жмем , в последнем окне выбираем ФС ext2. Далее «Select Packages», в ответ на все вопросы. Затем выбираем «Configure system», жмем , в следующем окне выбираем Done. Выбираем «Install bootloader», выходим из редактора, а когда получаем предложение изменить /boot/grub.conf, жмем и, наконец, «Exit install». Перезагружаем машину (не забываем отключить ISO-образ).
После установки системы и загрузки виртуальной машины заходим в систему под учетной записью root (пустой пароль) и начинаем разбираться с содержимым системы. Прежде всего необходимо обновить все ее пакеты. Но сначала следует настроить сеть. Нормальные виртуалки имеют встроенный DHCP-сервер, поэтому обычно достаточно только запустить DHCP-клиент:
# dhcpcd eth0
Теперь, чтобы указать менеджеру Pacman на нужный репозиторий, добавляем в файл /etc/pacman.d/mirrorlist две строки:
Server = ftp://mirror.yandex.ru/archlinux/$repo/os/$arch
Server = http://mirror.yandex.ru/archlinux/$repo/
os/$arch
И запускаем процесс обновления:
# pacman -Syu
Возможно, при первом запуске обновится только сам Pacman. В этом случае следует выполнить команду второй раз. Далее устанавливаем необходимые для функционирования сервера компоненты. В моем случае это nginx, Python, Django и PostgreSQL:
# pacman -S nginx python2 django
Запоминаем, какие зависимости были установлены в процессе (чтобы ненароком не удалить их на следующем этапе ковыряния). У меня получилось всего четыре зависимости: libffi, postgresql-libs, libxml2 и sqlite3, но ее как раз можно удалить, поскольку есть Постгрес. Делаем снимок состояния диска виртуальной машины (это очень важно). При необходимости выключаем ВМ, делаем снимок и снова включаем.
Теперь мы должны удалить все лишние пакеты. Для начала выясним, что вообще сейчас установлено:
# pacman -Qs
Список не такой уж и длинный, но больше половины из всего вышеперечисленного нам точно не нужно. Во-первых, мы должны избавиться от инструментов сборки. По умолчанию в Арче нет GCC, но зато есть пакет binutils, который содержит линковщик, архиватор статических библиотек и другие инструменты:
pacman -R binutils
Далее удалим ненужные пакеты, например документацию и man-страницы:
# pacman -R licenses groff man-db man-pages texinfo
Избавимся также от инструментов управления различными файловыми системами и настройки RAID-массивов. Те из них, которые могут быть нужны на сервере, оставим на месте:
# pacman -R cryptsetup device-mapper lvm2 mdadm \
xfsprogs jfsutils reiserfsprogs
Далее можно избавиться от неиспользуемых инструментов управления сетью и связанных с ними библиотек и зависимостей:
# pacman -R iputils keyutils krb5 heirloom-mailx ppp \
wget dbus-core wpa_supplicant libpcap libnl libldap
Различные утилиты для управления USB-оборудованием, PCI- и PCMCIA-устройствами на сервере нам тоже не нужны:
# pacman -R usbutils pcmciautils sysfsutils
Также можно избавиться от некоторых оставшихся библиотек:
# pacman -R libpipeline libsasl libgcrypt libgpg-error
На этом процесс подготовки закончен, и мы можем переходить к основной задаче.
Часть 3. Режем все, что ушло из-под ножа
Мы удалили только малую часть того, что не потребуется на сервере, однако другие пакеты и зависимости дистрибутива также содержат множество ненужных библиотек и утилит. Сейчас мы пройдемся по системе и попробуем избавиться от всего лишнего.
А лишнего много. Например, ArchLinux не разделяет пакеты на -dev и бинарные, поэтому каталог /usr/include содержит в себе множество совершенно бесполезных и даже опасных заголовочных файлов. Избавимся от них с помощью следующей простой команды:
# rm -rf /usr/include
По той же причине каталог /usr/lib содержит большое количество статических библиотек, которые не нужны для работы системы, но могут быть использованы для сборки софта. Удаляем их:
# rm /usr/lib/*.a
В каталогах /bin, /sbin, /usr/bin, /usr/sbin имеется множество утилит и команд, которые были установлены как часть других пакетов, но сами по себе могут нанести вред серверу. В качестве примера можно привести различные утилиты для манипулирования файловой системой и таблицей разделов, утилиты для управления расширенными атрибутами файлов, создания пакетов и т. д. На работающем настроенном сервере пользы от них не так много.
Для начала избавимся от низкуровневых утилит управления файловой системой:
# rm /sbin/{badblocks,debugfs,dumpe2fs,e2image,e2label,
e2undo,resize2fs,tune2fs}
Теперь удалим инструменты управления таблицей разделов. Вряд ли взломщик будет их использовать, но чем черт не шутит:
# rm /sbin/{fdisk,cfdisk,sfdisk}
Также можно избавиться от утилит для создания файловых систем и swap-разделов:
# rm /sbin/mkfs.*
# rm /sbin/mkswap
Заодно удалим dd и install:
# rm /bin/{dd,install}
Теперь самое важное. Хакеры очень любят использовать различные утилиты для манипулирования расширенными атрибутами файловой системы, правами ACL, файловыми capabilities для защиты своих файлов от удаления/модификации или для делегирования приложениям расширенных прав доступа. Многие админы могут даже не знать обо всех этих методах, поэтому они пользуются особой популярностью. К счастью, мы можем защитить себя от подобных махинаций, просто удалив такие утилиты (вообще-то, они распространяются в отдельных пакетах, но кроме самих утилит эти пакеты включают в себя еще и библиотеки, необходимые для работы других нужных нам софтин).
Итак, избавляемся от утилит управления мандатными правами доступа (пакет acl):
# rm /usr/bin/{chacl,getfacl,setfacl}
Далее удаляем утилиты для манипулирования расширенными атрибутами файлов (пакет attr):
# rm /usr/bin/{chattr,lsattr,getfattr,setfattr}
И конечно же, утилиты управления capablities (пакет libcap):
# rm /usr/sbin/{getcap,setcap}
Также отправим в /dev/null утилиту для изменения контекста безопасности:
# rm /usr/sbin/chcon
На этом можно было бы остановиться, но Арч по умолчанию содержит набор инструментов cracklib, который необходим для некоторых (весьма полезных, кстати) модулей PAM, но при этом может быть использован для взлома паролей. Его, естественно, лучше удалить:
# rm /usr/sbin/cracklib*
# rm -rf /usr/share/{cracklib,dict}
В завершение удаляем менеджер пакетов. Да-да, все что нужно мы уже установили, а для обновления дистрибутива будем использовать метод, приведенный в конце статьи.
# rm /usr/bin/pacman*
# rm /usr/bin/makepkg
# rm /etc/pacman*
# rm -rf /var/cache/pacman
Ну и подчистим разные ненужности. Например, удалим файлы, требующиеся для сборки образа initrd:
# rm -rf /lib/initcpio
Уберем из системы ненужные каталоги:
# rm -rf /media /opt /usr/local
Удалим ядро с отладочной информацией (вместе с каталогом /usr/src):
# rm -rf /usr/src
Теперь, когда мы избавились от лишнего софта, можно пройтись по каталогам /lib /usr/lib и удалить неиспользуемые библиотеки (вряд ли их будет много, но лучше сделать это). Список нужных библиотек можно получить с помощью следующей команды:
# find /bin /sbin /usr/bin /usr/sbin |\
xargs ldd | grep '\.so' |\
cut -d ' ' -f 1 | sed 's/^[ \t]*//' |\
sort | uniq
Его можно сохранить в файле и с помощью команды diff сравнить с выводом команды «ls -1 /lib /usr/lib». Все не совпадающие библиотеки можно смело удалять.
Часть 4. Настраиваем сервер
Теперь у нас есть система, очищенная от хлама, но к работе она пока не готова. Сейчас мы должны настроить сервер, то есть nginx, Постгрес, Django и другие компоненты. Как это сделать, я рассказывать не буду, все уже описано до меня. Скажу только, что корнем веб-сервера и Django-проектов следует сделать какой-нибудь каталог в корне файловой системы, например /www (в нем можно создать два каталога: http — статика и django — скрипты). Это необходимо для того, чтобы в дальнейшем со строгими ограничениями смонтировать для каталога отдельный раздел.
Чтобы все работало, выполним базовую настройку системы и добавим в автозагрузку nginx и остальные демоны. ArchLinux не делает это автоматически, поэтому придется править /etc/rc.conf:
# Имя хоста
HOSTNAME="example.com"
# Настройка сетевого интерфейса
interface=eth0
address=1.2.3.4
netmask=255.255.255.0
broadcast=1.2.3.4
gateway=1.2.3.4
# Запускаемые при старте системы демоны
DAEMONS=(hwclock syslog-ng network crond postresql nginx)
Для индивидуального запуска/остановки демонов можно использовать скрипты каталога /etc/rc.d, например:
# /etc/rc.d/nginx restart
Далее мы должны предпринять некоторые меры по обеспечению внешней безопасности сервера. Статья, конечно, не об этом, но о базовой настройке рассказать все же стоит. Во-первых, установим пароль для рута:
# passwd
Во-вторых, заведем непривилегированного пользователя:
# useradd vasya
В-третьих, отключим root-логин по SSH:
# echo 'PermitRootLogin no' > /etc/ssh/sshd_config
# /etc/rc.d/sshd restart
Теперь отредактируем sysctl.conf, чтобы применить несколько классических техник защиты:
# Отключаем форвардинг пакетов
net.ipv4.ip_forward = 0
# Включаем верификация маршрута пакета
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
# Уменьшаем количество повторных передач SYNACK
net.ipv4.tcp_synack_retries = 2
# Отключаем редиректы
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
# Игнорируем широковещательные PING-запросы
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Защита от SYN-флуда
net.ipv4.tcp_syncookies = 1
Настроим брандмауэр так, чтобы открытыми оставались только порты 22 и 80 (SSH и HTTP):
# Стандартная политика
iptables -P INPUT DROP
iptables -A INPUT -i lo -j ACCEPT
# Небольшая защита от DoS
iptables -A INPUT -p tcp -m tcp --tcp-flags \
SYN,ACK,FIN,RST RST -m limit --limit 1/s -j ACCEPT
# Разрешаем уже установленные соединения
iptables -A INPUT -p all -m state --state \
RELATED,ESTABLISHED -j ACCEPT
# Наши порты
iptables -A INPUT -i eth0 -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --dport 80 -j ACCEPT
# Разрешаем некоторые ICMP-сообщения
iptables -A INPUT -i eth0 -p icmp -m icmp \
--icmp-type 3 -j ACCEPT
iptables -A INPUT -i eth0 -p icmp -m icmp \
--icmp-type 11 -j ACCEPT
iptables -A INPUT -i eth0 -p icmp -m icmp \
--icmp-type 12 -j ACCEPT
И последняя, но очень важная настройка: запрет на выполнение системных действий после окончания загрузки дистрибутива.
# echo 'echo $((0xffffffff ^ (1 << 16))) > \
/proc/sys/kernel/cap-bound' >> /etc/rc.local
# echo 'echo $((0xffffffff ^ (1 << 18))) > \
/proc/sys/kernel/cap-bound' >> /etc/rc.local
# echo 'echo $((0xffffffff ^ (1 << 19))) > \
/proc/sys/kernel/cap-bound' >> /etc/rc.local
# echo 'echo $((0xffffffff ^ (1 << 21))) > \
/proc/sys/kernel/cap-bound' >> /etc/rc.local
Эти строки добавляют в ядро после загрузки ограничения на загрузку модулей (для защиты от бэкдоров и руткитов), на выполнение системного вызова chroot, на выполнение ptrace, то есть на трассировку процессов, и на различные системные действия, например на монтирование файловых систем и подключение swap. Пока система не будет перезагружена, отменить ограничения не сможет никто, включая root.
Часть 5. Перенос системы на сервер и окончательная настройка
Все, теперь у нас есть минималистичный Linux-дистрибутив, выполняющий строго определенные функции, но как установить его на реальный сервер? Очень просто — скопировать. Для этого понадобятся установочный образ ArchLinux, чистая болванка, флешка и немного терпения. Выполняем весь процесс как указано ниже.
- Завершаем работу виртуальной машины.
- Настраиваем ее загрузку с ISO-образа ArchLinux, вставляем в комп флешку и прокидываем ее в виртуальную машину (с помощью VirtualBox это легко сделать).
- Подключаем разделы дистрибутива к каталогу /mnt. На моем виртуальном диске было три раздела: /dev/sda3 - корень, /dev/sda1 - /boot, /dev/sda4 - /home. Монтируем:
# mount /dev/sda3 /mnt # mount /dev/sda1 /mnt/boot # mount /dev/sda4 /mnt/home
- Подключаем флешку к каталогу /media:
# mount /dev/sdb1 /media
- Запаковываем систему в архив и кладем его на флешку:
# cd /mnt # tar -czf /media/root.tar.gz . # sync
- Завершаем работу виртуальной машины, нарезаем ISO-образ ArchLinux на болванку и идем к серверу, прихватив с собой флешку и болванку.
- Загружаем сервер с болванки и разбиваем диск с помощью cfdisk, руководствуясь следующей схемой:
sda1 — swap (размер: объем ОЗУ * 2); sda2 — корень (размер: 1 Гб); sda3 — /www (размер: столько, сколько нужно для хранения всех файлов веб-сайта); sda4 — /var (размер: от 1 Гб).
- Создаем в разделах файловые системы:
# mkfs.ext4 /dev/sda{2,3,4}
- Монтируем разделы к /mnt:
# mount /dev/sda2 /mnt # mkdir /mnt/www /mnt/var # mount /dev/sda3 /mnt/www # mount /dev/sda4 /mnt/var
- Монтируем флешку и распаковываем архив с системой:
# mount /dev/sdb1 /media # cd /mnt # tar -xzf /media/root.tar.gz
- Устанавливаем загрузчик:
# chroot /mnt # grub-install /dev/sda1
- Правим конфиг загрузчика:
title Arch Linux root (hd0,1) kernel /boot/vmlinuz30 root=/dev/sda6 ro initrd /boot/kernel30.img
- Правим /etc/fstab следующим образом:
devpts /dev/pts devpts defaults 0 0 shm /dev/shm tmpfs nodev,nosuid 0 0 /dev/sda1 swap swap defaults 0 0 /dev/sda2 / ext4 defaults 0 1 /dev/sda3 /www ext4 defaults,noexec,nodev 0 1 /dev/sda4 /var ext4 defaults,noexec,nodev 0 1 tmpfs /tmp tmpfs defaults,noexec,nodev 0 0
Каталог /www будет смонтирован без возможности запуска файлов и создания устройств, так что если взломщик проникнет в систему через дыру в веб-сервере и получит права пользователя www, он даже не сможет запустить эксплойт. Для каталогов /var и /tmp действуют те же правила.
Все, теперь можно перезагрузить машину и посмотреть, как все работает.
Часть 6. Обновление
Обновить полученный дистрибутив не так-то просто. Для этого потребуется виртуальная машина с установленным ArchLinux (самое время восстановить снимок, созданный в начале процесса) и sshfs. Процесс выглядит так:
- Включаем виртуальную машину.
- Устанавливаем sshfs.
- Обновляем базу данных пакетов и нужные софтины (например, nginx, PostgreSQL, SSH):
# pacman -Sy # pacman -S nginx postgresql ssh
- Запоминаем, какие пакеты обновились, и для всех этих пакетов, включая nginx, PostgreSQL и SSH, смотрим список файлов:
# pacman -Ql nginx postgresql ssh
- Перекидываем файлы на наш сервер с помощью sshfs:
# sshfs сервер /mnt # for file in `pacman -Ql nginx | cut -d ' ' -f 2`; do\ cp $file /mnt/$file;\ done
Готово! Конечно, это не автоматические обновления каждую ночь, но и не ночной кошмар, тем более что весь процесс вполне можно реализовать в скрипте.
Часть 7. Выводы
В этой статье приведены только базовые сведения о том, как обезопасить дистрибутив. Каждый сервер имеет свои особенности, которые необходимо учитывать. В любом случае главное правило: чем меньше компонентов, тем лучше.