id: Гость   вход   регистрация
текущее время 07:12 20/08/2019
создать
просмотр
редакции
ссылки

Policy-based-фильтрация с помощью iptables


Этот документ и связанные с ним идеи ранее обсуждались в некоторых постах/тредах [1], [2], [3], [4], [5], [6]. По умолчанию здесь предполагается, что используемя система — последний релиз стабильного Debian'а.


Введение


Попытка написать сколь-нибудь сложные правила iptables сразу сталкивается со следующими трудностями:


  • iptables — не «policy-based»-фильтр. Свою «policy» нужно реализовывать самому. Например, для того, чтобы один клиент на lo общался с каким-то демоном на lo, нужно написать четыре правила: два на INPUT и два на OUTPUT. Если коммуникация идёт с сервером в сети, то тоже нужны минимум два правила: одно для OUTPUT и одно для INPUT. Действительно, нам очень редко нужен случай «мы хотим посылать пакеты на заданный хост, но не хотим никогда получать от него что-либо обратно». Одним словом, почти 100% всего, что когда-либо кому-либо понадобится в плане реальной фильтрации, будет сопряжено с бессмысленным механическим дубляжом правил (с минимальными исправлениями), разрешающих обратные ответы.

  • Другая проблема — читаемость правил. Опций много, они длинные, порядок следования свободный, перед многими опциями нужно много раз указывать бессмысленные -m tcp. Зачем? Хочется всю эту шелуху упрятать куда-то внутрь, чтобы осталось только то, что несёт информационную нагрузку.

Чтобы не писать кишки, я начал с того, что написал алиасы. Было решено не изобретать велосипед, а взять слова из того словаря, который уже используется в pf, и к которому многие, владеющие pf, привыкли. PF является именно «policy based»-фильтром, поэтому возникло желание писать так, как это пишется в PF, а скрипт чтобы всё превращал в iptables-правила. Эта задача была бы неподъёмно сложной, поэтому я был вынужден обойтись полумерой, при которой при внешне похожих правилах их семантическое значение нередко сильно отличается. У такого подхода есть и плюсы: в отличие от программы с нетривиальной логикой, которую нелегко проанализировать постороннему на предмет ошибок и утечек, в этом подходе очень легко понять, какие алиасы и функции каким iptables-правилам соответствуют. С учётом того, что при прочих раных простота способствует безопасности, это имеет свои плюсы.


Алиасы и функции


Чтобы сделать настоящие pass in/out on, одних алиасов было бы недостаточно, поэтому в скрипте $pass_out играет просто роль разрешающего правила iptables без каких-либо keep state.1 По смыслу к настоящему pass out в скрипте ближе $allow_out (аналогично для $allow_in).


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

X.X.X.X:PORT1
X.X.X.X:PORT2
X.X.X.X:PORT3
Допустимы комментарии и пустые строки — они будут выкинуты парсером.


Помимо multi-функций есть частичная поддержка ipset-списков. В частности, скрипт ipset_create_DB.sh используется для создания ipset-списков Tor'овских нод и их портов. Этот скрипт должен быть выполнен до запуска нижеприведённого iptables-скрипта, причём предполагается, что системный Tor уже работал в системе и успел создать файлы Tor-статистики в /var. Tor'у в iptables-скрипте можно указывать конкретные типы Tor-нод, с которыми ему разрешено соединяться.


Часто приходится для одного и того же пользователя писать как allow_in, так и allow_out-правило при фильтрации на lo, поэтому была создана ещё одна функция, lh_filter, которая объединяет их функционал.


Для Tor'а требуются многократные идентичные allow_in, отличающиеся только номерами портов и содержащие хак для user=root,2 поэтому для удобства все такие листинги были объединены в одну функцию allow_in_tor с простым синтаксисом. Аналогично функции allow_in_tor Tor'овская функция allow_out тоже была переделана в более функциональную allow_out_tor.


Правило allow_dns разрешает DNS-трафик для конкретного пользователя. Есть ещё несколько экспериментальных функций, которые нет смысла здесь описывать, т.к. они использовались только для тестов. Синтаксис большинства правил предельно прост. Например:

allow_out <интерфейс> <протокол> <src IP> <dst IP> <порт> <пользователь>
allow_out_tor <интерфейс> <src IP> <dst IP> <список портов> <пользователь>
allow_in <интерфейс> <протокол> <src IP> <dst IP> <порт> <пользователь>
allow_in_tor <интерфейс> <src IP> <dst IP> <список портов> <пользователь>
Вообще, мне кажется, что если продуманно писать свой DSL поверх iptables, вы просто изобретёте pf в той или иной его ипостаси — ничего иного получиться не может. Порядок следования параметров в allow-функциях такой же, как в PF, за исключением выкидывания ненужных служебных слов, упрощающих чтение и делающих правила более короткими/удобными (в конкретном случае), а также лишних параметров, которые обычно не нужны.


На данный момент правила никак не касаются перенаправлений, маскарадинга и прочих специфических случаев: во-первых, я с ними никогда не разбирался; во-вторых, не факт, что их можно легко представить как нечто pf-подобное. Даже при добавлении логирования пакетов особенности того, что iptables — не pf, сильно вылазят наружу, их уже легко не спрячешь.


iptables-скрипт


Предполагается, что скрипт-конвертор pf2iptables.sh, а также ipset-скрипт ipset_create_DB.sh с создаваемым им файлом ipset_DB.txt находятся в той же директории, что и нижеприведённый iptables-скрипт. Ниже приведён конкретный его пример, который следует отредактировать под свой случай.

#!/bin/sh
 
#-------------------------------
# Этот скрипт делает всё хорошо
#-------------------------------
 
## Раскомментируйте, если сделали ошибку в скрипте, и хочется его отладить:
#set -x
 
################################################################################
# Настройки сети
################################################################################
 
## IP, сетевой интерфейс и gateway вычисляются автоматически:
#eIF=$(ifconfig -a |sed '2,$d;s/[[:space:]].*$//') # eth output interface
#eIP=$(ifconfig $eIF |grep 'inet addr:' |sed 's/^.*addr:\([^ ]*\) \(.*\)/\1/')
## eIF = ethernet InterFace, eIP = ethernet IP.
## Аналогичным образом определяются параметры для wifi-интерфейсов (например,
## wIF и wIP).
 
# Если используется только один ethernet-интерфейс, проще его задать вручную:
oIF=eth0
# Текущий IP вычисляется автоматически:
oIP=$(ifconfig $oIF |grep 'inet addr:' |sed 's/^.*addr:\([^ ]*\) \(.*\)/\1/')
 
## Аналогичным образом можно узнавать свой gateway (предполагается, что
## последнее число его IP-адреса -- единица):
#GW=$(echo $oIP |sed 's/\(^.*\)\.\(.*$\)/\1.1/') # My gateway
#arp -s $GW XX:XX:XX:XX:XX:XX
 
# "Глобальные" переменные:
lh=127.0.0.1
az=0.0.0.0
a255=255.255.255.255
 
################################################################################
# Настройки DNS
################################################################################
 
## См. функцию allow_dns в pf2iptables.sh, которую следует отредактировать под
## свой случай. Если нужно, здесь можно указать свои DNS-сервера, но если весь
## трафик идёт через Tor, в этом нет необходимости.
 
