Здесь я приведу пример интеграции пакетного фильтра с програмным окружением существующим в BSD, на примере борьбы с атаками brute-force — лобовым подбором паролей к машине. Любая система с запущенным на ней sshd(8) демоном подвергается многочисленным атакам brute-force. Если в вашей системе присутствуют пользователи со слабыми паролями, в виду многочисленности таких атак вас рано или поздно взломают. Существует множество способов защиты от атак brute-force. Их обилие свидетельствует о том, что все они плохи.
Обычно такой метод реализуется через систему PAM. В
Linux для этих целей есть специальный
модуль — pam_tally
.
В BSD никаких аналогов я не знаю,
однако его не сложно реализовать при помощи модуля pam_exec
. Идея состоит в том, что в
некоторой базе данных фиксируются попытки неудачного
захода от каждого пользователя. Если оказывается превышен
некоторый лимит, т.е. пользователь предпринял более 5
неудачных попыток входа, он блокируется и разблокировать
его может только суперпользователь.
Данный метод неудачен во всех отношениях: главный его недостаток — идеологический. Модуль наказывает не атакующего, а атакуемого. Задача защиты поставлена с ног на голову. Некоторые brain-damaged администраторы всерьёз предполагают, что таким образом можно защититься от атаки. Это не так:
pam_tally
никак этому
воспрепятствовать не может.
У модуля pam_tally
есть
многочисленные дополнительные опции, которые позволяют
сделать его работу более или менее вменяемой. Например,
можно запретить блокировку root'а и обеспечить
авторазблокирование по истечении некоторого периода
времени. Однако модуль неправильно задуман, при его
проектировании была сделана неправильная посылка, это
неисправимо.
Замечание | |
---|---|
SSH может не использовать модули PAM
вообще. Проверьте содержимое опции UsePAM
в конфигурационном файле
/etc/ssh/sshd_config .
|
Замечание | |
---|---|
OpenBSD не использует систему
PAM вообще. Похожего функционала в ней
можно, вероятно, добиться через пользовательские стили
аутентификации, описанные в Раздел F.2, «/etc/login.conf в OpenBSD ».
|
В настройках демона sshd(8) можно
задать опции AllowUsers
,
AllowGroups
, DenyUsers
и
DenyGroups
. Таким образом, вы можете явно
указать какие пользователи могут пользоваться сервисом
SSH на вашей системе. Это полезно, если
вам надо обеспечить доступ для одного-двух
«судоеров» (уполномоченных пользователей). Но
если речь идёт о машине предоставляющей shell-хостинг,
такая схема не подходит.
Замечание | |
---|---|
Независимо ни от чего, безусловно надо выставлять опцию
PermitRootLogin в No .
Это сделано по умолчанию во всех системах
BSD, но многие
Linux-системы по умолчанию допускают
bruteforce пользователя root! Особенно это относится к
разнообразным Desktop'ам.
|
Большего функционала можно добиться при помощи модулей
PAM. В FreeBSD при помощи модуля pam_login_access
можно указать
какие пользователи с каких хостов и с каких терминалов
могут входить в систему. Эта информация хранится в файле
/etc/login.access(5)
.
Наконец, вы можете в брандмауэре открыть проход на 22-й порт только с некоторых IP-адресов, однако в этом случае у вас не будет информации о логинах. Таким образом, этот метод хуже метода с использованием PAM, так как менее гибок, однако он больше экономит ресурсы системы.
Все эти способы ограничения атак имеют один общий недостаток — они неприменимы в случае, если shell доступ надо предоставить всем, или почти всем и в случае, если пользователи склонны перемещаться в пространстве и заходить в систему из разных мест.
Сканированием интернета занимаются роботы. Есть сообщения, что некоторые роботы прекращают работу, если брандмауэр блокирует сессию. Мы можем попробовать в брандмауэре блокировать сессии, если они образуются чаще, чем три раза в минуту, например. Т.е. средствами брандмауэра запретить устанавливать сессию чаще чем три раза в минуту. Это может отпугнуть некоторых роботов.
Данное действие надо осуществлять на уровне брандмауэра, а
не на уровне модуля PAM. В пакетном
фильтре этого функционала можно добиться при помощи опции
max-src-conn-rate
(см. Раздел C.2.1.4.7, «Опции таблицы состояний»).
Идеологически данный метод плох тем же, чем и метод с
pam_tally
—
наказывается не атакующий, а атакуемый. Кроме того, мне
кажется, что эффективность такого метода сильно
преувеличена. Когда я блокирую у себя злоумышленника, он
продолжает попытки достучаться до сервера ещё в течение
получаса.
Сервер sshd(8) заносит сообщения в
журнал от системы auth уровня важности info. По умолчанию
они попадают в журнальный файл
/var/log/auth.log
:
Apr 19 00:18:22 house sshd[15877]: Invalid user adam from 83.19.31.123 Apr 19 00:18:25 house sshd[15879]: Invalid user stephen from 83.19.31.123 Apr 19 04:19:27 house sshd[22644]: Invalid user test from 86.54.112.218 Apr 19 04:19:28 house sshd[22646]: Invalid user guest from 86.54.112.218 Apr 19 04:19:28 house sshd[22648]: Invalid user admin from 86.54.112.218 Apr 19 04:19:29 house sshd[22650]: Invalid user admin from 86.54.112.218 Apr 19 04:19:35 house sshd[22660]: Invalid user test from 86.54.112.218
На основании этих записей можно на лету заносить адреса злоумышленников в чёрный список и блокировать в брандмауэре.
Эта идея тоже не лишена недостатков: за одним IP адресом может скрываться большое количество хостов (выходящих через NAT) и многие из них могут принадлежать добросовестным пользователям. Да и сам источник атаки, возможно, о ней не подозревает... Однако здесь «наказывается» атакующая сторона.
В сети немало готового програмного обеспечения, борющегося
с атаками brute-force подобным образом. Как вариант, вы
можете попробовать рассмотреть программу
sshguard. (В FreeBSD
см. порт security/sshguard
.)
sshguard незлопамятна — она
не ведёт никаких собственных баз с вредными IP адресами и
не занимается разбором существующих журнальных файлов. Она
лишь осуществляет мониторинг текущих событий. Несомненным
преимуществом sshguard является то, что
она не громоздка — маленькая программка
написанная на C, и при этом может взаимодействовать с
пакетным фильтром OpenBSD,
ipfw(8) FreeBSD и
iptables(8) в Linux.
Мы рассмотрим как реализовать эту методику самостоятельно при помощи пакетного фильтра и дополнительного программного обеспечения: Python, SQLite.
Пусть имеется некоторый хост с запущенной а нём операционной системой BSD, всё равно какой: FreeBSD, OpenBSD, NetBSD или DragonFly BSD. В системе запущен демон sshd(8). Настройки SSH стандартны — демон слушает 22-й порт. На сервере запущен пакетный фильтр от OpenBSD.
Преступление: Критерий атаки — 8 попыток аутентификации под несуществующим логином или одна попытка аутентификации под метапользователем (root, toor и системные сервисы).
Наказание: Блокируем в брандмауэре доступ с «проштрафившегося» IP на 22-й порт сервера навсегда.
В журнал syslogd(8) записи от демона
sshd(8) попадают от средства auth
и имеют уровень важности info
. Сообщения о попытке логина от
пользователя root
выглядят следующим
образом:
Mar 22 17:48:13 house sshd[16889]: error: PAM: authentication error for root from 218.26.165.234 Mar 22 17:49:48 house sshd[16893]: error: PAM: authentication error for root from 218.26.165.234 Mar 22 17:49:50 house sshd[16893]: error: PAM: authentication error for root from 218.26.165.234 Mar 22 17:51:38 house sshd[16916]: error: PAM: authentication error for root from 218.26.165.234 Mar 22 17:51:40 house sshd[16916]: error: PAM: authentication error for root from 218.26.165.234
Сообщения о попытке логина под несуществующим логином:
Apr 19 00:14:51 house sshd[15782]: Invalid user admin from 83.19.31.123 Apr 19 00:14:58 house sshd[15784]: Invalid user admin from 83.19.31.123 Apr 19 00:15:04 house sshd[15786]: Invalid user admin from 83.19.31.123 Apr 19 00:15:09 house sshd[15799]: Invalid user admin from 83.19.31.123 Apr 19 00:15:31 house sshd[15809]: Invalid user test from 83.19.31.123 Apr 19 00:15:36 house sshd[15811]: Invalid user test from 83.19.31.123 Apr 19 00:15:41 house sshd[15813]: Invalid user webmaster from 83.19.31.123 Apr 19 00:15:55 house sshd[15817]: Invalid user username from 83.19.31.123
Замечание | |
---|---|
И пусть владельцы этих адресов не обижаются, что попали в Историю :) — логи настоящие. |
Отлавливать IP адреса можно при помощи следующего регулярного выражения (здесь и далее применяется язык программирования Python):
# Следующие строки сгенерированы автоматически при помощи awk(1): # awk -F: '$3<999 {printf(" \"%s\",\n", $1)}' /etc/passwd # При использовании vim (:!cmd) не забудьте защитить знак '%'. METAUSERS = [ "root", "toor", "daemon", "operator", "bin", "tty", "kmem", "games", "news", "man", "sshd", "smmsp", "mailnull", "bind", "proxy", "_pflogd", "_dhcp", "uucp", "pop", "www", "mailman", "mysql", "alias", "cyrus", ] METAUSERS_SQL = " or ".join(['login = "%s"'%x for x in METAUSERS]) # Регулярное выражение для поиска злоумышленников SSH_BAD_USER = re.compile(r"""(?xm) ^(?P<date>(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\ +\d+\ \d+:\d+:\d+)\s house\s sshd\[\d+\]:\ (?:I(?:nvalid|llegal)\ user\ (?P<login>\S+)| error:\ PAM:\ authentication\ error\ for\ (?P<metauser>%s))\s from\ (?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) """%"|".join(METAUSERS))
Для того, чтобы реагировать на события немедленно, можно записывать сообщения о событиях не только в файл, но и через pipe в скрипт. Конечно, мы могли бы поместить сценарий реагирующий на события в cron(8), и он бы разбирал журнальные файлы раз в полчаса, однако это привело бы к снижению в оперативности и к неоправданному расходу ресурсов системы. Демон syslogd(8) во многих дистрибутивах Linux умеет записывать только в именованный канал. Возможности syslogd(8) в BSD более привлекательны — он позволяет писать непосредственно на стандартный ввод программе, таким образом, он сам следит за тем, чтобы программа обработчик событий была запущена.
Итак, в файл /etc/syslog.conf(5)
помещаем
следующие строки:
auth.info;authpriv.info /var/log/auth.log auth.info | exec /root/bin/bfdefender -a
Здесь подразумевается, что
/root/bin/bfdefender
это программа
обработчик событий, которая читает стандартный ввод, если
вызвана с аргументом -a
. Поскольку, команда,
после знака |
выполняется
syslogd(8) демоном через
sh(1), мы применили команду exec
. Она замещает процесс
sh(1) на bfdefender.
Вот как выглядит дерево процессов при использовании ключевого
слова exec
:
$
pgrep python2.5 90433$
pstree -p 90433 -+= 00000 root [swapper] \-+= 00001 root /sbin/init -- \-+= 25867 root /usr/sbin/syslogd -l /var/run/log -l /var/named/var/run/log \--= 90433 root /usr/local/bin/python2.5 /root/bin/bfdefender -a
А вот как оно выглядит, если про команду exec
забыть:
$
pgrep python2.5 90385$
pstree -p 90385 -+= 00000 root [swapper] \-+= 00001 root /sbin/init -- \-+= 25867 root /usr/sbin/syslogd -l /var/run/log -l /var/named/var/run/log \-+= 90383 root sh -c /root/bin/bfdefender -a \--= 90385 root /usr/local/bin/python2.5 /root/bin/bfdefender -a
В пакетном фильтре необходимо создать две таблицы: таблицу с «хорошими» адресами, дабы случайно не заблокировать что-то не то, и таблицу с плохими адресами:
table <crackers> persist file "/etc/crackers" table <trusted_ip> persist { \ 62.117.108.0/26\ 212.57.97.252\ } ..... pass in quick on $ext_if inet proto tcp from <trusted_ip> \ to $ext_if port ssh keep state block in log quick on $ext_if inet proto tcp from <crackers> \ to $ext_if port ssh
Таблицу <crackers>
мы будем
на лету модифицировать при помощи скрипта, обрабатывающего
журнальные события.
#
pfctl -t crackers -Ta 218.26.165.234 83.19.31.123
2/2 addresses added.
Что же должен делать обработчик событий?
/etc/crackers
из которого будет брать
информацию пакетный фильтр при перезапуске.
В каком виде обработчик должен хранить промежуточную информацию? Сценарий на python(1) может хранить информацию в различных форматах. В виде конфигурационных файлов, в виде сериализованных объектов, во внешних базах данных, таких как MySQL и её аналоги. (Забегая вперёд, скажу, что сам я предпочёл вариант с SQLite.)
Недостатком конфигурационных файлов является то, что их трудно разбирать, это значит, что либо мы будем тратить несколько драгоценных секунд (а может быть и минут) при каждом запуске сценария, либо мы должны хранить уже «пережёванную» информацию.
Дело в том, что, согласно поставленной задаче, мы должны сохранять информацию не только о том, кто проштрафился (это список из нескольких сот IP адресов), но и информацию о том, под каким именем и сколько раз к нам подсоединялись злоумышленники. Это усложнит строение конфигурационного файла.
Пожалуй лучшее решение, которое тут можно
предложить — на лету писать сценарий на
python(1) и подгружать его в качестве модуля.
Т.е. использовать сам python(1) в качестве
парсера конфигурационного файла. Это и решит, отчасти, проблему
со скоростью, и упростит резервное копирование. Однако при этом
мы будем мусорить по диску прекомпилированными файлами
.pyc
и .pyo
, которые
будут содержать в себе секретную информацию, и нам придётся за
всем этим безобразием следить. Не лучший вариант.
Мы можем хранить информацию при помощи сериализованных объектов python(1). Это позволит ещё сильнее ускорить запуск. Однако работать с таким файлом не очень удобно — его формат непрозрачен. Человек, разбирающийся с python(1) легко с ним сладит, но не многие сочтут такой способ удобным. (Хотя в том, что таковые найдутся — не сомневаюсь.)
Наконец, можно хранить данные во внешней базе данных.
Для этого мы можем использовать СУБД, типа MySQL или PostgreSQL. Однако такое решение представляется слишком громоздким и опасным — при выходе из строя базы данных (конструкция, как ни крути, сложная и капризная) наш програмный комплекс сломается.
Более разумным представляется использование сисемы SQLite. SQLite не требует никакого демона, Python будет обращаться непосредственно файлу с базой данных. Это достаточно быстро и весьма надёжно.
Я не могу здесь рассказывать о том, как программировать на Python. Эта задача явно выходит за рамки данного учебника. Здесь будет приведено описание принципов работы скрипта и HOWTO: как его применять. Сам скрипт можно найти по следующей ссылке: bfdefender — скрипт для защиты от bruteforce атак.
Для работы данного скрипта нам понадобится установить следующие
порты: lang/python25
, databases/sqlite3
и databases/py-sqlite3
.
Для начала, скрипт надо запустить с опцией -i
:
#
./bfdefender -i
Это приведёт к тому, что скрипт проанализирует все журнальные
файлы /var/log/auth.log*
, включая сжатые
bzip2, и создаст базу данных
/var/db/crackers
. С этой базой в дальнейшем
можно общаться при помощи утилиты-клиента
sqlite3(1). Например, если нас одолеет
любопытство, когда к нам пытались зайти используя логин
закачивающийся на root
и сколькко раз
к нам пытались зайти под именем admin
, мы можем сделать к базе SQL
следующие запросы:
#
sqlite3 /var/db/crackers SQLite version 3.3.15 Enter ".help" for instructionssqlite>
.explain onsqlite>
.width 20 15 15sqlite>
SELECT * FROM crackers WHERE login LIKE "%_root" LIMIT 10; date ip login -------------------- --------------- --------------- 2007-03-28 14:14:56 89.149.195.132 11root 2007-03-28 14:16:17 89.149.195.132 wwwroot 2007-03-28 14:16:37 89.149.195.132 xxxroot 2006-11-22 08:12:45 217.160.173.110 nfsroot 2006-11-22 08:14:15 217.160.173.110 webroot 2007-02-19 13:12:39 221.254.131.203 gamroot 2007-01-25 02:15:24 148.244.79.94 cvsroot 2007-01-25 02:15:27 148.244.79.94 cvsroot 2006-10-23 23:36:17 208.57.150.227 gamroot 2006-10-13 13:48:34 208.57.150.227 gamrootsqlite>
SELECT COUNT(*) FROM crackers WHERE login = "admin"; COUNT(*) -------------------- 1862sqlite>
.exit
С помощью утилиты sqlite3(1) можно делать резервные копии базы, однако этот механизм в нашем случае внедрён прямо в скрипт.
Следующее действие — мы модифицируем файлы
/etc/pf.conf
и
/etc/syslog.conf
как было показано выше.
/etc/pf.conf
:
table <crackers> persist file "/etc/crackers" table <trusted_ip> persist { \ 62.117.108.0/26\ 212.57.97.252\ } ..... pass in quick on $ext_if inet proto tcp from <trusted_ip> \ to $ext_if port ssh keep state block in log quick on $ext_if inet proto tcp from <crackers> \ to $ext_if port ssh
/etc/syslog.conf
:
auth.info;authpriv.info /var/log/auth.log auth.info | exec /root/bin/bfdefender -a
В crontab(8) помещаем следующее задание:
@monthly /root/bin/bfdefender -b
Это задание будет ежемесячно изготавливать резервную копию с
нашей базы данных. Сама база, как говорилось, находится в файле
/var/db/crackers
, а резервная копия в файле
/var/db/crackers.sql.bz2
— это
обычный текстовый файл. Восстановить базу можно командой
#
./bfdefender -r
Это всё, что касается установки скрипта. Дополнительные
возможности по его использованию, можно узнать передав скрипту
параметр -h
или --help
. Скрипт
позволяет собирать некоторую статистическую информацию:
#
./bfdefender -s
===========================================================================
bfdefender report
===========================================================================
Detected 62309 attacks
user root: 161
user toor: 0
user daemon: 0
user operator: 0
user bin: 0
user tty: 0
user kmem: 0
user games: 0
user news: 0
user man: 0
user sshd: 0
user smmsp: 0
user mailnull: 0
user bind: 0
user proxy: 0
user _pflogd: 0
user _dhcp: 0
user uucp: 0
user pop: 0
user www: 0
user mailman: 0
user mysql: 0
user alias: 0
user cyrus: 0
===========================================================================
2006 May: 3981 attacks
2006 Jun: 2098 attacks
2006 Jul: 2094 attacks
2006 Aug: 6397 attacks
2006 Sep: 11202 attacks
2006 Oct: 11982 attacks
2006 Nov: 12 attacks
2006 Dec: 8921 attacks
2007 Jan: 4861 attacks
2007 Feb: 8693 attacks
2007 Mar: 2068 attacks
===========================================================================
poplar logins: 20 top
admin ................ 1862
test .................. 985
guest ................. 558
apache ................ 427
user .................. 403
info .................. 295
tester ................ 277
oracle ................ 272
webmaster ............. 271
ftp ................... 267
web ................... 252
sales ................. 239
adm ................... 231
postgres .............. 230
testing ............... 219
ftpuser ............... 218
administrator ......... 216
alex .................. 193
student ............... 168
paul .................. 165
Наслаждайтесь :)