Файрвол-невидимка на OpenBSD

Andrey Matveev

Хакер, номер #079

(andrushock@real.xakep.ru)

Хакерский брандмауэр на страже интересов админа

Так это, значит, Невидимка? — спросил чернобородый, заложив одну руку за спину. — Я думаю, пора уж и посмотреть на него. (Herbert Wells. The Invisible Man.)

Представь себе такую ситуацию: 24 часа в сутки, 7 дней в неделю хост в интернете, не имея ни одного IP-адреса, фильтрует входящий и исходящий трафик, нарезает канал, осуществляет привязку IP к MAC, противодействует DOS-атакам и попыткам IP Spoofing'а, дезинформирует сканирующих киддисов, работает в качестве системы обнаружения вторжений, блокирует червей и нежелательную корреспонденцию. Мечта любого админа, гроза всех хакеров. Думаешь, это фантастика? Постараюсь тебя переубедить.

[плюсы и минусы темной лошадки]

Одна из программных систем, позволяющих справиться со всеми перечисленными задачами — связка pf(4) и bridge(4). Если с первым псевдоустройством все должно быть более-менее ясно, то на втором стоит остановиться подробнее.

Бридж представляет собой разновидность интеллектуального коммутатора, соединяющего различные сети между собой так, что компьютер из одной сети способен общаться с компьютером из другой без участия маршрутизатора. С помощью прозрачного моста можно:

1. установить файрвол без изменения топологии существующей сети;
2. объединять не только проводные и беспроводные участки сети, но и сети разной архитектуры (скажем Ethernet и Token Ring);
3. изолировать трафик между несколькими выбранными клиентами;
4. снизить общую нагрузку на сеть;
5. вести таблицу соответствия MAC-адресов источников и адресатов;
6. обходить привязку IP к MAC, сделанную на провайдерском сервере (см. врезку);
7. экономить статические IP-адреса, предоставленные поставщиком услуг интернета.

Нельзя забывать и об аспекте безопасности: такой хост будет невероятно сложно не то что взломать, но даже обнаружить. В довершение всего мы обойдемся без хардкорного твикинга переменных sysctl, тоскливой перекомпиляции ядра и многочисленных перезагрузок.

Хочу сразу рассказать и о минусах использования такой конструкции:

1. все сетевые интерфейсы, помещенные в бридж, будут автоматически переведены в режим приема всех пакетов (дело в том, что PROMISC в некоторых случаях не представляется возможным: не позволяет сетевая карта, например TI ThunderLAN, на бридже используется слишком слабый процессор, вето провайдера);
2. для корректной работы по протоколу FTP придется установить специальную проксю ftpsesame;
3. с помощью правила pf "synproxy state" нельзя будет проксировать TCP-соединения и защищать бокс от SYN-флуда;
4. может возникнуть некоторая сложность в администрировании.

Но обо всем по порядку.