#DNS1=X.X.X.X
#DNS2=X.X.X.X
 
#DNS_local_1=X.X.X.X
#DNS_local_2=X.X.X.X
#DNS_inet_1=X.X.X.X
#DNS_inet_2=X.X.X.X
 
## Если этот скрипт используется для разных сетей (к примеру, домашей и рабочей),
## где используемые IP-адреса на исходящих интерфейсах разные, можно
## автоматически получать правильные настройки DNS, опираясь на сетевой адрес,
## заранее назначенный интерфейсу (отредактируйте функцию allow_dns под свой
## случай):
 
#xIP=X.X.X.X
#if [ $oIP = $xIP ]; then
#    # DNS IP на работе:
#    DNS1=X.X.X.X
#    DNS2=Y.Y.Y.Y
#else
#    # DNS IP дома:
#    DNS1=Z.Z.Z.Z
#    DNS2=W.W.W.W
#fi
 
################################################################################
# Инициализация (сброс всех настроек и выставление запрещающей policy)
################################################################################
 
# Переключить в 'on', если нужен список правил на выходе без их загрузки.
dry_run=off
 
. ./pf2iptables.sh
 
clear_all
block_all
 
## Здесь мы могли бы сразу заблокировать весь UDP-трафик (см. функцию block_all),
## если он не нужен, но лучше сначала его записать в логи, и только затем
## заблокировать (см. правила логирования и блокирования в конце этого скрипта):
 
#$block_out $proto udp
 
################################################################################
# Загрузка ipset-списков Tor-нод
################################################################################
 
# Здесь мы получаем нужные IP и порты Tor-нод, чтобы ниже фильтровать по ним
# трафик Tor'а. Не забывайте время от времени выполнять скрипт
# ./ipset_create_DB.sh, который обновляет базу данных Tor-нод (по локальной
# Tor-статистике) в памяти ipset'а и в файле ipset_DB.txt.
 
oIP_prefix=$(echo $oIP |sed 's/^\(.*\)\.\(.*\)$/\1/')
 
if grep $oIP_prefix ipset_DB.txt > /dev/null ; then 
    # Это может быть признаком атаки. Такая нода не должна использоваться нами
    # при построении Tor-цепочек.
    echo Warning: IP from our subnet is in the ipset list
fi
 
# Удаляем все ipset-списки вместе с их содержимым из памяти и создаём новые
# списки, используя базу данных из заранее подготовленного файла ipset_DB.txt:
 
ipset x
ipset restore -file ipset_DB.txt
 
################################################################################
# Правила для пользователя torbrowser_user
################################################################################
 
# Разрешаем соединение с системным Tor'ом для Tor Browser'а:
allow_out lo tcp $lh $lh 9150 torbrowser_user
 
# Чтобы не засорять лог-файлы, сразу заблокируем обращения Tor Browser'а
# к управляющему порту Tor'а (ControlPort, man torrc):
$block_out $on_o lo $proto tcp $from $lh $to $lh $dt_port 9151 \
  $user torbrowser_user
 
# Разрешаем соединение с системным Tor'ом (используя другие Tor-цепочки) для
# тулзов типа wget, которые могут могут быть нужны (под этим же пользователем):
allow_out lo tcp $lh $lh 9510 torbrowser_user
 
##------------------------------------------------------------------
## Использование Tor Browser'а с локальным Tor'ом (небезопасно):
##------------------------------------------------------------------
 
## Если Tor Browser используется с локальным Tor'ом, iptables мало
## чем может помочь. Однако, этот вариант может быть сделан немного
## более безопасным, если разрешить Tor'у соединяться только с
## определёнными заданными guard-нодами, которые в данный момент ему
## нужны:
 
#multi_allow_out $oIF tcp $oIP guard_list_file torbrowser_user
 
## Т.е. вышеприведённое правило лучше, чем разрешать вообще весь
## TCP-трафик в Интернет:
 
#allow_out $oIF tcp $oIP all 0:65535 torbrowser_user
 
## В случае локального Tor'а также требуется разрешить
## torbrowser_user'у входящие и исходящие соединения на
## loopback-интерфейсе:
 
#lh_filter 9150:9151 torbrowser_user
 
## Множество пакетов ошибочно блокируются функцией lh_filter по
## неизвестной причине (проблема только с Tor'ом; другие сервисы --
## например, ssh, работают нормально). Если понизить требования к
## безопасности, некоторые из этих ошибочно блокируемых пакетов
## могут быть разрешены следующими правилами, но только некоторые
## (часть пакетов продолжит блокироваться):
 
#tor_lh_filter 9150:9151 torbrowser_user
#bug_workaround
 
## Поскольку Tor и другие программы работают нормально со
## стандартным правилом lh_filter (несмотря на блокируемые пакеты),
## лучше не использовать вышеприведённые функции tor_lh_filter и
## bug_workaround. Однако, эти функции могут быть полезны (см.
## скрипт pf2iptables.sh) для анализа проблемы и нахождения её
## причины.
 
################################################################################
# Правила для основного пользователя "main_user"
################################################################################
 
# Разрешить использовать SOCKS-порт (40000) SSH-соединения, запускаемого
# от имени main_user'а:
lh_filter 40000 main_user
 
## Если используется системная privoxy, разрешить соединение с ней:
#allow_out lo tcp $lh $lh 8118 main_user
 
# Разрешить соединение с mpd-сервером, который запущен от имени пользователя
# music_user (правило allow_out здесь было бы избыточным, потому что
# для пользователя music_user используется lh_filter, см. ниже):
$pass_out $on_o lo $proto tcp $from $lh $to $lh $dt_port 6600 $user main_user
 
# Объяснение: коммуникация между клиентом и сервером на "lo" требует 4
# правила: 2 входящих и 2 исходящих. Все они уже добавлены через lh_filter
# (клиент и сервер mpd запускаются, в том числе, под пользователем
# music_user).
#
# Мнемоническое правило: lh_filter для пользователя music_user = allow_out для
# music_user + allow_in для music_user. allow_in уже указано для music_user.
# Следовательно, нам нужна только функция allow_out для пользователя
# main_user.  Кроме того, allow_out = $pass_out + $pass_in. Однако, поскольку
# iptables не поддерживает опцию "owner" для входящих пакетов, нужное правило
# $pass_in уже добавлено в список. Т.е., нам нужно добавить только $pass_out.
# Иными словами говоря, если $pass_in указан для одного пользователя
# посредством lh_filter, он же добавлено и для всех других пользователей --
# для них остаётся добавить только $pass_out. Если вместо $pass_out мы напишем
# allow_out, одно и то же фильтрующее правило iptables будет указано в списке
# iptables-save дважды.
 
# Разрешаем соединение с системным Tor'ом через порты на "lo" (набор разных
# портов нужен, чтобы использовать разные Tor-цепочки в разных программах):
allow_out_tor lo $lh $lh 9070:9072 9099 main_user
 
# Не показывать в логах UDP-пакеты, автоматически генерируемые mail-программой,
# когда приходит новое письмо. Сразу блокируем такие пакеты, чтобы не засорять
# ими dmesg:
$block_out $on_o lo $proto udp $from $lh $to $lh $du_port 512 $user main_user 
## Если нужно, эти пакеты могут быть отдельно залогированы ниже следующим
## правилом:
#$match_out_log $log_label "mail UDP packets: " \
#  $on_o lo $proto udp $from $lh $to $lh $du_port 512 $user main_user 
 
