ДЕНИС НАЗАРОВ
Пакетный фильтр OpenBSD
Позвольте мне поведать вам вновь об уникальных возможностях операционной системы OpenBSD[1]. На сей раз мой рассказ будет о пакетном фильтре, входящем в стандартный дистрибутив данной системы, начиная с версии 3.0.
Зачем вообще нужны пакетные фильтры? Любой из вас, наверное, знает о неприятностях, существующих в просторах сети, и не раз сталкивался с ними. Читая «Hacking Exposed 3th edition», я наткнулся на фразу: «2.13 AM, Do you know who is on your network?». Да, именно так, ведь вы, будучи администратором сети, не можете постоянно следить за тем, как и что проходит через вашу сеть. Однако вы можете контролировать данный процесс, используя пакетные фильтры. Итак, прошу любить и жаловать – OpenBSD’s Packet Filter (PF).
Конфигурация находится в файле /etc/pf.conf. Однако фильтр отключен по умолчанию в только что установленном дистрибутиве, и существует 2 способа включить его:
- Отредактировать файл /etc/rc.conf, изменив значение pf=NO на pf=YES, также стоит сразу проверить путь к конфигурационному файлу в директиве pf_rules=/etc/pf.conf и перезагрузить систему.
- Дать команду pfctl –e, которая заставит ядро включить фильтр с пустыми настройками и без перезагрузки системы.
Приступаем к конфигурации фильтра. Что же он умеет? Практически все. PF является очень серьезной альтернативой таким монстрам, как Checkpoint Firewall и Sun Screen, однако полностью бесплатен.
Итак, PF имеет следующие возможности.
Макросы
Макрос – очень удобная вещь, которая может сократить и оптимизировать ваш конфигурационный файл и избавит вас от проблем с частым использованием обьемных и часто повторяющихся директив. Я практически не представляю себе написание конфигурационных файлов для PF без использования макросов.
Например, определение макросов:
ext_if = “fxp0”
external_addr = “{ 192.168.0.13/32, 192.168.0.113/32 }”
И их использование:
pass in on $ext_if from any to $external_addr keep state
pass out on $ext_if from $external_addr to any port 1337 keep state
В качестве имени макроса не могут использоваться зарезервированные слова (pass, block, in, out и т. д.). Привыкайте сразу использовать макросы, это хороший стиль. Следование ему в дальнейшем позволит избежать многих проблем с пониманием работы и настройки фильтра.
Таблицы
Именные структуры, хранящие адреса хостов и сетей. Использование таблиц предпочтительнее использования макросов в тех случаях, когда необходимо описать огромное количество адресов. Для обработки правил, использующих таблицы, требуется меньше ресурсов, чем для обычных правил, решающих ту же задачу.
Таблицы также могут использоваться совместно с scrub (нормализация трафика), rdr (перенаправление пакетов), nat (трансляция адресов). И могут быть определены следующими способами:
- Вручную. Постоянные таблицы могут быть созданы командой add или replace утилиты pfctl до или после загрузки набора правил.
- pf.conf – для описания таблиц в файле используется директива table. Таблица, инициализированная с пустым листом { }, будет очищена при загрузке.
Таблицы бывают двух видов:
- persist – этот флаг заставляет ядро хранить содержание таблицы, даже если она не используется никакими правилами. Если таблица не помечена таким флагом, то ядро уничтожит ее после того, как последнее правило, ссылающееся на нее, будет удалено.
- const – этот флаг запрещает изменять содержание таблицы после того, как она была создана.
Например:
table const “{ 192.168.0.0/24, 192.186.13.0/24 }”
table persist
block in quick log on $ext_if from to any
pass in on $int_if from to $internal_addr keep state
Данный фрагмент конфигурационного файла создаст 2 таблицы. В первой, , будут содержаться адреса наших внутренних сетей, а будет пустой. Таблица не может быть изменена или дополнена, т.к при создании использовался флаг const.
Правила фильтрации отбрасывают любые пакеты, приходящие на внешний интерфейс ($ext_if) от хостов из таблицы , и пропускающие любые пакеты на внутреннем интерфейсе ($int_if), приходящие от хостов из таблицы .
# pfctl –t badhosts –T add 81.146.13.16
Теперь добавим в таблицу хост с адресом 81.146.13.16, и все пакеты от данного хоста будут отвергнуты. Таблицу лучше иметь всегда, т.к. в случае чего – это очень быстрый способ отвергнуть то, что может причинить боль вашей сети.
Таблицы могут заполняться списком адресов из какого-либо файла. Синтаксис команды, выполняющей это действие, таков:
table persts file “/etc/pf/spam” file “/etc/pf/openrelays”
block from to any port 25
Файлы /etc/pf/spam и /etc/pf/openrelay содержат список IP-адресов в формате:
/etc/pf/spam
204.168.0.13
213.145.168.2
/etc/openrelay
213.146.16.0/24
166.15.13.5/28
Допускается использование FQDN вместо IP-адреса. Перед занесением в таблицу имя будет переведено в IP-адрес с помощью функции gethostbyname().
Stateful Inspection
Прошу внимательно прочитать данный параграф, в нем я попытаюсь дать базовые понятия о Stateful Inspection – зачем это надо и почему полезно.
State – «состояние, положение» в переводе с английского. PF Stateful – пакетный фильтр, а это означает, что он отслеживает состояние соединения. Вместо того чтобы пропускать весь трафик, например, на порт 25 через все инстанции (читай фильтрацию) легче пропустить первый пакет и затем просто следить за соединением. Фильтр не сортирует трафик – он уже предупрежден об этом соединении и значит, просто следит за его состоянием.
Если пакет попадает под pass … keep state, фильтр создает state для этого соединения и автоматически пропускает весь трафик, принадлежащий данному соединению.
Перед тем как правила будут применены к данному пакету, фильтр проверяет наличие state для данного пакета, и если находит – пакет будет пропущен без проверки правилами. State удаляются сразу же по закрытии соединения или же по истечении тайм-аута.
Это дает некоторые преимущества. Сравнение пакета с таблицей states подразумевает проверку его sequence номера. Если номер вне диапазона ограниченного окна – пакет будет отброшен. Это позволяет избежать атак с подменой адреса (spoofed attacks).
Также поиск среди states гораздо быстрее, чем полная фильтрация пакета по правилам.
Использовать Stateful Inspection, еще раз напомню, позволяет директива keep state.
Например:
block all
pass out proto tcp from any to any flags S/SA keep state
pass in proto tcp from any to any port 25 flags S/SA keep state
Это основные понятия о Statetul Inspection.
Теперь давайте рассмотрим дополнительные настройки, которые мы можем использовать совместно с возможностью Stateful Inspection. Дело в том, что при стандартных установках фильтр будет работать, однако часто бывает, что для идеального функционирования сети данные параметры следует основательно подправить.
set timeout , где :
- interval – интервал перед удалением истекших (expired) state и фрагментов.
- frag – секунды перед тем, как неассемблированный пакет будет помечен как истекший.
- sc.track – время, в течение которого адрес отправителя будет продолжать отслеживаться, даже после того как последнее «состояние» для этого адреса будет окончено.
- tcp.first – «состояние» после первого пакета.
- tcp.opening – «состояние» перед тем, как удаленный хост пошлет первый пакет.
- tcp.estabished – «состояние» для TCP Three Way Hand shake (установленного соединения).
- tcp.finwait – «состояние» после того, как оба хоста обменялись пакетами с установлеными FIN-флагами и соединение считается закрытым.
- tcp.closed – «состояние» после того, как один из хостов посылает пакет с флагом RST
Протоколы ICMP и UDP настраиваются так же, как и TCP, только с гораздо меньшим набором states.
- udp.first – «состояние» после первого пришедшего пакета.
- udp.single – «состояние», если удаленный хост послал больше чем один пакет, а хост назначения не послал в ответ ни одного.
- udp.multiple – «состояние» после того, как оба хоста послали пакеты.
- icmp.first – «состояние» после первого пришедшего пакета.
- icmp.error – state на любое сообщение об ошибке, возвращенное ICMP-прокотолом.
Любые другие протоколы могут настраиваться так же, но используя:
- other.first;
- other.sigle;
- other.multiple.
Время timeout может изменяться в зависимости от количества state в той или иной группе, а также от нагрузки на сеть, за это отвечают параметры adaptive.start и adaptive.end.
- adaptive.start – когда количество «состояний» достигает данного порога, все timeout начинают изменять свои значения по формуле:
(adaptive.end – number of states) /
(adaptive.end – adaprive.start)
- adaptive.end – когда количество «состояний» достигает данного порога, все timeout-значения обнуляются и удаляют за собой все «состояния».
Все вышеперечисленные значения могут быть определены как глобально, так и для каждого правила в отдельности, это затронет и «состояния», созданные такими правилами.
Например:
set timeout tcp.first 120
set timeout tcp.established 86400
set timeout { adaptive.start 6000, adaptive.end 12000 }
set limit states 10000
Как только таблица будет содержать 9000 state, значения timeout снизятся до 50% (tcp.first 60, tcp.established 43200).
Манипуляция данными значениями позволяет очень тонко настроить работу вашего фильтра в тех или иных ситуациях. Это полезно для больших сетей или сетей, через которые проходят огромные обьемы трафика.
set loginterface – опция, которая позволяет указать, для какого интерфейса вы будете получать статистику по команде.
Например, у меня статистика выглядит так:
# pfctl -s i
Status: Enabled for 0 days 04:16:28 Debug: Urgent
Hostid: 0xd5f48ad7
Interface Stats for em1 IPv4 IPv6
Bytes In 219620287 72
Bytes Out 62229416 352
Packets In
Passed 759343 0
Blocked 1273 1
Packets Out
Passed 807867 2
Blocked 3 3
State Table Total Rate
current entries 344
searches 3286659 213.6/s
inserts 37555 2.4/s
removals 37211 2.4/s
Counters
match 182069 11.8/s
bad-offset 0 0.0/s
fragment 0 0.0/s
short 0 0.0/s
normalize 0 0.0/s
memory 0 0.0/s
bad-timestamp 0 0.0/s
|
Идем дальше:
set limit – позволяет установить жесткое ограничение на количество областей в памяти с фиксированным размером, используемых пакетным фильтром. Например:
set limit states 20000
устанавливает максимальное значение для количества элементов, используемых для «состояний».
set limit frags 20000
устанавливает максимальное значение для количества элементов, используемых для «fragment reassembly», генерируется при помощи scrub.
set limit scr-nodes 2000
число IP-адресов, для которых будут создаваться «состояния». Используется опять же для тонкой настройки фильтра, генерируется sticky-address и source-track опциями в правилах.
Наконец, все это можно обьединить в одну строчку:
set limit { states 20000, frags 20000, src-nodes 2000 }
set optimization – опция, которая позволяет оптимизировать работу вашего фильтра для сетей следующего типа:
- normal – обычная сеть, подходит для большинства сетей.
- high-latency – соединение с большими обьемами трафика (например, спутниковое).
- satellite – синоним для high-latency.
- aggressive – аргессивный режим, позволяет очень сильно уменьшить использование памяти, однако ценой сбрасывания соединений, находящихся в состоянии idle, работает быстрее, чем обычная.
- conservative – обратное от aggressive. Соотвественно грузим больше – живем дольше.
Я предпочитаю использовать на всех довольно загруженных серверах (порядка 150 Гб трафика в день на каждом) режим aggressive, в то время как в обычных сетях, конечно же, я использую normal.
Вы спросите, а зачем это надо? Зачем нужны те лимиты и тайм-ауты, которые я описывал выше? Ответ очевиден, PF может быть настроен так тонко, как не каждый пакетный фильтр, существующий ныне. Порой стандартных уровней оптимизации либо «слишком мало», либо «очень много», вот и приходится играть с тайм-аутами и лимитами, чтобы достичь идеальной производительности и максимальной отдачи.
set block-policy – определить, каким же будет ответ фильтра для действия block в правилах.
- drop – пакет просто отбрасывается.
- return – фильтр ответит пакетом с флагом RST для TCP или же пакетом ICMP UNREACHEBLE для UDP, и все остальные пакеты будут просто отброшены.
set require-order – определить порядок обслуживания. По умолчанию фильтр пропускает пакет по следующей цепочке: «Options –> Normalization –> Queueing –> Translation –> Filtering». То есть сначала применяются настройки для фильтра, потом нормализация трафика, затем распределение по очередям, дальше трансляция адреса и, наконец, сама фильтрация.
Данная цепочка является идеальной с точки зрения разработчиков фильтра, но они оставляют вам возможность исправить ее на ваше усмотрение.
set fingerprints – указать системе, где находится файл с определениями отпечатков (fingerprints) для различных операционных систем. Ну где вы еще видели пакетный фильтр, который умеет пропускать наружу или внутрь пакеты, приходящие от Linux, а от Windows 2003 отбрасывать? Мелочь, а очень приятно. Например:
set fingerprints "/etc/pf.os.devel"
set debug – уровень дебага для фильтра.
Опции:
- none – никаких сообщений;
- urgent – сообщения только для серьезных ошибок;
- misc – для различных ошибок;
- loud – для всего подряд.
Нормализация трафика
Не секрет, что нестандартными пакетами (например, размер или набор флагов) можно заставить некоторые сервисы перестать работать и еще много каких гадостей натворить в сети.
scrub – директива, отвечающая за нормализацию трафика.
Дополнительные параметры:
- no-df – убирает флаг «Don’t fragment» из заголовка пакета. Позволяет избавить вашу сеть от проблем со специально засланными пакетами огромного размера.
- min-ttl – меняет минимальное значение TTL для IP-пакета.
- max-mss – меняет максимальное значение MSS для TCP-пакета.
- random-id – меняет IP identification-поле в заголовке пакета для защиты TCP-соединения от вторжения методом подбора значения, передаваемого в данном поле, применяется только к исходящим соединениям.
- fragment reassemble – позволяет держать в памяти фрагменты пакетов до того момента, пока пакет не будет полностью составлен. Гарантирует вам отсутствие лишнего трафика и нагрузки на сеть.
- fragment crop – стандартный метод, фрагменты пакетов пропускаются в сеть, где уже сами хосты пытаются собрать их в целый пакет. Данный метод неэффективен с точки зрения нагрузки на сеть.
- fragment drop-ovl – то же самое, что и предыдущее, разница лишь в том, что одинаковые фрагменты, пришедние по какой-либо ошибке, будут отброшены фильтром.
- reassemble tcp – нормализация TCP-соединений. Дополнительные возможности – ttl, timeout modulation, extended PAWS checks. Данные темы я рассмотрю в следующих статьях, т.к это относится непосредственно к протоколу, а не к пакетному фильтру.
Например:
scrub in all
и все, фильтр сам подберет оптимальные параметры и разберется со всеми потенциально опасными пакетами.
Распределение по очередям
В этой статье я не буду описывать возможности PF при работе с контролем полосы пропускания, об этом в следующих статьях. Скажу только, что в фильтр встроен ALTQ и механизмы cbq, priq, hfsc.
NAT – Network Address Translation – трансляция адресов. Определяет, как адреса будут транслироваться или перенаправляться относительно других адресов.
Фильтр позволяет осуществить:
- binat – полную двухстороннюю замену между внутренним и внешним IP-блоком;
- nat – обычную трансляцию;
- rdr – пакет перенаправляется на любой порт и любой хост. Маленькое дополнение: если пакет перенаправляется во внутренную сеть, то помимо rdr надо сделать и nat для хоста, который будет отвечать во внешний мир на данные перенаправленные пакеты.
Пример:
rdr on em1 inet proto tcp to port 80 -> $web_server port 80
Фильтрация пакетов
Фильтрация протекает стандартно – создаются правила.
Правила могут иметь 2 результата:
- block – пакет будет отброшен, используя следующие способы:
- drop – пакет просто удаляется и никаких данных в ответ не посылается.
- return-rst – только для TCP-пакетов, отсылается пакет с флагом RST, который прекращает соединение немедленно.
- return-icmp (return-icmp6) – возвращается сообщение ICMP UNREACHABLE, однако данное значение можно переопределить любым кодом ошибки ICMP-протокола.
- return – автоматически TCP RST будет возвращен для TCP-пакетов и ICMP UNREACHABLE для UDP- и других протоколов.
- pass – пакет будет пропущен фильтром.
Если ни одно правило не подходит, то пакет будет пропущен.
Итак, параметры фильтрации:
- in или out – пакет идет к нам или от нас соотвественно. Если не определено, то считается, что направление двухстороннее.
- log – записывать информацию о пакетах, однако будут записаны только пакеты, которые попадают в «состояния».
- log-all – записывать информацию о всех приходящих пакетах, независимо от того, попал он в state или нет.
- quick – если пакет попадает под это правило, то дальнейшие правила не применяются. Полезно, например, использовать с таблицей .
- on<interface> – правила применяются только в случае, если пакет идет с указанного интерфейса.
- proto<protocol> – правила применяются только к пакетам данного протокола. Например, ICMP, TCP, UDP. Полный список протоколов находится в /etc/protocols.
from <source> port <sourceport> to <dest> port <destport>
Примерно понятно? Откуда, с какого порта, куда, на какой порт.
Опции следующие:
- any – любой адрес.
- no route – адреса, которые вне таблицы роутинга.
- <table> – адреса из таблицы «table».
Порты могут указываться с использованием стандартных операторов (=, !=, <, <=, >, >=, :, ><, <>).
- flags <a>/<b> | <b> – данные правила применяются только для пакетов с установленными флагами <a> перед флагами <b>. Флаги F(IN), S(YN), R(ST), P(USH), A(CK), (U)RG, E(CE), C(W)R.
Комбинировать флаги можно по-разному, однако опыт показывает, что оптимальным является сочетание flags S/SA. Это значит, что перед тем как отправлять пакеты с флагами SYN+ACK (соединение установлено), хост обязан отправить пакет с флагом SYN. Данный набор идеален для обычных TCP/IP-сетей, где не проводят никаких сетевых экспериментов, а просто потихонечку выкачивают весь Интернет к себе на жесткие диски.
Утилита управления пакетным фильтром pfctl
Pfctl – довольно мощное средство, предоставляемое вам для управления фильтром. И, кстати, единственное.
Используется для выполнения следующих операций:
# pfctl –T load –f /etc/pf.conf
Вот, в общем, основные команды для pfctl. Думаю, с этой утилитой вы разберетесь очень быстро.
Заключение
В следующей статье я обязательно расскажу вам обо всех вещах, не затронутых в этой статье, таких как контроль полосы пропускания, роутинг и т. д. И с удовольствием отвечу на все ваши вопросы относительно пакетного фильтра OpenBSD.
Напоследок привожу пример конфигурационного файла, в котором использованы все те возможности, о которых я писал.
ext_if="{ rl0 }"
external_addr="{ 192.168.0.13/32, 192.168.0.113/32 }"
allowed_tcps = "{ 22, 25, 80 }"
allowed_udps="53"
insecure_tcps="{ 21, 53, 110 }"
#insecure_udps=
insecure_tcp_hosts = "{ 192.168.9.13/32, 192.168.13.13/32 }"
#insecure_udp_hosts
set timeout { interval 30, frag 10 }
set timeout { tcp.first 120, tcp.opening 30, tcp.established 86400 }
set timeout { tcp.closing 900, tcp.finwait 45, tcp.closed 90 }
set timeout { udp.first 60, udp.single 30, udp.multiple 60 }
set timeout { icmp.first 20, icmp.error 10 }
set timeout { other.first 60, other.single 30, other.multiple 60 }
set limit { states 10000, frags 5000 }
set loginterface rl0
set optimization normal
set block-policy drop
set require-order yes
scrub in all
table persist
table persist
rdr pass inet proto tcp from to any port smtp -> 127.0.0.1 port 8025
rdr pass inet proto tcp from ! to any port smtp -> 127.0.0.1 port 8025
block in log all
pass out proto { tcp, udp, icmp } all keep state
pass in on lo0 all
pass out on lo0 all
pass in inet proto icmp all icmp-type echoreq keep state
pass in on $ext_if proto tcp from any to $ext_if port $allowed_tcps flags S/SA keep state
pass in on $ext_if proto udp from any to $ext_if port $allowed_udps keep state
pass in on $ext_if proto tcp from $insecure_tcp_hosts to $ext_if port $insecure_tcps flags S/SA keep state
pass in on $ext_if proto tcp from any to any port > 49151 keep state