Тестирование прозрачного моста проводилось на Pentium 90/32 Mb/4 Gb/2 fxp (две PCI'ные сетевые карты Intel EtherExpress PRO/100+) под управлением OpenBSD 3.7. Будем считать, что на внешний сетевой интерфейс (fxp0) подходит кабель от провайдера, а внутренний интерфейс (fxp1) соединен кроссовером с внешним интерфейсом шлюза компании:

--[ ISP_LAN ]-----(fxp0)[ BRIDGE ](fxp1)-----[ GATEWAY ]--

Также (для чистоты эксперимента и остроты ощущений) приведу вариант с ADSL LAN-модемом и беспроводным клиентом:

--[ ISP_ADSL ]-----(fxp0)[ BRIDGE ](ral0) - - - [ LAPTOP ]

Последовательно поднимаем сетевые интерфейсы:

# echo up > /etc/hostname.fxp0

# echo up > /etc/hostname.fxp1

В случае с Wi-Fi адаптером (здесь Gigabyte GN-WPKG 802.11 b/g выступает в качестве точки доступа) команда будет выглядеть так:

# echo 'up media autoselect mode 11g mediaopt hostap nwid wlan chan 11' > /etc/hostname.ral0

Создаем транспарентный бридж:

# echo 'add fxp0 add fxp1 up' > /etc/bridgename.bridge0

Для проверки работоспособности разрешаем прохождение всех пакетов без ограничений:

# echo 'pass quick on { lo0, fxp1, fxp0 } all' > /etc/pf.conf

Правим конфигурационный файл /etc/rc.conf, содержащий параметры загрузки демонов при старте системы:

# vi /etc/rc.conf

/* В сетевых службах нет надобности, мы просто не сможем ими воспользоваться */
sshd_flags=NO
sendmail_flags=NO
portmap=NO
inetd=NO
/* Включаем фильтр пакетов */
pf=YES
pf_rules=/etc/pf.conf
Все. На этом настройка закончена, перезагружаемся:
# shutdown -r now


Существует две немаловажные особенности, относящиеся ко всем видам бриджей (обычному, при котором каждому сетевому интерфейсу соответствует свой IP-адрес; полупрозрачному — установлен хотя бы один IP-адрес; прозрачному — наш случай, в котором нет ни одного IP-адреса). Во-первых, пакеты проходят через псевдоустройство pf дважды. Такое поведение обусловлено тем, что бридж перенаправляет пакетики с одного сетевого интерфейса на другой (примечание: в файле /etc/sysctl.conf нет необходимости устанавливать значение переменной net.inet.ip.forwarding равным 1). Поэтому нужно пропускать весь трафик на внутреннем интерфейсе, а фильтрацию производить только на внешнем. Либо наоборот: фильтровать на внутреннем, пропускать на внешнем. Кому как проще мысленно представлять маршруты прохождения пакетов. И, во-вторых, в разрешающих правилах (pass) в обоих направлениях (in/out) на фильтруемом интерфейсе (в нашем случае fxp0) для сохранения состояния соединений необходимо использовать сочетание «keep state».

# vi /etc/pf.conf

/* Объявляем макросы */
ext_if = "fxp0"
int_if = "fxp1"
unroutable = "{ 127/8, 10/8, 172.16/12, 192.168/16, 255.255.255.255/32 }"
/* Предмет отдельного разговора, см. ниже */
scrub on $ext_if all random-id reassemble tcp fragment reassemble
/* Запрещаем трансляцию адресов (паранойя, так как NAT на транспарентном бридже все равно не будет работать) */
no nat on { lo0, $int_if, $ext_if } from any to any
/* Применяем политику запрета по умолчанию */
block all
block inet proto tcp
block inet proto udp
/* Блокируем и регистрируем соединения по протоколам IPv6 и IGMP */
block in log quick inet6 all
block out log quick inet6 all
block in log quick proto igmp all
block out log quick proto igmp all
/* Блокируем широковещательные запросы и пакеты с фальсифицированным адресом источника */
block in quick on $ext_if inet from any to 255.255.255.255
block in log quick on $ext_if inet from $unroutable to any
/* Блокируем и регистрируем попытки сканирования */
block in log quick on $ext_if from any os NMAP
/* На этих интерфейсах пропускаем пакеты */
pass quick on { lo0, $int_if } all
/* Еще один метод борьбы с IP Spoofing'ом */
antispoof log for lo0 inet
/* Осторожно отвечаем на эхо-запросы */
pass in on $ext_if inet proto icmp from any to any icmp-type 8 code 0 keep state (max 32)
pass out on $ext_if inet proto icmp from any to any icmp-type 8 code 0 keep state
/* пропускаем весь исходящий UDP-трафик */
pass out on $ext_if inet proto udp from any to any keep state
/* Чтобы получить качественно сгенерированные ISN-числа, включаем модуляцию для исходящих TCP-соединений (так мы становимся менее уязвимыми для атак типа TCP Hijacking); применив модуляцию для входящих TCP-соединений, можно успешно противостоять ACK-наводнениям */
pass out on $ext_if inet proto tcp from any to any flags S/SA modulate state

За счет использования ключевого слова scrub производится нормализация и дефрагментация IP-пакетов (замечание: пересборка фрагментов производится только для IPv4-пакетов, и только если явно не указан параметр no-df в случае, когда необходимо пробросить NFS-трафик через файрвол). Правило вида «scrub on $ext_if all fragment reassemble» отбрасывает пакеты с недопустимыми комбинациями флагов (например SYN и FIN или SYN и RST), тем самым вводя в заблуждение сканеры портов ala Nmap. Помимо «fragment reassemble», директива scrub обладает еще несколькими заслуживающими внимания опциями:

random-id — позволяет генерировать случайные идентификаторы в заголовке IP-пакета;

min-ttl значение — устанавливает минимальное значение времени жизни в заголовке IP-пакета;

max-mss значение — устанавливает максимальный размер сегмента в заголовке IP-пакета (например, правило «scrub out on pppoe0 max-mss 1440» идеально для ADSL);

reassemble tcp — усложняет выяснение аптайма хоста, расположенного за NAT'ом, а также запрещает понижать TTL как инициатору соединения, так и адресу назначения (мы же не хотим, чтобы наш бридж так просто обнаружили).

Для тестирования некоторых правил файрвола можно воспользоваться пассивным фингерпринтингом lcamtuf.coredump.cx/p0f-help/. Приведу пример для обычного, непрозрачного бриджа под управлением OpenBSD 3.6-STABLE (pf с опциями scrub и nat):

my.real.ip.addr:52566 - OpenBSD 3.0-3.4 (up: 494 hrs) Signature:

[16384:48:1:64:M1460,N,N,S,N,W0,N,N,T:.] ->

213.134.128.25:80 (distance 16, link: ethernet/modem)

Отчет онлайновой утилиты после включения "reassemble tcp":

my.real.ip.addr:60512 - OpenBSD 3.0-3.4 (up: 8850 hrs) Signature:

[16384:48:1:64:M1460,N,N,S,N,W0,N,N,T:.] ->

213.134.128.25:80 (distance 16, link: ethernet/modem)

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

# pfctl -n -f /etc/pf.conf

И активируем новые правила:

# pfctl -f /etc/pf.conf


Невероятно, но факт: на одном хосте *можно* совместить транспарентный мост и транспарентную проксю. Сложность заключается вот в чем: ввиду специфики сетевой модели (разные уровни OSI) пакеты, которые форвардит с одного интерфейса на другой прозрачный мост, просто не доходят до IP-стека операционной системы. Соответственно, правила перенаправления (rdr) не будут работать для пакетов, не адресованных бриджу. Поэтому одного редиректа недостаточно: нужно роутить (route-to) входящие www-запросы на интерфейс обратной петли. Если перейти на язык конфигов, ларчик открывается следующим образом:

# vi /etc/pf.conf

/* Пользователи (например, 192.168.1.2/32, 192.168.1.5/32) прописаны в специальном файле */

table persist file "/etc/pfusers.conf"

/* Содержимое www-серверов этих подсетей не кэшируем */

table { 192.168.1.0/24, 192.168.7.0/24 }

/* Запросы от пользователей, поступающие на внутренний сетевой интерфейс, перенаправляем на проксю */

rdr on $int_if from to ! port { 80, 8080, 8101 } -> 127.0.0.1 port 3128

/* Роутим нужные пакетики на loopback-интерфейс */

pass in on $int_if route-to lo0 from to 127.0.0.1 port 3128


У данной конструкции есть еще одна изюминка: бридж можно подстраивать и пересобирать что называется "на лету". А именно: удалять одни сетевые интерфейсы, добавлять другие, модифицировать правила привязки и значения переменных, полностью или частично очищать таблицу MAC-адресов. И все это без перезагрузки: успевай только корреспондировать имена сетевых интерфейсов в бридже и правилах файрвола. В качестве примера заменим fxp1 на беспроводной ral0:

# brconfig bridge0 down

# ifconfig fxp1 down

# ifconfig bridge0 destroy

# ifconfig bridge0 create

# brconfig bridge0 add fxp0 ral0 up

Теперь в /etc/pf.conf остается лишь исправить значение макроса $int_if на ral0 и перечитать конфиг. Сказка.

% brconfig bridge0

bridge0: flags=41

Configuration:

priority 32768 hellotime 2 fwddelay 15 maxage 20

Interfaces:

ral0 flags=7

port 1 ifpriority 128 ifcost 55

fxp0 flags=7

port 3 ifpriority 128 ifcost 55

Addresses (max cache: 100, timeout: 240):

00:0f:ea:91:43:f6 ral0 1 flags=0<>

00:0f:3d:f4:d6:63 fxp0 1 flags=0<>


Так как наш хост не имеет ни одного IP-адреса, нам не удастся использовать его сетевые службы, а значит, и удаленно админить. Изменять конфигурацию можно будет только сидя непосредственно за компьютером (console) либо с помощью нуль-модемного кабеля (serial console). Конечно, доставив еще один сетевой адаптер и присвоив ему глобально (не)маршрутизируемый IP-адрес, мы решим проблему, но, согласись, с этим ухищрением пропадет вся красота прозрачности брандмауэра. Поэтому в качестве терминала предпочтительнее использовать старенький ноут класса 486/DX2-66 ;-). Рассмотрим этот вариант поподробнее.