##------------------------------------------------------------------
## Правила для прямого соединения main_user'а с Интернетом (опасно)
##------------------------------------------------------------------
 
## Скорей всего, если ваши настройки безопасные, вам не понадобятся
## эти правила.
 
## Разрешить DNS-запросы (DNS-порты и DNS-сервера фиксированы
## функцией allow_dns):
#allow_dns main_user
 
## Разрешить весь TCP-трафик в Интернет:
#allow_out $oIF tcp $oIP all 0:65535 main_user
## но лучше его ограничить более специфичными правилами (см. ниже).
 
## Разрешить соединение с конкретным списком хостов. Каждый список
## имеет формат "host:port" (один на строку), причём возможны
## комментарии и пустые строки:
#multi_allow_out $oIF tcp $oIP host_and_port_list_1 main_user
#multi_allow_out $oIF tcp $oIP host_and_port_list_2 main_user
 
## Разрешить соединение с конкретными SSH-серверами (задаются 
## по IP):
#allow_out $oIF tcp $oIP X.X.X.X 22 main_user
#allow_out $oIF tcp $oIP Y.Y.Y.Y 22 main_user
 
## Разрешить соединение с конкретными XMPP-серверами (задаются 
## по IP):
#allow_out $oIF tcp $oIP X.X.X.X 5222 main_user
#allow_out $oIF tcp $oIP Y.Y.Y.Y 5223 main_user
 
################################################################################
# Правила для пользователя debian-tor (loopback-интерфейс)
################################################################################
 
# Разрешаем Tor'у принимать соединения от других пользователей на
# loopback-интерфейсе [порты, разделённые пробелом, будут в разных правилах
# iptables, что позволяет отслеживать счётчики пакетов (команда "iptabels-save
# -c") по каждому из портов или их списков отдельно]:
 
allow_in_tor lo $lh $lh 9150 9510 9070:9072 9080 9099 debian-tor
 
##------------------------------------------------------------------
## DNS-резолвинг через Tor (если какой-то программе требуется):
##------------------------------------------------------------------
 
## В норме DNS не требуется, поскольку там, где это нужно,
## DNS-резолвинг происходит на exit-нодах Tor'а автоматически.
## Однако, если какой-то программе всё же нужен локальный
## DNS-резолвинг, можно разрешить его делать через Tor [не забудьте
## указать опцию DNSPort в torrc, добавить "nameserver 127.0.0.1" в
## resolv.conf и указать нужное allow_out-правило для того
## пользователя, которому нужен DNS (см. ниже пример правила для
## пользователя "userX")]:
 
#allow_in lo udp $lh $lh 53 debian-tor
 
## Некоторые пакеты ошибочно получают root'а в качестве owner'а,
## поэтому для работы DNS следующее правило также необходимо:
 
#$pass_out $on_o lo $proto udp $from $lh $su_port 53 $to $lh \
#  $user root $keep_state
 
################################################################################
# Правила для пользователя debian-tor (ethernet-интерфейс)
################################################################################
 
## Нужно позволить Tor'у соединяться с Интернетом. Мы можем разрешить все хосты:
 
#allow_out $oIF tcp $oIP all 0:65535 debian-tor
 
## но лучше ограничить список разрешённых хостов только (какими-то) Tor-нодами.
## Ниже предложены правила для двух типов настроек: ipset (все Tor-ноды
## разрешены) и фиксированные guard-ноды (разрешены только некоторые Tor-ноды из
## имеющих guard-флаг). Вариант, разрешённый в скрипте по умолчанию -- ipset.
 
#------------------------------------------------------------------
# Метод "ipset" (трафик разрешён ко всем Tor-нодам):
#------------------------------------------------------------------
 
# Требуется установленный ipset (см. выше), чтобы использовать
# списки Tor-нод в правилах фильтрации.  В норме Tor соединяется
# только с портами ORPort (см. man torrc) тех нод, которые имеют
# guard-флаг. Однако, при первом запуске Tor может соединяться с
# портами ORPort и других нод.  Существует иной порт, DirPort (см.
# man torrc), используемый Tor'ом для получения статистики. Пока
# непонятно, может ли Tor-клиент коннектиться к портам DirPort
# каких-либо нод напрямую, но такая возможно разрешена здесь
# используемыми правилами фильтрации.  Таким образом, у нас есть
# два типа портов и два типа Tor-нод (с guard-флагом и без него) --
# вместе получается 4 различных варианта списков ipset, все из
# которых могут быть обновлены вручную с помощью скрипта
# ipset_create_DB.sh, который использует файлы Tor-консенсуса в
# /var. В списках ipset фиксированы как IP, так и порты Tor-нод.
 
# Разрешить соединение с портами ORPort нод, имеющих guard-флаг:
allow_out_ipset $oIF tcp $oIP GuardORPort debian-tor
# Разрешить соединение с портами ORPort нод без guard-флага:
allow_out_ipset $oIF tcp $oIP NonGuardORPort debian-tor
 
# Разрешить соединение с портами DirPort нод, имеющих guard-флаг:
allow_out_ipset $oIF tcp $oIP GuardDirPort debian-tor
# Разрешить соединение с портами DirPort нод без guard-флага:
allow_out_ipset $oIF tcp $oIP NonGuardDirPort debian-tor
 
#------------------------------------------------------------------
# Проблема ошибочно блокируемых пакетов в методе "ipset":
#------------------------------------------------------------------
 
# После выполнения "/etc/init.d/tor stop" множество нужных
# Tor-пакетов блокируется, потому что они не имеют какого-либо
# owner'а (нам нужен owner = debian-tor). Формат блокируемых
# пакетов выглядит примерно так:
#
# SRC=$oIP DST=Tor_IP LEN=40 TOS=0x00 PREC=0x00 TTL=64 ID=XXXX DF
# PROTO=TCP SPT=XXXX DPT=Tor_port WINDOW=XXXX RES=0x00 ACK URGP=0
#
# Причина проблемы неизвестна. Поскольку нет желания логировать
# такие пакеты и засорять ими dmesg, мы их здесь же и заблокируем:
 
$block_out $on_o $oIF $proto tcp $from $oIP \
  $for_ipset GuardORPort $to_ipset $ipt_module tcp
 
# Нет понимания, нужно ли указывать "$ipt_module tcp" в
# вышеприведённой и других командах, но вариант с написанием
# встречается в некоторых статьях в сети.
#
# Списки ipset содержат как IP, так и порт для каждого хоста.
# Вышеприведённые правила (см. также функцию allow_out_ipset в
# скрипте pf2iptables.sh) работают и без указания "$ipt_module tcp"
# [однако, есть опасность, что порт хоста может игнорироваться в
# этом случае, т.е. все порты трактоваться как разрешённые -- я не
# проверял, так ли это].  Вообще, опция "$ipt_module tcp" (т.е. "-m
# tcp") нужна в обычных правилах iptables, когда требуется указать
# TCP-порт, но здесь TCP-порт указан в базе данных ipset-списка.
# Одним словом, непонятно, нужно ли продолжать указывать эту опцию
# в правилах с ipset.
 