В отличие от DOS и Windows, нумерация последовательных портов в OpenBSD начинается не с единицы, а с нуля. Виндовому COM1 соответствует com0 (/dev/tty00), а COM2 - com1 (/dev/tty01). На самом раннем этапе загрузки OpenBSD убедись, что оба порта верно распознаются загрузчиком: "probing: pc0 com0 com1 apm mem[636K 510M a20=on]". Если ты являешься счастливым обладателем управляемого источника бесперебойного питания, повесь интерфейсный кабель на com1, так как «serial console» умеет работать только с com0.

Настройка сводится к приведению файла терминальных сессий /etc/ttys и конфига загрузчика /etc/boot.conf (по умолчанию не существует) к следующему виду:

# vi /etc/ttys

/* Включаем коммуникационный терминал */

console "/usr/libexec/getty std.9600" vt220 on secure

# vi /etc/boot.conf

/* Устанавливаем скорость порта равной 9600 bps */

stty com0 9600

/* Нуль-модемный кабель подключен к первому последовательному порту */

set tty com0

/* Игнорируем пятисекундную задержку при загрузке ОС */

boot

Можно на свой страх и риск установить скорость порта равной 19200 bps, но только после перекомпиляции ядра с опциями:

option PCCOMCONSOLE

option CONSPEED=19200

Теперь перезагружай мост, а на ноуте (или на обычном компьютере, играющем роль терминала) открывай SecureCRT, создавай новое подключение по протоколу serial, отключай аппаратное управление потоком и в комбобоксах выбирай 9600/8/None/1. С этого момента весь консольный вывод будет идти на терминал по нуль-модемному кабелю:

>> OpenBSD/i386 BOOT 2.08

com0: 9600 baud

switching console to com0


Но вернемся к нашим бриджам. С помощью системных вызовов ioctl(2) утилита brconfig(8) позволяет не только запрашивать у ядра состояние помещенных в мост сетевых интерфейсов, но и производить их конфигурирование. Перечислю некоторые интересные опции brconfig:

maxaddr размер — количество записей в кэше моста.

timeout время — таймаут, в течение которого записи будут истекать.

static ифейс адрес — занесение клиентского MAC-адреса в кэш.

deladdr адрес — удаление клиентского MAC-адреса из кэша

blocknonip ифейс — блокировка трафика, отличного от IP, например соединения по протоколам IPX или NETBEUI (постарайся использовать эту опцию при первой возможности).

rule запись — добавить правило фильтрации.

Пример предоставления доступа беспроводному клиенту с привязкой IP к MAC:

# vi /etc/bridgename.bridge0

static ral0 00:0f:ea:91:43:f6

up

rule pass in on ral0 src 00:0f:ea:91:43:f6 tag andrushock

rule block in on ral0

# echo 'block in quick on ral0 from ! 192.168.1.21/32 to any tagged andrushock' >> /etc/pf.conf

В итоге, собрав по кусочкам необходимые переменные вместе, получим конфиг для транспарентного бриджа, работающего в параноидальном режиме. Будь осторожен: это достаточно агрессивная конфигурация, которая может не заработать в твоем случае.

# vi /etc/bridgename.bridge0

add fxp0
add ral0
blocknonip fxp0
blocknonip ral0
-learn fxp0 ral0
-discover fxp0 ral0
static ral0 00:0f:ea:91:43:f6
maxaddr 10
timeout 0
up
rule pass in on ral0 src 00:0f:ea:91:43:f6 tag andrushock
rule block in on ral0


[kernel hacking]

Есть способ, позволяющий сделать невидимый файрвол еще более невидимым :). Заключается он в том, чтобы научить ядро операционной системы возвращать ICMP-сообщения так, будто они отправлены с клиентского хоста, а не с файрвола. Экспериментальный патч для OpenBSD 3.5 можно взять здесь: marc.theaimsgroup.com/?l=openbsd-pf&m=108858252615179&w=2. Попробуй адаптировать его к своей версии операционки, если, конечно, ты действительно заинтересован в этой фиче.


Присутствует еще один интересный момент, о котором нельзя не упомянуть. С помощью прозрачного моста можно в некотором роде обойти привязку IP к MAC, сделанную на провайдерском сервере. Поясню на примере. Допустим, по некоторым причинам (это может быть кривой биллинг, отсутствие непосредственного доступа к компьютеру — из сейфа, ключи от которого неоднократно потеряны, торчит только шнурок и т.д.) у нас нет возможности заменить на шлюзе, который выпускает в Сеть мириады клиентов, привязанную на сервере провайдера сетевую карту и/или модифицировать правила файрвола. Нужно в кратчайшие сроки заблокировать чрезвычайно настойчивых спаммеров, прикрыть несколько порносайтов и (не)привилегированных портов. Возможно, тебе знакома эта ситуация. Решение сводится к установке между сервером прова и шлюзом компании транспарентного бриджа, разруливающего пакетами в соответствии с поставленными задачами. Поскольку мост функционирует на канальном уровне, обеспечивая связь между сетевым ПО и сетевыми картами, его работа не зависит от протоколов, соответственно, привязка обходится с невероятной легкостью.

INFO

Чтобы обмануть p0f, можно поэкспериментировать с директивой scrub и переменными sysctl: net.inet.tcp.sack, net.inet.ip.ttl, net.inet.tcp.sendspace, net.inet.tcp.recvspace.

DANGER

В настоящее время на всех видах бриджей правила pf "return-icmp" и "return-icmp6" для заблокированных пакетов не работают.

Обновлено: 13.03.2015