## Нижеприведённое правило решило бы проблему ошибочно блокируемых
## пакетов, но оно слишком небезопасно, поскольку позволяет всем
## пользователям посылать некоторые пакеты (правда, возможно,
## только в определённых случаях) в Интернет напрямую:
 
#$pass_out $on_o $oIF $proto tcp $from $oIP \
#  $for_ipset GuardORPort $to_ipset $ipt_module tcp 
 
##------------------------------------------------------------------
## Метод "фиксированные guard'ы" (лишь некоторые guard'ы разрешены)
##------------------------------------------------------------------
 
## Если используются только какие-то конкретные guard-ноды, напрямую
## заданные в torrc-файле, можно разрешить Tor'у соединяться только
## с ними (требуется приготовить файл "guard_list_file" и положить 
## его в директорию, где находятся все скрипты):
 
#multi_allow_out $oIF tcp $oIP guard_list_file debian-tor
 
## В принципе, по аналогии с ipset-скриптом мы могли бы узнавать
## список текущих guard'ов, используемых Tor'ом, анализируя
## state-файл. Однако, если мы ограничим Tor соединениями только с
## ними, возникнут проблемы, когда Tor захочет обновить их список
## (что случается не так часто, но, тем не менее) и ошибочно
## посчитает новые выбираемые guard'ы нерабочими. Наверно, лучше не
## вмешиваться в поведение Tor'а таким жёстким образом, хотя более
## умные варианты фильтрации, позволяющие Tor'у обновлять списки
## guard'ов, могли бы использоваться. В частности, ipset позволяет
## динамически формировать и обновлять списки хостов по факту
## срабатывания нужных правил фильтрации/мачинга.
 
################################################################################
# Правила для пользователя root
################################################################################
 
# Разрешить соединение с Tor'ом на loopback-интерфейсе (к примеру, для apt-get):
allow_out lo tcp $lh $lh 9080 root
 
# Разрешить принимать соединения по SSH на loopback-интерфейсе (нужно
# пользователю ssh_user):
allow_in  lo tcp $lh $lh 22 root
 
##------------------------------------------------------------------
## Правила для прямого содиения root'а с Интернетом (небезопасно):
##------------------------------------------------------------------
 
## --- Интернет-соединение ---
 
## Разрешить соединение с Интернетом (все хосты по TCP и несколько
## заданных хостов для DNS):
 
#allow_out $oIF tcp $oIP all 0:65535 root
#allow_dns root
 
## ------ DHCP--запросы ------
 
## Возможно, эти правила имеют смысл для каких-то старых систем, но
## в современных системах DHCP-запросы и ответы вообще не
## фильтруются посредством iptables:
 
#dhcp_server=X.X.X.X
#allow_dhcp $dhcp_server
 
## Это почти точно не нужно:
 
## My IP:
#mIP=Y.Y.Y.Y
#$pass_in $on_i $oIF $proto udp $from $dhcp_server $su_port 67 \
#    $to $mIP $du_port 68 $keep_state 
 
################################################################################
# Правила для пользователя userX
################################################################################
 
# Разрешить соединение с SOCKS-сервером SSH'а, запускаемого от имени
# пользователя main_user [поскольку правило для него использует функцию
# lh_filter (см. выше), здесь достаточно указать $pass_out вместо allow_out
# (см. пояснения выше)]:
 
$pass_out $on_o lo $proto tcp $from $lh $to $lh $dt_port 40000 $user userX
 
## Если это требуется для каких-то программ, можно разрешить пользователю userX
## делать DNS-резолвинг через Tor:
#allow_out lo udp $lh $lh 53 userX
 
## Можно разрешить соединение с privoxy, если нужно:
#allow_out lo tcp $lh $lh 8118 userX
 
##------------------------------------------------------------------
## Прямое соединение пользователя userX с Интернетом (опасно)
##------------------------------------------------------------------
 
## Этого должно быть достаточно:
 
#allow_dns userX
#allow_out $oIF tcp $oIP all 0:65535 userX
 
## Лучше не позволять никакому пользователю (помимо debian-tor)
## напрямую соединяться с Интернетом во время нормальной работы
## системы (при множестве залогиненных пользователей и т.д.). В тех
## редких случаях, когда прямое соединение допустимо и необходимо,
## лучше использовать не этот iptables-скрипт, а отдельный простой,
## специально заточенный под эту задачу. Стоит отметить, что
## перезагрузка правил iptables -- тоже не безопасная процедура при
## залогиненных пользователях.
 
################################################################################
# Правила для других пользователей
################################################################################
 
# Пользователю "music_user" разрешено быть клиентом и сервером для сервиса mpd:
lh_filter 6600 music_user
 
# Пользователю "ssh_user" разрешаем получать root'а по SSH на
# loopback-интерфейсе:
allow_out lo tcp $lh $lh 22 ssh_user
 
################################################################################
# Неиспользуемые пользователи
################################################################################
 
## ------ privoxy ------
 
## Если используется privoxy, разрешаем ей принимать соединения на "lo":
#allow_in lo tcp $lh $lh 8118 privoxy
 
## Если privoxy использует Tor как parent proxy с определённым портом
## "SOME_Tor_PORT", разрешаем ей коннектиться на этот порт:
#allow_out lo tcp $lh $lh SOME_Tor_PORT privoxy
 
## Если privoxy нужно соединяться с SSH SOCKS-прокси (запущенной от имени
## main_user) как с parent proxy, разрешаем это (используется $pass_out вместо
## allow_out, потому что порт 40000 был добавлен посредством правила lh_filter):
#$pass_out $on_o lo $proto tcp $from $lh $to $lh $dt_port 40000 $user privoxy
 
## ------- userY -------
 
## Пользователю userY разрешаем прямой доступ в Интернет (всё TCP, но UDP только
## для DNS):
#allow_dns userY
#allow_out $oIF tcp $oIP all 0:65535 userY
 
################################################################################
# Блокирование и логирование всех остальных соединений
################################################################################
 
# Множество Tor-пакетов ошибочно блокируются на lo-интерфейсе, потому что они
# потеряли параметр "owner" и не подпадают под состояние TCP-сессии (state
# ESTABLISHED). Пакеты имеют вид:
# 
# IN= OUT=lo SRC=$lh DST=$lh LEN=40 TOS=0x00 PREC=0x00 TTL=64 ID=XXXXX DF
# PROTO=TCP SPT=XXXXX DPT=Tor_Port WINDOW=XXXX RES=0x00 ACK FIN URGP=0 
#
# IN= OUT=lo SRC=$lh DST=$lh LEN=40 TOS=0x00 PREC=0x00 TTL=64 ID=XXXX DF
# PROTO=TCP SPT=Tor_Port DPT=XXXXX WINDOW=XXX RES=0x00 ACK FIN URGP=0 
#
# Чтобы не засорять ими dmesg, мы блокируем их до попадания в лог (аналогично
# для SSH, mpd и др. сервисов):
 
block_bug_mports lo $lh $lh 9150 9510 9070:9072 9080 9099 22 6600 40000
 
# Прыгаем в LOGGING-цепочку iptables'а:
start_logging
 
    # ------------------------------------------------------------------------
    # Если некоторые Tor-пакеты на ethernet-интерфейсе будут блокироваться,
    # здесь мы сможем проанализировать причину, логируя их в dmesg'е (пока что
    # ошибочных блокирований не замечено):
 
    #for IPSET in GuardORPort GuardDirPort NonGuardORPort NonGuardDirPort ; do
    #    for SRC_USER in debian-tor root ; do
    #        $match_out_log $log_label "$IPSET $SRC_USER: " \
    #            $on_o $oIF $proto tcp $from $oIP \
    #            $for_ipset $IPSET $to_ipset $ipt_module tcp $user $SRC_USER
    #    done
    #done
 
    #for IPSET in GuardORPort GuardDirPort NonGuardORPort NonGuardDirPort ; do
    #    $match_out_log $log_label "$IPSET no_user: " \
    #        $on_o $oIF $proto tcp $from $oIP \
    #        $for_ipset $IPSET $to_ipset $ipt_module tcp
    #done
    # ------------------------------------------------------------------------
 
    # Логируем все пакеты, которые достигли этой стадии (ещё не были разрешены
    # или заблокированы). Используем метки, чтобы указать на направление, 
    # интерфейс и протокол (например, "dmesg |grep 'out,eth,TCP'" покажет
    # список записанных в лог исходящих TCP-пакетов на ethernet-интерфейсе):
 
    # Пакеты на loopback-интерфейсе:
 
    log_out 'out,lo,TCP:'  lo tcp  
    log_out 'out,lo,UDP:'  lo udp  
    log_out 'out,lo,ICMP:' lo icmp 
    log_out 'out,lo,GEN:'  lo
 
    log_in  'in,lo,TCP:'   lo tcp  $lh
    log_in  'in,lo,UDP:'   lo udp  $lh
    log_in  'in,lo,ICMP:'  lo icmp $lh
    log_in  'in,lo,GEN:'   lo $lh
    log_in                 lo
 
    # Пакеты на ethernet-интерфейсе:
 
    log_out 'out,eth,TCP:'  $oIF tcp  
    log_out 'out,eth,UDP:'  $oIF udp  
    log_out 'out,eth,ICMP:' $oIF icmp 
    log_out 'out,eth,GEN:'  $oIF
 
    log_in  'in,eth,TCP:'   $oIF tcp  $oIP
    log_in  'in,eth,UDP:'   $oIF udp  $oIP
    log_in  'in,eth,ICMP:'  $oIF icmp $oIP
    log_in  'in,eth,GEN:'   $oIF $oIP
    log_in                  $oIF
 
# Наконец, блокируем все записанные в лог пакеты:
$block_log
 
# В этих правилах нет необходимости, потому что на этой стадии все пакеты уже
# были разрешены или заблокированы (или залогированы и потом заблокированы),
# но для гарантии лучше оставить эти правила здесь, в конце [если в процессе
# редактирования скрипт(а,ов) где-то будет допущена фатальная ошибка, эти
# правила станут последним эшелоном, который может уменьшить утечку]:
 
$block_out $on_o $oIF $proto tcp
$block_out $on_o $oIF $proto udp
$block_out $on_o $oIF $proto icmp
 
# EOF
В конфиге много закомментированного для разных случаев, стандартная шапка и концовка, но, в принципе, он понятен. Чтобы подчеркнуть краткость, отдельно приведу информативные правила, которые незакомментированы:
allow_out lo tcp $lh $lh 9150 torbrowser_user
$block_out $on_o lo $proto tcp $from $lh $to $lh $dt_port 9151 \
  $user torbrowser_user
allow_out lo tcp $lh $lh 9510 torbrowser_user
lh_filter 40000 main_user
$pass_out $on_o lo $proto tcp $from $lh $to $lh $dt_port 6600 $user main_user
allow_out_tor lo $lh $lh 9070:9072 9099 main_user
$block_out $on_o lo $proto udp $from $lh $to $lh $du_port 512 $user main_user 
allow_in_tor lo $lh $lh 9150 9510 9070:9072 9080 9099 debian-tor
allow_out_ipset $oIF tcp $oIP GuardORPort debian-tor
allow_out_ipset $oIF tcp $oIP NonGuardORPort debian-tor
allow_out_ipset $oIF tcp $oIP GuardDirPort debian-tor
allow_out_ipset $oIF tcp $oIP NonGuardDirPort debian-tor
$block_out $on_o $oIF $proto tcp $from $oIP \
  $for_ipset GuardORPort $to_ipset $ipt_module tcp
allow_out lo tcp $lh $lh 9080 root
allow_in  lo tcp $lh $lh 22 root
$pass_out $on_o lo $proto tcp $from $lh $to $lh $dt_port 40000 $user userX
lh_filter 6600 music_user
allow_out lo tcp $lh $lh 22 ssh_user
block_bug_mports lo $lh $lh 9150 9510 9070:9072 9080 9099 22 6600 40000
Эти 19 строк по сути и составляют правила файерволла в более-менее том виде, в каком хотелось их видеть. Это реальные правила с реальной системы, на которой работет множество сервисов/программ. Анализировать и редактировать такие строки намного проще, чем исходные «асемблерные» кишки iptables. Для сравнения, вывод iptables-save для этих правил даёт около 100 строк, что в 5 раз больше. Проверить отсутствие дубляжа в правилах можно командой iptables-save |sort | uniq -d: если возвращается только перевод строки, значит, дублей в правилах нет.


Буду благодарен за критику и замечания.



1 В современных версиях PF настоящий pass out подразумеает keep state, т.е. разрешение обратных пакетов-ответов.
2 Часть пакетов проходит как принадлежащие debian-tor, как и должно быть, а другая часть — почему-то как принадлежащая root. Должен заметить, что в BSD тоже есть такие ошибки.


 
— unknown (20/02/2015 11:58, исправлен 20/02/2015 12:07)   профиль/связь   <#>
комментариев: 9796   документов: 488   редакций: 5664

Неконструктивные, обобщённые и прочие (не)нужные коменты перенесены в отдельную тему.

— Гость (08/03/2015 18:16)   <#>
По аналогии с allow_in_tor можно было бы написать allow_out_tor, потому что часто нужно много однотипных правил, отличающихся только портами.

multi_allow_out чем-то похожа на allow_out_tor, это можно было бы чётче отразить на синтаксисе. Из allow_in_tor убран аргумент протокола в пользу большей краткости (у меня пока нет udp-трафка на хосте), но в ущерб универсальности. Если делать универсально, то нужно его добавлять. Потом, есть идея «размножения правила» по одному из аргументов — либо по портам (как это сделано в allow_in_tor) либо по списку dst hosts (как это сделано в multi_allow_out). Это можно описать в виде функций allow_(in,out)_mports и allow_(in,out)_mhosts (что-то типа такого). В этой идеологии allow_in_tor была бы разновидностью allow_in_mports, отличающейся от неё тем, что содержала бы хак с uid-owner=root.

multi_allow_out сейчас завязана на внешние файлы-листинги со списками host:port, а можно было бы их сделать на месте через переменную вида hosts=HOST1:PORT1,HOST2:PORT2,... или просто через пробел указывать в самом вызове функции multi_allow_out (как это сейчас сделано для списка портов в allow_in_tor).

В общем, всё это можно пообдумывать и попричёсывать, но золотое правило — «работает — не трогай». Можно разве что allow_out_tor добавить, это просто и логично делается поверх того, что уже есть — достаточно одну простую функцию дописать.
— Гость (08/03/2015 20:06)   <#>

Сделано. Параллельно было замечено, что в allow_in_tor перед переменными не было указано local, теперь исправлено. Также был найден баг в функции multi_allow_out — она подставляла фиксированные аргументы вместо тех, с которыми вызывается функция (исправлено). dstPORT в allow_in_tor и allow_out_tor теперь тоже local-переменная.

В OP этой страницы в пояснениях к использованию по ошибке в allow_in_tor в одном месте был указан интерфейс (реально он фиксирован и есть tcp) — тоже исправлено. Сам OP-документ подправлен с учётом новой введённой функции allow_out_tor.
— Гость (10/03/2015 22:48)   <#>

Если хочется видеть детальную статистику по каждому из портов через iptables-save -c, нужно писать так:

allow_out_tor lo tcp $lh $lh 9070 9071 9072 9099 main_user

тогда он не будет объединять диапазон портов в одно iptables-правило.
— pgprubot (13/06/2015 23:33, исправлен 13/06/2015 23:43)   профиль/связь   <#>
комментариев: 511   документов: 2   редакций: 70

При похожих правилах iptables обнаружил в логах среди заблокированных крайне странный ICMP-пакет:


IN= OUT=eth0 SRC=MY_IP DST=SOME_IP LEN=576 TOS=0x14 PREC=0xC0 TTL=64 ID=7385 PROTO=ICMP TYPE=11 CODE=1 [SRC=SOME_IP DST=MY_IP LEN=1500 TOS=0x14 PREC=0x00 TTL=50 ID=XXXXX MF PROTO=UDP SPT=53 DPT=53 LEN=XXXX]

Казалось бы, если весь UDP- и ICMP-трафик забанен, никакие UDP/ICMP-пакеты система сама ни с того ни с сего в ответ на сканирования посылать не должна, но, видимо, всё намного тоньше.


То, что в квадратных скобках — информация о том пакете, который вызвал ответный ICMP. Сначала подумал на хитрые способы обхода NAT [1], [2], но дело, похоже, в другом.


ICMP TYPE 11 CODE 1 — это «Fragment Reassembly Time Exceeded». Т.е. был послан фрагментированный DNS-пакет, но фрагменты не собрались. ОС выслала в ответ ICMP TYPE 11 CODE 1, как и положено. Возможно, это было сканирование на предмет уязвимости к DNS amplification attack.


Т.е. iptables сидит после уровня IP reassembly, он проверяет уже собранные пакеты. UDP-пакет сначала собирается из IP-пакетов, затем попадает на iptables, где и дропается, но ICMP-ответ о невозможности сборки пакета стоит ниже iptables.


Применение такому сканированию — своеобразный пинг на хост, защита — блокировка OUTPUT [жертве посылается неполный пакет из IP-фрагментов, ядро не сможет его собрать и отправит ICMP-ответ (до iptables этот пакет даже не дойдет)].


Документация догадки, похоже, подтверждает:


Specifying Fragments

Sometimes a packet is too large to fit down a wire all at once. When this happens, the packet is divided into fragments, and sent as multiple packets. The other end reassembles these fragments to reconstruct the whole packet.

The problem with fragments is that the initial fragment has the complete header fields (IP + TCP, UDP and ICMP) to examine, but subsequent packets only have a subset of the headers (IP without the additional protocol fields). Thus looking inside subsequent fragments for protocol headers (such as is done by the TCP, UDP and ICMP extensions) is not possible.

If you are doing connection tracking or NAT, then all fragments will get merged back together before they reach the packet filtering code, so you need never worry about fragments.

Please also note that in the INPUT chain of the filter table (or any other table hooking into the NF_IP_LOCAL_IN hook) is traversed after defragmentation of the core IP stack.

Otherwise, it is important to understand how fragments get treated by the filtering rules. Any filtering rule that asks for information we don't have will not match. This means that the first fragment is treated like any other packet. Second and further fragments won't be. Thus a rule -p TCP --sport www (specifying a source port of `www') will never match a fragment (other than the first fragment). Neither will the opposite rule -p TCP --sport! www.

However, you can specify a rule specifically for second and further fragments, using the `-f' (or `--fragment') flag. It is also legal to specify that a rule does not apply to second and further fragments, by preceding the `-f' with ` ! '.

Usually it is regarded as safe to let second and further fragments through, since filtering will effect the first fragment, and thus prevent reassembly on the target host; however, bugs have been known to allow crashing of machines simply by sending fragments. Your call.

Т.е. цепочка INPUT проходится после дефрагментации пакета (cами фрагменты могут быть перепутаны местами, первый фрагмент может не содержать заголовка протокола следующего уровня, фрагменты могут быть посланы в произвольном порядке и даже с перекрытием).


Кстати, PF работает до TCP/IP-стэка (поэтому его и портировали под винду, он не зависит от ядра). В винде проще работать до стека, чем встраиваться внутрь, и сторонние файерволлы в большинстве своем работают до. Родной виндовый фаерволл работает внутри стека, и с ним те же самые проблемы, что с iptables. PF в винде действительно сам собирает фрагменты и выдает в стек уже целый пакет, после проверки это наиболее надежное решение, но тут дублируется функционал — сборка фрагментов есть 2 раза: в TCP/IP-стеке и в файерволле. В линуксе функционал, видимо, решили не дублировать.

— pgprubot (11/10/2015 18:48, исправлен 11/10/2015 19:11)   профиль/связь   <#>
комментариев: 511   документов: 2   редакций: 70

При использовании исходных правил в логи dmesg сыпалось множество ошибочно блокируемых Tor-пакетов на eth-интерфейсе. Среди этой массы можно было легко не заметить какую-то важную утечку. Первая мысль была — написать парсер, который проанализирует записи в логах и исключит из них те, которые относятся к Tor'у. Tor project официально предоставляет листинг Tor-нод, но это только exit-ноды, а завязываться на неофициальные листы нод тоже не хотелось. Последний вариант был — получать актуальные списки из кэша статистики самого Tor-клиента. К удивлению, их формат оказался не слишком сложным, поэтому было решено с помощью хитрых grep-сортировок создать списки нод с определёнными параметрами (Guard'ы, ноды с DirPort'ами и т.д.).


Речь идёт о списках в тысячи хостов, а это пограничный случай, когда правила iptables могут начать тормозить соединения, поэтому был выбран ipset.3 В Tor-faq пишут, что Tor-клиент может ходить на DirPort'ы и ORPort'ы нод, поэтому сортировка была по двум критериям: есть ли флаг guard, и какой тип порта используется — в итоге получается 4 списка.4 В обновлённых правилах сортировка исходящих Tor-пакетов идёт по этим спискам,5 поэтому было легко убрать из dmesg'а все лишние логируемые пакеты. Кстати, у ipset (см. ссылки в сноске) много интересных применений, упрощающих написание правил, даже в тех случаях, где хостов мало, и скорость не важна.


Поверхностный просмотр ipset-списков при отладке процедур выявил интересные факты. То, что на одном IP может быть запущено несколько Tor-нод (с разными портами и отпечатками) — это ещё ничего, — куда интересней, когда одна и та же нода с одними и теми же портами присутствует в списке дважды, отличаются только отпечатки

r Chiraq NWTaJ6ERrM5lbuI6BBff5KphFnM 2015-10-07 15:12:23 67.173.119.40 51256 0
m 7UK36ywxh2exaZeyf4ua8NtQw3C331yAN8rlediJVIw
s Running Valid
v Tor 0.2.4.27
 
r Chiraq LZGONTO0iQLu9ooyRn7WM9N2F3o 2015-10-07 15:32:08 67.173.119.40 51256 0
m 7VRupdW/G1xEZsnIiesPge7J++6Oih2K6SQRoOUkZjM
s Running Valid
v Tor 0.2.4.27
Разница всего в 20 минут. Это глюк? Странно, что DA подписали разные отпечатки для одной и той же ноды. Кстати, сейчас эта нода тоже в двойном экземпляре присутствует. Вот пример статистики здоровой Tor-ноды — все отпечатки одинаковые, а вот — той самой, Chiraq, все отпечатки разные. То, что она упомянута дважды, видно и в списке blutmagie, причём uptime — 1 час.


В текущем виде ограничение в виде фильтрации хостов по спискам Tor-нод (для Tor-клиента) мало что даёт помимо повышения информативности dmesg'а, который играет роль «IDS'а для бедных». Действительно, если Tor-клиент скомпрометирован, ему не составит труда добавить злонамеренные узлы в статистику, которая потом попадёт в списки ipset. Однако, Tor-статистику ipset-скриптом можно собирать с разных хостов и сравнивать — это уже интересней. Т.е., например, можно загружать статистику в память ipset'а только после соответствующих проверок и анализа.


Чем-то это всё очень напоминает corridor (но у него другие цели и масштабы), где, кстати, тоже используется ipset:


Да, можно дополнительно наворотить подстраховку через файерволл по типу corrdior, и это может быть дополнительной фенечкой к уже работающему Tor-роутеру, но никак не его заменой.

Именно это сейчас сделано через вышеописанный в конфиге «ipset-метод».


Подавать corridor как дополнительную фичу к уже работающему роутеру в качестве дополнительной защиты от уязвимостей самого Tor-клиента — уже другое дело, но и тут всё не очень хорошо с рекламой. Сейчас guard'ы живут долго, и в норме меняться не должны длительное время. Весь трафик в норме идёт только через guard'ы. А раз так, то вместо риска получить ещё одну дыру из-за чужих малоизученных костылей, куда проще прописать в файерволле разрешение трафика только на собственные guard'ы. Когда понадобиться через пару месяцев их сменить, процедуру можно будет повторить.

Этот вариант тоже поддерживается из коробки через multi-функции (метод «фиксированные guard'ы», указать их в конфиге можно так).


В случае ipset предполагается, что сначала пользователь выполняет (под root'ом) ipset-скрипт, а потом уже сам вышеприведённый iptables-скрипт. Все скрипты, конфиги и листы хостов должны находиться в одной директории.


Чтобы ipset работал, в pf2iptables-конвертер была добавлена функция allow_out_ipset с соответствующими алиасами. Ну, и вообще все эти документы были существенно переработаны, лучше задокументированы, и подчищен язык.



3 Источники вдохновения: [1], [2], [3] (not good).
4 Один и тот же хост может попасть в разные списки, но одинаковым будет только IP-адрес, порты будут разными (во всяком случае, в норме).
5 Прямых использований DirPort'ов ни в каких случаях пока не замечено.

— pgprubot (11/10/2015 19:51, исправлен 11/10/2015 19:54)   профиль/связь   <#>
комментариев: 511   документов: 2   редакций: 70

[offtop]
Дзен из man ipset:


For example if a and b are list:set type of sets then in the command

iptables -m set --match-set a src,dst -j SET --add-set b src,dst

the match and target will skip any set in a and b which stores data triples, but will match all sets with single or double data storage in a set and stop matching at the first successful set, and add src to the first single or src,dst to the first double data storage set in b to which the entry can be added.

Если очень долго читать (лучше вместе с другими мануалами), то этот поток сознания на образцовом английском вроде даже можно почти понять, что хотел сказать автор.
[/offtop]

— pgprubot (13/10/2015 20:20)   профиль/связь   <#>
комментариев: 511   документов: 2   редакций: 70

В них особого смысла нет (разве что кто-то не хочет устанавливать ipset) — через ipset (функцию allow_out_ipset) это же самое делается проще и естественней.


В лог иногда попадают Tor'овские ICMP-пакеты такого вида. Не поймёшь, что с ними делать, чтоб не открывать очередную утечку (блокировать, разве что).
— pgprubot (16/10/2015 01:55, исправлен 16/10/2015 07:48)   профиль/связь   <#>
комментариев: 511   документов: 2   редакций: 70

Исправлено. Теперь Tor'овские пакеты блокируются отдельно созданной в pf2iptables.sh-скрипте функцией block_tor_bug. Помимо этого обнаружились логируемые пакеты к управляющему порту Tor'а — они тоже были заблокированы соответствующим правилом.


Очередная попытка понять, почему пакеты на lo блокируются, успеха не потерпела. Интернет забит жалобами на ошибочно блкируемые пакеты с URGP=0, причины почти во всех случаях очень тумманны. Некоторые выжимки из чужих разбирательств в подобных проблемах:


Each VM gets its own filtering bridge, so the path of a packet between two VMs on the same host looks like this:

<..>

In this setup each packet goes through a conntrack lookup twice (once on each bridge). This would normally not be an issue; however, the conntrack state is shared between the filtering bridges. This is normally not a problem because conntrack is keeping track of both sides of the TCP connection. The issue comes with the RST flag.

When conntrack encounters a TCP packet with a RST flag it immediately destroys the conntrack entry for that connection. This means that once the RST packet reaches the second filtering bridge, the conntrack state has already been removed, so the RST packet is marked as INVALID.

There is a bug open for this behavior here

It can also be fixed with a hack to get iptables to skip the DESTROY phase, but that will then leave TCP states open that were RST until they expire, so it's not likely that will be an acceptable solution.

Here's what's happening:

TCP 3 packet handshake takes place
Client issues a data request
Client issues a FIN/ACK since its done transmitting info
Netfilter drops the state time out to 60 seconds
Server starts transmitting data back to the client
More than 60 seconds goes by
Netfilter removes the state entry
Server can never complete the data transfer and continually tries to issue a FIN/ACK to close the connection
Netfilter drops all FIN/ACK's because the state table entry is gone

I reported this problem back in 2000 and the time out was increased to 120 seconds. At some point a few years back the time out was dropped back down again causing the problem you are seeing.

So its not a malicious packet, just a bug/feature in the code.

Кто-то находит причину проблем в MTU. В Tor-расссылке тоже на это жалобы есть, но ответа на них нет. Здесь есть ещё один разбор подобных проблем.


UPD: Проблема блокируемых пакетов на lo обнаружилась и для SSH. Пришлось для него тоже написать соответствующие правила. Для этого была определена новая более универсальная функция block_bug, а старая функция block_tor_bug выражена через неё и переименована в block_bug_tor.


UPD2: Та же проблема есть и для mpd. Одним словом, она касается всех соединений, причём как на lo, так и на eth.


Красивым было бы следующее решение: после каждого $pass_out-правила в функциях allow_out, allow_out_ipset, allow_in и allow_in_tor добавить соответствующее $block_out-правило [точно такое же, как $pass_out, только без опции пользователя (и без состояния, если есть)]. Однако, это нарушило бы весь смысл этого «DSL», т.е. он перестал бы быть policy-based (файерволлом по политикам), поскольку в основной части правил стал бы важен порядок. В частности, если бы после allow_out и allow_in функций на lo понадобилось бы дать доступ к порту ещё какому-то пользователю через $pass_out-правило, оно бы не сработало [$block_out-правило внутри функций заблокировало бы напоследок всё для всех (и других пользователей, в том числе)].


Решение: функция block_bug_tor была сделана более универсальной и переименована в block_bug_mports. Теперь её нужно писать в конце всех правил, где перечислить все используемые TCP-порты на lo. Есть ли проблема для UDP — пока неизвестно. Трафик в Интернет идёт через ipset-функции, и там аналогичное блокирующее правило указано непосредственно сразу после allow_out_ipset-правила (т.е. не в конце скрипта, как для пакетов на lo).

— pgprubot (18/10/2015 05:49, исправлен 18/10/2015 06:01)   профиль/связь   <#>
комментариев: 511   документов: 2   редакций: 70

Уже заметили, больше хоста в статистике нет. Однако, есть другое, чему я не вижу простых объяснений: на примерно 200 IP-адресах запущены 2 Tor-ноды. Возможно, это позволяет лучше забить доступную полосу на многоядерных системах и т.д., но 200 — не слишком ли это много? К тому же, нет ни одного IP, где крутилось бы больше двух нод(!).


Many of our biggest relays are actually multi-core systems with one tor instance running on each core, all on the same network connection (since processor utilization tends to bottom out before network bandwidth). Each separate instance (whether on one machine or several) should have a unique ORPort and DirPort, and your router should forward the proper set of ports to the proper machine. Each machine should have the corresponding two ports opened in their firewall. Technically, each node you run should have each of the other's fingerprint(s) in the MyFamily option of their torrc, but since tor never builds circuits within the same /16 subnet anyway, a circuit will never be built with two nodes coming from the same IP. Otherwise, the authorities don't care at all.



Логирование пакетов и их анализ были доведены до ума. Замены:


  • start_out_loggingstart_logging
  • $block_out_logged$block_log
  • log_out_connectionslog_out
  • Добавлены правила log_in.

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


Помимо исходящих пакетов теперь также логируются входящие (те, кто имеет нужный dst IP, остальные — просто блокируются). Это позволяет «посмотреть погоду» в сети — т.е. что собственно происходит. Казалось бы, понятно, что сканируют всех, поэтому ничего информативного из входящих выяснить не удастся (разве что анализировать интенсивность сканирования в целом). Тем не менее, были написаны два скрипта, чтобы было легче разбирать логи. Первый скрипт показывает общую статистику по разным протоколам и пакетам, второй — облегчает анализ счётчиков в выводе iptables-save -c (большинство логирующих правил пустые, и скрипт выделяет из них те, которые непусты, т.е. через которые прошли пакеты, а также сортирует правила по входящим/исходящим).


У скриптов более-менее компактный информативный вывод, взглянув на который легко сразу увидеть, что и где происходит. Например, утечка по UDP или попыка какого-либо пользователя соединиться с сетью напрямую сразу отразятся на счётчиках. Сейчас в норме никакие легитимные исходящие не блокируются помимо редких ICMP unreachable на lo от Tor'а. Т.е. любые заблокированные исходящие — это тревога и повод посмотреть dmesg вручную внимательнее на предмет того, что бы это могло быть.


Наконец, самое интересное — это входящие. Несмотря на примитивность такого кустарного «IDS» результаты не заставили себя долго ждать и оказались крайне неожиданными: некоторые «доверенные» Tor-ноды втихушку занимались сканированием, причём делали это очень хитро. На Tor-ноде был поднят Tor на одних портах (не самых типичных для Tor'а), что легко проверяется по Tor-статистике, а она посылала TCP-пакеты с других src-портов, более типичных для Tor'а — внешне (судя по src/dst PORT) они выглядели как нормальные ответы Tor-ноды (или нода чиста, а это кто-то спуфит?). Пришлось добавить ноду в ExcludeNodes. Теперь статистика сканирующих автоматически проверяется скриптом на наличие в ней Tor-нод, заодно выводится число нод-дублей (несколько нод на одном IP) и полных дублей (типа здесь упомянутого Chiraq).


В принципе, есть много автоматизированных средств для анализа логов iptables (как монстров типа snort, так и примитивных скритов, рисующих графики и разного рода статистику), но тут требовалось что-то совсем простое (лишь бы не вручную листать вывод dmesg), доверенное и плюс нацеленное на сравнение со списком Tor-нод.

— pgprubot (19/10/2015 06:40, исправлен 19/10/2015 06:45)   профиль/связь   <#>
комментариев: 511   документов: 2   редакций: 70

Исправлена ошибка в регекспах (записи об ICMP-пакетах в конце содержат дополнительную информацию в квадратных скобках о том пакете, который вызвал ICMP, из-за чего SRC/DST/PROTO будут упомянуты в записи дважды; поскольку регекспы жадные, захват будет по последнему совпадению, а не первому) и в сортировке (перед uniq нужно ставить sort).


Окончательная сортировка вывода теперь идёт не по первому столбцу (т.е. по количеству пакетов с одинаковыми SRC, SPT и DPT), а по последнему (DPT, порт назначения). Статистика показывает, что множество разных IP из одной подсети /16 ломятся на один и тот же порт, а иногда ещё и SPT выбираются нерандомно, что позволяет предположить, что все пакеты относятся к одному источнику сканирования. Если в указанном скрипте заменить '\2 \4 \6' на '\2 \6', то влияние SPT будет убрано из всей диагностики во всех подсчётах.


По-хорошему, вместо примитивного логирования нужна запись содержимого всех пакетов (особенно исходящих заблокированных), потому что без этого установить причины утечки порой затруднительно. Например, в случае утечек по DNS хорошо было бы знать, какие имена хостов или какие IP пытались отрезолвить программы под указанным пользователем — в обычных записях логов этой информации нет, поэтому остаётся только гадать, вспоминая, какие программы запускались, и какие команды выполнялись. Судя по гуглу, задача полной записи пакетов решается через -j ULOG/NFLOG + ulogd2 + pcap-плагин, но заставить работать pcap с этим комбайнером у меня пока не получилось (ULOG/NFLOG-логи сыпятся в обычном dmesg-формате в иной текстовый файл).

— pgprubot (08/03/2016 19:06, исправлен 08/03/2016 19:07)   профиль/связь   <#>
комментариев: 511   документов: 2   редакций: 70

Добавлена опция dry_run. Если её поменять на on, получается просто список команд-правил на выходе без их загрузки в iptables локальной машины — такой вывод iptables-скрипта можно использовать в качестве готового списка правил для загрузки на другие машины, т.е. "компилим" правила локально и загружаем удалённо.

Ваша оценка документа [показать результаты]
-3-2-1 0+1+2+3