МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное агентство по образованию
Государственное образовательное учреждение
высшего профессионального образования
«Новосибирский государственный университет» (НГУ)
____________________________________________________________________________________________________________________
Кафедра общей информатики
Дмитрий Валерьевич САВЕНКО
АВТОМАТИЧЕСКАЯ ГЕНЕРАЦИЯ АНАЛИЗАТОРОВ ТРАФИКА ЛОКАЛЬНЫХ СЕТЕЙ
МАГИСТЕРСКАЯ ДИССЕРТАЦИЯ
по направлению высшего профессионального образования
230100.68 Информатика и вычислительная техника
факультет информационных технологий
Тема диссертации утверждена распоряжением по НГУ №___ от «___»___________200__г.
Руководители
Иртегов Дмитрий Валентинович,
доцент, НГУ
Романенко Алексей Анатольевич,
канд. техн. наук, НГУ
Новосибирск, 2009г.
Содержание.
ВВЕДЕНИЕ................................................................................................................................... 3
1 Постановка задачи и новизна................................................................................................... 5
2 Выбор платформы...................................................................................................................... 7
3 Технические требования........................................................................................................... 8
4 Алгоритм построения правил................................................................................................. 10
5 Работа с базой данных............................................................................................................. 13
6 Сбор статистики....................................................................................................................... 14
7 Архитектура генератора правил............................................................................................. 15
8 Правило портов и протоколов................................................................................................ 20
9 Правило масок.......................................................................................................................... 22
10 Структура и использование генерируемых правил........................................................... 25
11 Оповещение об аномальной активности............................................................................. 29
12 Тестовые испытания и их результаты.................................................................................. 29
ЗАКЛЮЧЕНИЕ........................................................................................................................... 31
Литература................................................................................................................................... 33
Публикации автора..................................................................................................................... 35
Приложение А. Некоторые интерфейсы системы. ................................................................ 36
Приложение Б. Файл описания диапазонов адресов. ............................................................ 39
ВВЕДЕНИЕ.
Краткое описание.
В работе реализована идея автоматической генерации анализатора трафика локальной сети на основе ранее собранной статистики трафика в этой сети. Такой анализатор способен выделять из потока пакетов в сети аномальный трафик и извещать о нем администратора (либо сразу блокировать). Подход ценен тем, что дает возможность обнаружения атак и вирусов ранее неизвестных типов, а также неисправностей и нецелевого использования ресурсов.
Актуальность и новизна.
Контроль над трафиком в локальной сети обычно осуществляется с помощью установки и настройки межсетевых экранов на нескольких ее ключевых узлах. Это позволяет разграничить доступ к основным ресурсам и тем самым обеспечить некоторый уровень безопасности для сети в целом. Такое решение является неотъемлемой частью комплексной защиты, но оно никак не может быть признано достаточным.
Более совершенным методом является использование NIDS, Network Intrusion Detection Systems [8, 9]. Существует несколько типов таких систем, от бесплатного ПО с открытым исходным кодом до дорогих программно-аппаратных комплексов, разрабатываемых обычно крупными производителями сетевого оборудования (например, Cisco). В таких системах для идентификации вредоносного трафика используются часто обновляемые базы данных сигнатур уже известных сетевых атак.
Отличием нашего подхода от описанных выше является то, что создаются индивидуальные
для каждой сети анализаторы трафика, которые выявляют подозрительные пакеты с учетом особенностей конкретных
локальных сетей. Это позволяет избавиться от большого количества ложных срабатываний (что является одной из серьезных проблем при разработке NIDS-систем), а также в ряде случаев позволяет выявить атаки и вредоносное ПО неизвестного ранее типа. Во многих NIDS-системах имеются модули, призванные решать задачу поиска вредоносного трафика неизвестных типов, но они основаны на эвристических гипотезах общего характера, и результаты их работы не могут считаться вполне достоверными. Также наш подход дает возможность выявлять не только вредоносный трафик, но и трафик, сам по себе являющийся безопасным, однако свидетельствующий о проблемах в локальной сети, таких как неисправности оборудования или нецелевое использование ресурсов пользователями.
Первым этапом работы программы является сбор информации о нормальной активности в сети. Сборщик создает базу данных с описанием типов устанавливаемых за время его работы соединений. В дальнейшем нормальным считается соединение, принадлежащее какому-либо из этих типов, а аномальным — соединение, которое нельзя отнести ни к одному из них. Вторым этапом по базе данных нормальных соединений строится анализатор трафика в виде набора логических правил, которые точно отделяют нормальный трафик от аномального. Третьим этапом правила переводится в формат iptables ([7], утилита настройки межсетевых экранов, используемая повсеместно на компьютерах, работающих под управлением Linux/UNIX-систем). Затем они могут быть применены для мониторинга трафика и информирования администратора о появлении аномальных соединений. Чтобы быстро обрабатывать большие объемы трафика, набор правил строится таким образом, что чем чаще то или иное соединение появляется в сети, тем быстрее оно проходит через него. Набор правил является точным описанием трафика сети за период наблюдений (например, в отличие от [2], где для предсказания аномального трафика используется метод гибридной корреляции событий [1]).
Результаты.
Результатом работы над проектом является комплекс утилит для операционной системы Linux, написанных на языке С++ и решающих следующие задачи: сбор статистики установки соединений в локальной сети, ее анализ и построение набора правил, перевод его в формат iptables, а также анализ результатов работы правил.
Тестовые испытания проводились на одном из сегментов локальной сети НГУ. Статистика собиралась непрерывно на протяжении недели, затем сгенерированные по ней правила применялись для поиска аномальной активности. Получены практические результаты, доказывающие полезность проекта в реальных условиях, а именно удалось выявить несколько фактов нецелевого использования ресурсов (несанкционированные запуски клиентов пиринговых сетей на компьютерах лаборатории Parallels-НГУ).
При тестировании были замерены важные технические характеристики системы, определяющие ее применимость на практике, а именно: количество генерируемых правил и скорость принятия решений о соединениях этими правилами. После преодоления определенного порога происходит насыщение по количеству правил, и оно больше не зависит от размера статистики, а колеблется вокруг некоторого среднего значения. Замеры же скорости показали, что более 91% всего трафика сети фильтруется за 3 или 4 проверки, и лишь 0,7% трафика требует более 8 проверок. Это гарантирует высокую скорость работы генерируемых наборов правил. 3-4 проверки примерно соответствуют количеству, которое обычно получается при задании правил администраторами вручную.
Перспективы развития.
Система требует доработки в основном по двум направлениям: реализация более дружественного интерфейса и расширение круга поддерживаемых свойств сетевых соединений, в первую очередь — учет количества соединений и объема трафика в каждом соединении. Также существующая версия хотя и поддерживает протоколы TCP, UDP и ICMP, но для двух последних из них не поддерживает отслеживание соединений (connection tracking), если работает в качестве пассивного прослушивающего узла. Это приводит к потере информации, и при дальнейшей доработке данную проблему необходимо устранить. Еще одним перспективным вариантом развития мог бы быть учет времени суток. Очевидно, что в корпоративной сети характер и количество сетевых взаимодействий во время рабочего дня и во внерабочее время должны различаться.
1 Постановка задачи и новизна.
Существующие методы защиты локальных сетей обычно либо никак не используют особенности трафика конкретных сетей, либо полагаются на представления администраторов о том, каким должен быть
трафик. Появилась идея создания средства защиты, которое будет базироваться на объективной
картине трафика для каждой конкретной локальной сети. Постановка задачи была сформулирована следующим образом:
Разработать метод контроля над трафиком локальной сети при помощи выявления аномальных для данной сети соединений. Выяснить перспективы применимости такого подхода на практике.
Для классификации объектов и выявления закономерностей используются методы интеллектуального анализа данных (ИАД). Но для выявления аномальных событий стандартные методы ИАД не приспособлены, т.к. требуют наличия в обучающей выборке как положительных, так и отрицательных примеров. Существуют разработки, призванные избавиться от этой проблемы, например, метод гибридной корреляции событий (ГКС, [1]). Основная суть ГКС состоит в том, чтобы искать аномальные события как нарушения течения нормальных событий. Использованию ГКС применительно к трафику локальной сети посвящены работы [2, 3]. В данной работе применяется метод, эксплуатирующий ту же идею, но выявляющий аномальный трафик с помощью точного
описания собранной статистики трафика локальной сети. Такой метод нельзя назвать методом ИАД, т.к. любой трафик, отсутствующий в статистике, признается аномальным. Прогнозов по поводу нормального трафика, которого нет в статистике — не делается. Чтобы компенсировать этот недостаток, нужно учесть особенности, вносящие элемент случайности в трафик локальной сети (например, номера портов отправителя в протоколе TCP), а также работать со статистиками, собранными в течение продолжительного времени. К тому же, необходимо реализовать анализатор таким образом, чтобы он мог обрабатывать большие объемы данных в реальном времени. К «плюсам» подхода относится точность распознавания (при достаточно большой статистике, ведь трафик локальных сетей статичен и редко меняется) и низкие требования к квалификации пользователя (нет необходимости настраивать различные параметры, присущие методам ИАД).
Для достижения поставленной цели необходимо разработать систему, которая по статистике установки соединений в локальной сети создает правила, с помощью которых можно выявить аномальный трафик. Нормальный трафик (который есть в статистике) должен игнорироваться. Набор таких правил и представляют собой автоматически сгенерированный анализатор трафика для данной локальной сети. В качестве основного варианта использования такого анализатора предлагается следующая схема. С помощью управляемого коммутатора настраивается зеркалирование трафика сети на определенный физический порт. К нему подключается компьютер с операционной системой Linux (возможен вариант с развертыванием ее внутри виртуальной машины). На этом компьютере устанавливаются правила, и таким образом происходит мониторинг трафика сети. Анализатор работает в автономном режиме и извещает администратора о появлении аномалий каким-либо образом (например, по электронной почте). Более подробно о вариантах использования будет рассказано в главе 10.
При наличии инструмента, позволяющего выявить аномальный для данной локальной сети трафик, можно решать широкий спектр задач по обеспечению безопасности работы в сети. Ниже приведено несколько примеров.
Появилось новое соединение от пользователя А к пользователю Б. Это может быть следствием активности вредоносного программного обеспечения. Например, на А появился вирус, который распространился на Б. Или Б установил на А программу-троян, которая теперь пересылает ему личную информацию А. Или А проводит атаку на Б.
Появилось новое соединение от А к Б через узел М1. Возможно, раньше они соединялись через узел М2, но на нем появились неполадки, и им пришлось искать обходные пути.
Появилось соединение от А к Б, при этом у Б есть безлимитный доступ в Интернет, и активность общения Б с внешним миром повысилась. Это может сигнализировать о том, что А договорился с Б и начал пользоваться его внешним каналом, что во многих сетях запрещено.
Таким образом, возможность выявления аномального трафика в локальной сети помогает в обнаружении проблем, связанных с вредоносным ПО, неисправностями оборудования или нелегальным использованием ресурсов. Но исследование доступных решений показало, что систем такого типа, по-видимому, не существует, либо они имеют закрытый статус и не предназначены для широкого использования. Ближайшие аналоги, которые удалось найти — это широкий спектр так называемых NIDS/NIPS-решений (Network Intrusion Detection/Prevention Systems, сетевые системы обнаружения/предотвращения вторжений). Данные системы призваны решать задачу комплексной защиты сетей, однако они работают с позиций, сильно отличающихся от наших. Используются в основном два алгоритма анализа трафика. Первый — это сравнение битовой последовательности потока данных с эталонной сигнатурой. Второй — выявление аномальной протокольной активности (Protocol Anomaly Detection, PAD). Сигнатурный анализ позволяет установить и обезвредить уже известную угрозу, а PAD специализируется на атаках, не имеющих сигнатур. В процессе проверки средствами PAD система исследует использование сетевых протоколов на соответствие заложенным требованиям (это могут быть как общие спецификации RFC, так и специфические критерии разработчиков).
Фактически стандартом де-факто среди NIDS/NIPS благодаря своей широкой доступности стал Snort [8]. Это бесплатная система с открытым исходным кодом. Она способна выполнять регистрацию пакетов и в реальном времени осуществлять анализ трафика в IP сетях. Состоит из набора модулей для активного блокирования или пассивного обнаружения ряда известных нападений и зондирований, таких как переполнение буфера, стелс-сканирование портов, атаки на веб-приложения, попытки определения ОС. Кроме Snort существует много коммерческих программных и программно-аппаратных решений класса NIDS/NIPS (Cisco IDS/IPS, StoneGate IPS, Radware Defense Pro, Juniper Networks Tap, Intrusion SecureNet и т.д.). Они были нам недоступны для непосредственного изучения, однако из обзоров (например, [9]) следует, что базируются все эти системы также на анализе сигнатур и PAD.
2 Выбор платформы.
В качестве платформы для реализации была выбрана операционная система Linux. Для представления правил выделения аномального трафика решено было использовать формат правил iptables, утилиты управления межсетевым экраном netfilter, встроенным в ядро ОС. Выбор обусловлен популярностью Linux на серверах и удобством экспериментов с ОС этого семейства благодаря их открытости. Правила iptables были выбраны по нескольким причинам.
Во-первых, программа анализа трафика должна содержать в себе компоненты, ответственные за регистрацию пакетов, сравнение их характеристик и т.д. Весь этот код уже содержится в netfilter/iptables. Более того, из-за широкой популярности и открытости ядра Linux этот код заслуживает гораздо большего доверия, чем любые другие варианты.
Во-вторых, формат правил iptables схож с форматом логических решающих правил [4] — математической абстракцией, используемой в ряде алгоритмов для решения задачи распознавания образов.
В-третьих, netfiler/iptables являются частью любой ОС семейства Linux/UNIX. Это избавляет администраторов от необходимости устанавливать и осваивать дополнительное ПО при работе с нашей системой. Результат работы программы представляет собой набор правил iptables, которые отлично знакомы любому Linux/UNIX-администратору.
С самого начала было ясно, что задача предполагает большие объемы вычислений, из-за чего необходимо минимизировать накладные расходы при работе системы. Поэтому от языков с автоматическим управлением памятью (например, Java) решено было отказаться, и в качестве языка реализации был выбран С++. Также некоторые компоненты (например, оповещение администратора о появлении аномальной активности) реализованы при помощи скриптового языка Bash.
В качестве инструмента хранения и управления статистиками была выбрана встраиваемая база данных Berkeley DB. Возможностей стандартной библиотеки С++ недостаточно, т.к. объемы данных могут быть огромны, что не позволяет хранить все в оперативной памяти. Реляционные же базы данных для наших целей избыточны и потребовали бы усложнения системы, как с точки зрения программиста, так и с точки зрения пользователя.
3 Технические требования.
1. Система должна функционировать не на уровне отдельных пакетов, а на уровне соединений. Под соединением понимается поток пакетов между клиентом (инициатором соединения) и сервером. Даже для протоколов, не поддерживающих явно соединения (UDP, ICMP), необходимо выделять их логически из общего потока пакетов. Это необходимо для борьбы со случайными параметрами соединений (например, значения портов в некоторых протоколах).
2. Система должна предоставлять возможность сбора трафика, проходящего через определенный узел сети, и сохранение информации о параметрах устанавливаемых соединений и частоте их установки в базе данных. Таким способом можно собрать информацию не только о пакетах, относящихся к данному узлу, но и обо всех пакетах, появляющихся в сети. Для этого либо сам узел должен быть маршрутизатором, либо коммутатор должен быть настроен на зеркалирование пакетов на данный узел.
3. Система должна предоставлять возможность построения набора правил, описывающих собранную статистику. Скорость построения правил не является критичным параметром. Однако, в целях обеспечения применимости системы на практике, время построения набора не должно быть чрезмерно велико. Здесь трудно дать точные числовые характеристики. Но представляется разумным, если алгоритм будет работать за полиномиальное время.
4. Набор сгенерированных правил должен обладать следующим важнейшим свойством: чем чаще соединение с определенными параметрами встречается в статистике (то есть, чем чаще такое соединение появляется в сети), тем быстрее оно должно проходить через правила. Это позволяет значительно снизить нагрузку на процессор машины, на которой будут установлены правила, и обрабатывать большие объемы трафика на относительно маломощных компьютерах.
5. Количество генерируемых правил не должно расти с ростом размера статистики. При малых размерах такая зависимость допустима, однако по достижении определенного порога мощности должно происходить насыщение, т.е. прекращение роста количества правил.
6. Система должна поддерживать протоколы TCP, UDP и ICMP [10] и при построении правил использовать как минимум следующие параметры сетевых соединений: тип протокола, IP-адрес отправителя, IP-адрес получателя, порт получателя (имеет смысл для протоколов TCP и UDP. Порт отправителя обычно выбирается случайным образом и не должен учитываться).
7. Система должна предоставлять возможность перевода построенного набора правил в формат правил для iptables. Система должна генерировать файл(ы) с командами, необходимыми для того, чтобы полученные правила iptables вступили в силу. Также система должна генерировать файл(ы) с командами, позволяющими корректно удалить сгенерированные правила.
8. Правила, генерируемые системой, должны пропускать нормальный трафик, а аномальный регистрировать при помощи стандартных средств iptables (записывать его параметры в журнал ОС). Возможна и реализация, когда аномальный трафик будет блокироваться, но для применения таких реализаций необходимо накопить опыт эксплуатации анализатора, в первую очередь — оценить количество и характер ложных срабатываний анализатора.
9. Система должна иметь средства автоматического оповещения администратора о появлении аномального трафика. Администратор не должен быть обязан самостоятельно просматривать журнал сообщений в поисках сообщений об аномальном трафике. Разумным предоставляется наличие в системе скрипта, который можно было бы настроить на периодический запуск и проверку журнала сообщений и, при наличии аномального трафика, формирование электронного письма администратору.
10. Система должна представлять собою набор утилит для ОС Linux, управляемых при помощи интерфейса командной строки и/или конфигурационных файлов.
Дополнительные требования к отдельным компонентам системы будут освещены ниже, в разделах, посвященных этим компонентам.
4 Алгоритм построения правил.
Требование к скорости анализа трафика исключает наивные алгоритмы, типа выписывания параметров каждого соединения из статистики в виде отдельного правила. В таком случае сложность принятия решения по каждому соединению оценивалась бы как , где n
— количество уникальных соединений (обычно это десятки или сотни тысяч объектов).
Нашу задачу нельзя отнести к классу задач распознавания образов, т.к. прогнозирования не требуется, а необходимо лишь описать на языке правил имеющуюся статистику. Однако некоторые методы решения задач распознавания образов могут послужить основой для решения нашей задачи. Из всего спектра этих методов в нашем случае наиболее эффективными представляются алгоритмы, основанные на построении так называемых логических решающих правил (ЛРП, [4]). Представим себе объекты с разными свойствами в виде точек в многомерном пространстве, каждое измерение которого соответствует определенному свойству. Тогда различные образы объектов будут в этом пространстве являться несовпадающими множествами точек. Если бы одномерные проекции этих множеств не перекрывались, задача распознавания была бы тривиальной. Обычно это требование в чистом виде не выполняется. Проекции точек разных образов на координатные оси образуют перекрывающиеся области. Но области перекрытия на разных координатах выглядят по-разному, что позволяет строить эффективные решающие правила с помощью комбинаций несовпадающих перекрытий на нескольких осях. Правила, описывающие такие комбинации, и называются логическими решающими правилами. Формат ЛРП («если такие-то свойства объекта удовлетворяют таким-то значениям, то принять такое-то решение») похож на формат, в котором обычно записываются инструкции для межсетевых экранов (причем, не только для netfilter, но и для большинства других представителей данного класса ПО). Однако это не единственная причина, почему ЛРП хорошо подходят для нашей задачи. Еще более важными являются следующие соображения:
У сетевых соединений имеется большое количество разнотипных признаков. Порты — это целые числа со стандартно заданным отношением порядка, диапазоны IP-адресов — это множества особой структуры со своими правилами принадлежности и вложенности, протоколы — это числовые значения, не подразумевающие какого-либо порядка, и т.д. Но при использовании ЛРП подобное разнообразие типов признаков не вызывает проблем.
Как уже говорилось выше, необходимо обеспечить максимально возможное быстродействие готовых анализаторов трафика, при этом скорость построения анализатора не является критичной. ЛРП удовлетворяют этому требованию. Принимать по ним решения просто и быстро.
Метод построения правил, выбранный для реализации в нашей системе, представляет собой модификацию алгоритма DW [5]. Этот алгоритм предполагает, что имеется два образа, и строит дерево правил, позволяющее отделить один образ от другого. Он оперирует понятием элементарного высказывания (ЭВ), под которым подразумевается выражение вида , где Р
обозначает некое свойство объекта a
, а Х0
— фиксированное множество, называемое пороговым значением. Во многих случаях это представление можно упростить до вида , либо (под Х0
в этом случае понимается уже не множество, а конкретное значение). Будем обозначать элементарные высказывания символом J
. Дополнение к J
представляет собой обычное логическое отрицание —
¬
J.
Из ЭВ и их дополнений можно строить конъюнкции разной длины. Набор таких конъюнкций представим в виде дихотомического дерева, в котором каждой ветви соответствует одна конъюнкция. Конечные вершины дерева содержат объекты обучающей выборки, пришедшие в них из корня. Лист признается принадлежащим тому образу, объектов которого в нем скопилось больше.
При выборе конкретных ЭВ и пороговых значений к ним возникает большой перебор, сокращение которого основано на методе наращивания «от лучшего к лучшему» [6]. Согласно этому методу, дерево ЭВ строится следующим образом. На первом шаге мы имеем одну вершину дерева (корень), которой приписана вся обучающая выборка целиком. Строится наилучшее в смысле некоторого критерия G
(см. ниже) элементарное высказывание J(11)
и его дополнение J(12)
. С помощью них выборка делится на две группы: U(11)
, удовлетворяющая J(11)
, и U(12)
, удовлетворяющая J(12)
. Строится два потомка корневой вершины, «левому» приписано множество U(11)
, а «правому» — U(12)
. Соответственно, можно считать, что «левому» ребру приписано ЭВ J(11)
, а «правому» ребру — J(12)
. Процесс повторяется для новых вершин.
Это канонический вариант метода. Для нашего случая (описание свойств имеющейся статистики) алгоритм был существенно изменен.
В нашей задаче требуется точное отделение нормальных объектов от аномальных. Множества возможных значений всех свойств (т.е. множества значений разных параметров сетевых соединений) — дискретные и ограниченные, непрерывных параметров нет. Также в статистике нет аномальных объектов. Теоретически, общее количество всевозможных комбинаций параметров соединений конечно, и мы могли бы методом исключения сгенерировать множество аномальных объектов. Однако мощность перебора, который возникает при этом, сводила бы на нет практическую ценность программы. Поэтому, чтобы обеспечить точное отделение нормальных объектов от аномальных, пороговые значения для ЭВ не могут быть произвольными, а должны выбираться только из тех значений, которые встречались в статистике (т.е. нормальные). Тогда мы можем быть уверены, что если в лист дерева попал хотя бы один нормальный объект из статистики, то аномальные объекты (которых мы не знаем) в этот лист попасть не могут. И наоборот, если в лист не попало ни одного нормального объекта, то туда могут попасть только аномальные объекты. Также благодаря выбору только используемых в статистике значений существенно сокращается перебор, т.к. пространство значений некоторых параметров достаточно велико (например, 216
портов), в то же время число реально используемых в обучающей выборке значений обычно гораздо меньше.
Необходимо описать критерий G
выбора определенных пороговых значений. В оригинальном DW этот критерий носит статистический характер, в нашем же алгоритме он призван обеспечивать основное свойство дерева: минимизация средневзвешенного времени прохождения соединений через него. Под весом соединения здесь и далее понимается частота его встречаемости в сети. Чтобы удовлетворить этому требованию, при построении очередной вершины мы выбираем такую связку ЭВ и порогового значения для него, которая обеспечивает наиболее равномерное деление текущего подмножества объектов обучающей выборки. Иными словами, две части, получаемые в результате деления, должны как можно меньше отличаться друг от друга по весу. То есть, минимальной должна быть величина , где и — ЭВ и дополнение к нему, а , где U
— это текущее подмножество, — это количество появлений объекта во время сбора статистики, а запись означает, что объект i
удовлетворяет J
( — аналогично). Это гарантирует, что чем чаще пакет появляется в сети, тем быстрее он пройдет через дерево правил.
Таковы общие теоретические основы работы. В следующих частях будут освещены конкретные технические решения и сопутствующие алгоритмы, использованные для реализации генератора правил, а также других модулей.
5 Работа с базой данных.
Хотя в начале разработки база данных Berkeley DB была выбрана как инструмент управления статистиками, было решено реализовать отдельный модуль, предоставляющий интерфейс управления статистиками и инкапсулирующий работу с базой данных с тем, чтобы при необходимости можно было легко перейти на другую СУБД. К тому же, C++ API, включенный в стандартную поставку Berkeley DB, является лишь очень тонкой надстройкой над API на С. Игнорируются многие возможности, доступные в С++, такие как исключения, умные указатели, шаблоны, перегрузка операторов и т.д. Вся логика работы, присущая API на С (в том числе необходимость обработки «голых» указателей), осталась неизменной. Это чревато серьезными ошибками, особенно когда остальной код активно использует исключения. Поэтому интерфейс управления статистиками необходим также для сглаживания недостатков Berkeley DB C++ API. Речь не идет о реализации полноценного интерфейса для Berkeley DB, такая задача не ставилась. Были выбраны лишь те возможности, которые необходимы для работы со статистиками.
Вся работа со статистиками происходит через шаблонный класс DBController. Класс принимает четыре шаблонных параметра: тип ключа, тип данных, стратегия управления типом ключа и стратегия управления типом данных. Стратегия предоставляет функции сериализации и десериализации данных определенного типа. Была написана стратегия для встроенных типов (не указателей) и STL-строк под названием StdStrategy. Она используется по умолчанию для типов ключа и данных шаблона DBController.
Методы DBController позволяют записывать и считывать данные по ключу и проверять наличие ключа в базе данных. От реализации доступа к записям через итераторы решено было отказаться. Однако DBController имеет шаблонный метод template<T> foreach(T& op), получающий в качестве параметра функтор (указатель на функцию или экземпляр класса с перегруженным оператором вызова — operator()), который должен принимать два аргумента (ключ и значение) и будет вызван для каждой записи в базе данных.
Реализация шаблона DBController основана на идиоме скрытой реализации pimpl (pointer to implementation, [16]). Объявлен класс DBContRawImpl. Он ответственен за всю работу непосредственно с базой данных, его методы используются шаблоном DBController. Его определение и реализация находятся в отдельном файле. Таким образом, DBContRawImpl может быть изменен без перекомпиляции кода, использующего шаблон DBController. Это позволяет при необходимости изменить используемую СУБД, изменив лишь файл реализации DBContRawImpl или вообще подменив его другим файлом. Стратегия StdStrategy и интерфейс шаблона DBController приведены в приложении А.
6 Сбор статистики.
За сбор статистики отвечает компонент под названием коллектор. Он занимается перехватом пакетов и записью их в базу данных. Захват производится средствами популярной библиотеки pcap (Packet Capture [11], ее использует подавляющее большинство снифферов и им подобного ПО, в том числе широко распространенная утилита tcpdump [12]), а работа с базой данных ведется при помощи шаблона DBController.
Необходимость написания отдельной программы захвата пакетов, когда уже есть tcpdump, на первый взгляд кажется необоснованной. Однако это повышает удобство использования системы и увеличивает надежность кода сбора статистики. Tcpdump выводит информацию о пакетах в текстовом виде. При применении его для сбора статистики нужно собрать ее в текстовый файл, а затем перевести этот файл в базу данных. На начальных стадиях разработки проекта использовалась именно такая схема. Но из-за огромного количества соединений и множества избыточных данных текстовые файлы, содержащие статистики, получались очень большими (по несколько гигабайт или даже десятков гигабайт). Такие файлы трудно перемещать, а их конверсия в базу данных занимает слишком много времени.
Эту проблему, будь она единственной, можно решить, используя конвейеры Linux/UNIX [13]: запустить tcpdump и направить его вывод на вход утилиты конверсии. Но проблема, связанная с разбором текстового представления данных — остается. Алгоритм разбора был бы нетривиален и изобиловал ветвлениями. Обеспечить его надежность гораздо сложнее, чем использовать библиотеку pcap напрямую. Поэтому в данном случае самописный легковесный сниффер — лучшее решение.
7 Архитектура генератора правил.
Основным компонентом созданной в рамках проекта системы является генератор — программа, которая по данной статистике строит дерево правил и переводит его в формат iptables. В этом же разделе будет представлена общая архитектура программы. Особенности строения отдельных правил будут разобраны в следующих главах.
В математическом описании алгоритма каждому узлу приписано определенное подмножество статистики, а каждому ребру — ЭВ или его дополнение. В генераторе правил ребер как таковых нет. Дерево представлено набором узлов, каждый из которых имеет указатели на своих потомков и своего предка. Каждому узлу приписан набор ключей
соединений из статистики (массив целых чисел). Правило представляет собой описатель определенного свойства соединения (протокол, адрес, порт). Экземпляры классов правил содержат в себе пороговые значения, их можно считать представлением ЭВ в системе. Каждая вершина дерева содержит список правил, описывающих свойства множества соединений, приписанного ей. В корне дерева находятся правила, описывающие все возможные соединения. Для обеспечения расширяемости системы правила должны быть максимально отделены от остальной программы, но вместе с тем иметь полный доступ к соединениям, приписанным вершинам (но о самих вершинах они знать не должны).
Правила не должны иметь дело с контроллером статистик. Единственные данные, которыми они оперируют — это наборы соединений. Поэтому был определен интерфейс[1]
Set, описывающий набор соединений и с произвольным доступом по локальным номерам и используемый правилами. DBVectorSet — класс, реализующий интерфейс Set. В методах этого класса локальный номер переводится в глобальный номер соединения в статистике, затем по этому номеру из базы данных извлекается описатель соединения. Правилам в качестве наборов соединений передаются указатели на экземпляры класса DBVectorSet. Интерфейс Set на языке С++ приведен в приложении А.
Само правило определяется как класс, реализующий интерфейс Rule. Основной его метод — compute. Он проводит поиск порогового значения, обеспечивающего наилучшее деление данного набора соединений данным правилом. Метод получает в качестве параметра множество соединений (указатель Set*), и возвращает числовое значение разности в весах двух частей, полученных при делении. Чем меньше разность — тем лучше. Полученные характеристики деления (пороговое значение, левое и правое подмножества, а также два дочерних правила, описывающие свойства левого и правого подмножеств) сохраняются в объекте правила и могут быть получены в дальнейшем с помощью специальных методов. Если не удалось найти такое пороговое значение, при котором хотя бы одно соединение из переданного множества удовлетворяет ЭВ, то будем говорить, что правило не прошло
. Если для любого возможного порогового значения ЭВ удовлетворяют все соединения, то будем говорить, что правило прошло, но не дало деления
(невырожденного). Для определения того, какое именно из правил вершины использовалось при делении, служит флаг активности правила. Также у правила есть метод, позволяющий получить его параметры в текстовом виде, пригодном для использования в командах iptables. В дополнение ко всему, определена процедура клонирования, позволяющая получить точную копию объекта правила. Интерфейс Rule на языке С++ приведен в приложении А.
Работа генератора состоит из двух основных частей: построение дерева и перевод дерева в формат iptables.
Построение дерева.
Первым этапом создается корень будущего дерева. Ему приписываются ключи всех соединений из статистики. Также создаются экземпляры правил с самыми широкими границами, возможными для каждого класса (все протоколы, все порты, все диапазоны адресов) и приписываются корню. После этого корень ставится в очередь обработки. Дальнейшие действия выполняются до тех пор, пока очередь не пуста.
Из очереди извлекается очередная вершина. У каждого из находящихся в ней правил вызывается метод compute, которому передается в качестве параметра множество соединений, приписанное вершине. Если хотя бы одно из правил не прошло, то работать с вершиной дальше нет смысла — она становится аномальным листом, и все ее правила помечаются как активные (смысл этого действия станет ясен позже). Если же все правила прошли успешно, то возможны два случая:
Ни одно правило не дало деления. Это означает, что мы имеем дело с нормальным листом, и все его правила также помечаются как активные.
Есть правила, дающие невырожденное деление. Тогда выбирается то из них, метод compute которого вернул наименьшее значение. Обозначим его через AR
. Это правило помечается как активное. Создается два новых узла — левый и правый потомки текущего. Все неактивные правила клонируются в них без изменений. Активное правило не клонируется. Вместо этого левому потомку приписывается его левое дочернее правило, а правому потомку — его правое дочернее правило. Также левому потомку приписывается подмножество соединений, удовлетворяющее левому дочернему правилу AR
(левое подмножество AR
), а правому потомку приписываются все остальные соединения (правое подмножество AR
). Так в программе реализуется деление множества соединений при помощи конкретного ЭВ. Две новые вершины добавляются в очередь.
Как видно, активным является то правило, по которому происходит деление. Нужно пояснить, зачем все правила листьев помечаются активными, если деления не происходит. Необходимость этого действия вытекает из того, что в статистике нет аномальных пакетов, и все листья должны быть однозначно идентифицированы либо как нормальные, либо как аномальные (см. алгоритм). Опишем ситуацию на примере. Пусть множество состоит из двух соединений — Conn1 и Conn2, и их параметры различаются только адресом отправителя: для Conn1 это A1, а для Conn2 — A2. Очевидно, что деление произойдет именно по этому полю. Мы получим две вершины, по одному соединению в каждой. И будет известно, какой адрес отправителя у обоих соединений (т.к. именно по этому правилу произошло деление). Но все остальные правила просто клонируются, поэтому нет пока никаких данных, например, о портах соединений. У правил портов этих вершин будет вызван метод compute, который не найдет деления, но обнаружит, что порт всего один. Если не определить правило портов как активное, эта информация будет утеряна, и в левый лист в дальнейшем будут попадать соединения с любым значением порта, имеющие адрес отправителя A1 (аналогично для правого листа). Что неверно, т.к. допускает попадание в нормальные листья аномальных соединений. Если же сделать правило портов активным, мы будем иметь информацию, что в этих двух листьях должны находиться соединения только с данным значением порта.
Как было задано в требованиях к системе, сложность построения должна быть полиномиальной. Без учета сложности правил сложность алгоритма можно оценить в лучшем случае как , а в худшем — как , где n
— размер статистики. В первом случае всегда происходит равномерное деление, и получается уровней по n
соединений в каждом, во втором — всегда отделяется ровно одно соединение, и получается n
уровней. Из разделов, посвященных конкретным правилам, станет ясно, что сложность метода compute любого из них не может превышать (на самом деле, в большинстве случаев она гораздо меньше и приближается к ). Таким образом, общая сложность алгоритма построения дерева правил оценивается в худшем случае как , что нам кажется вполне приемлемым результатом.
Перевод дерев
Допустим, есть вершина N, и это не лист. Тогда одно из правил N является активным. Из-за того, что правила в вершине описывают состояние соединений именно в этой вершине, получить ЭВ, используемое для деления узла N можно так:
Найти, какое из правил активно в N;
Выписать значение этого правила для левого потомка
N.
Условно обозначим данную операцию PrintSeparation(N). На втором шаге можно было бы использовать и правого потомка, это вопрос соглашения. В генераторе всегда используется левый. Хотя iptables не поддерживает древовидные структуры, есть возможность создавать пользовательские цепочки правил, с помощью чего деревья можно моделировать. Делается это следующим образом (на примере все того же узла N):
создается новая цепочка с уникальным именем C(N);
создается правило вида «если PrintSeparation(N), то перейти в цепочку C(N)».
Все соединения левого потомка N перейдут в цепочку C(N), а соединения правого потомка останутся в текущей цепочке — что и описывает деление вершины. Имя цепочки строится по шаблону «chain» + порядковый номер.
Все дерево переводится в набор команд iptables обходом в глубину. Выше разобрано, как реализуется перевод обычных вершин (не листьев). В листьях происходит иной процесс. Напомним, что все правила листа помечены как активные. Печатаются именно эти правила, а не правила потомков (т.к. потомков нет). Также производится проверка на избыточность правил. Согласно требованиям к системе, должно обеспечиваться как можно меньшее количество сравнений для определения статуса соединения. В не-листьях избыточных сравнений быть не может, а вот в листьях они могут иметь место. Где-то в ветке от корня до листа может сработать некоторое правило R
с пороговым значением а
. Если ниже по ветке будут работать другие правила, то вполне вероятна ситуация, когда в конечной вершине ветки R(a)
будет выписано вторично. Но это совершенно не нужно, потому что пакеты, не удовлетворяющие R(a)
, отсеяны раньше и в вершину попасть не могут. Для борьбы с подобными ситуациями при распечатке правил листов происходит обратный поиск по ветке от листа до корня, и избыточные правила не печатаются.
Приведем пример реального дерева правил и того, как оно переводится в набор команд iptables. Данные получены при обработке статистики, собранной на одном из сегментов локальной сети НГУ (подробнее см. главу о тестовых испытаниях системы).
Рисунок 1. Пример дерева.
На рисунке 1 изображено начало дерева. Смысл обозначений таков:
J
0
: порт назначения меньше или равен 80;
J
1
: получатель находится в любой из местных подсетей (внутренний адрес);
J
2
: адрес отправителя равен 193.124.208.85.
А вот как выделенная жирным ветка дерева (J
0
→ ¬
J
1
→ J
2
) была переведена в команды iptables:
1: iptables -N chain1
2: iptables -A chain0 -p tcp --dport 1:80 -j chain1
3: iptables -N chain2
4: iptables -A chain1 -m set --set mset0 dst -j chain2
. . .
X: iptables -A chain1 -p tcp --dport 80 -s 193.124.208.85 -j chain_accept
. . .
Номера строк проставлены вручную для удобства, в настоящих командах их нет. Из фрагмента можно увидеть, во что преобразуются J
0
, J
1
и J
2
. J
0
описано в строке 2, J
1
— в строке 4. Как видно, все соединения, удовлетворяющие J
1
, уходят в цепочку chain2 и там проходят дальнейшее деление. Соединения, не удовлетворяющие J
1
, остаются в цепочке chain1. В строке X
описано J
2
, а также видно следствие того, что правило портов оказалось активным в листе. Было обнаружено, что все соединения в этом листе имеют порт назначения с номером 80, и этот факт отражен в командах iptables (хотя деления по данному параметру не было). О цепочке chain_accept будет рассказано в главе, посвященной структуре генерируемых правил. Сейчас важно лишь то, что в нее попадают все нормальные пакеты.
Далее мы разберем работу двух имеющихся на данный момент в системе правил, которые покрывают четыре свойства соединения: протокол, порт назначения, адреса отправителя и получателя. Станет понятно, почему в примере выше нигде нет проверок на порт отправителя, что скрывается за наименованием mset0, откуда берется адрес 192.124.208.85, и почему в строке X
нет проверки на адрес назначения.
8 Правило портов и протоколов.
В некоторых протоколах портов нет, поэтому порты и протоколы в нашей системе реализованы в одном классе PortRule (больше подошло бы название ProtocolAndPortRule, но PortRule сложилось исторически). Проверка порта осуществляется только для порта назначения. В протоколе TCP порт отправления (порт на клиенте) задается случайным образом. Для UDP правила его задания оговариваются в спецификации протоколов прикладного уровня. В некоторых из них он также случаен. В других определяется, что клиент должен ждать ответа на том же порту, что и сервер, либо предлагаются более изощренные решения (например, системный сервис resolver протокола DNS или специальный сервис отображения портов в Sun RPC [10]). Общепринятого соглашения не существует, поэтому в текущей версии нашей системы UDP-порт на клиенте считается случайным (как и TCP-порт).
Случайные порты не должны фигурировать в наборе правил. Поэтому нельзя было реализовывать систему на уровне простых пакетов, а необходим уровень соединений. Для отдельно взятого пакета невозможно определить, какой из его портов случайный, а какой — фиксированный. Если этот пакет шел от клиента к серверу, то случайным будет порт отправителя, а если это был ответ сервера клиенту — то получателя.
Работа метода PortRule::compute заключается в следующем. Устанавливается, скольким различным протоколам принадлежат переданные соединения. Если протоколов несколько, то деление проходит по ним. Для каждого протокола подсчитывается, каков суммарный вес соединений, соответствующих ему. В качестве ЭВ выступает выражение ,
а пороговым значением выбирается протокол с наибольшим весом. На этом работа прекращается.
Если протокол один, и он поддерживает порты (TCP или UDP), то деление происходит по портам. ЭВ тогда имеет вид . Это не каноническая форма ЭВ, т.к. есть конъюнкция. Но она необходима, потому что в правиле iptables нельзя задать значение порта, не задав перед этим значение протокола (что логично). Для сокращения перебора в качестве кандидатов на выступают только те номера портов, которые встречаются во множестве. Это дает существенное ускорение работы, ведь пространство значений параметра велико (в худшем случае 216
вариантов), в то же время почти всегда используется гораздо меньше различных портов. Для каждого кандидата запоминается, каков суммарный вес соединений, имеющих порты, меньше или равные ему. Наилучшее деление будет обеспечивать тот порт, у которого этот параметр максимален (иногда это — не единственное возможное решение, но всегда один из равноценных вариантов).
Вместе с подсчетом веса портов подсчитывается и так называемый «строгий вес», то есть вес соединений, имеющих порты строго меньше данного. Если после выбора оказывается, что этот параметр для него равен нулю, то ЭВ преобразуется к форме .
Это корректно, т.к. выбирался из используемых во множестве портов, и сам он удовлетворяет условию ,
а значит, весь его вес состоит из соединений со значением порта, в точности равным .
Сложность метода PortRule::compute может быть оценена как , где u
— количество уникальных портов в данном множестве, а n
— его мощность. В худшем случае это . Но такая сложность достигается, только если каждое соединение во множестве имеет уникальный порт. Это редкая ситуация. Поэтому в большинстве случаев метод выполняется гораздо быстрее.
9 Правило масок.
Правило масок (класс MaskRule) описывает свойство принадлежности IP-адреса соединения определенному диапазону (поэтому правильнее было бы назвать его правилом адресов; еще одно не слишком удачное, но прочно закрепившееся название). В зависимости от настройки оно может обрабатывать как адрес отправителя, так и адрес получателя, представляя собой, фактически, два правила. Но нет никакой разницы в алгоритме работы, поэтому в дальнейшем мы будем ссылаться просто на адреса. Адрес соединения является свойством протокола IP, и поскольку выход за рамки семейства протоколов TCP/IP [10] не планируется (во всяком случае, в обозримом будущем), то считается, что IP-адрес есть у любого соединения.
Правило масок может проводить деление соединений, только опираясь на заданные вручную диапазоны адресов. Генерация новых диапазонов не предусмотрена. Это решение требует пояснения. Нет сомнений, что правило масок должно уметь отличать внутренний трафик от внешнего, а значит, информация о диапазонах адресов внутри локальной сети в любом случае необходима. Однако почему бы в дополнение к этим диапазонам не генерировать новые? Возможны два варианта их создания: они либо будут никак не связаны с заданными вручную, либо будут их конкретизировать (сужать). Первый вариант означает попытку эвристически разделить внешнюю сеть на сегменты. Это представляется неразрешимой задачей. Глобальная сеть функционирует по сложным законам, и IP-адреса ее элементов с точки зрения пользователей локальной сети можно считать случайными, не имеющими никакой структуры. Деление их на сегменты приведет к появлению множества бесполезных ветвлений в дереве правил. Внешние IP-адреса могут меняться, что приведет к появлению ложных сообщений об аномальном трафике. К тому же, мощность перебора, необходимого для решения этой задачи, очень велика. Никакой выгоды извлечь не удастся, зато мы получим существенный проигрыш по основным параметрам наборов правил — их количеству и скорости обработки соединений. Поэтому было принято решение никак не обрабатывать адреса, не лежащие ни в одном из заданных вручную диапазонов. Правило масок отключается в вершинах, которым приписаны только соединения с внешними адресами (ниже этот момент будет рассмотрен подробнее). Внешний трафик разбирается только по портам и протоколам.
Попытка конкретизировать диапазоны, заданные администратором, также не кажется хорошей идеей. Если внутри какого-то диапазона нет структуры, то незачем ее искать — это опять же приведет к разбуханию набора правил из-за бесполезных ветвлений и генерации ложных сообщений об аномальном трафике. Если же структура внутри диапазона имеется, то администратор должен сам ее задать. Он может это сделать, ведь структура адресов в локальных сетях почти всегда привязана к их физическому устройству и хорошо известна.
Однако если правило масок встречает множество, у всех соединений которого одинаковый адрес (например, А
), и А
лежит в одном из заданных диапазонов, то делается естественная конкретизация — вместо этого диапазона используется непосредственно адрес А
. Вот откуда появился точный адрес 193.124.208.85 в примере, рассмотренном в главе об общей архитектуре генератора.
Конфигурация правила масок.
Описание структуры адресов локальной сети загружается из файла только при создании корневых правил для адресов отправителя и получателя. В дальнейшем обращения к конфигурации не происходит. По умолчанию берется файл masks.conf в директории, из которой запускается генератор. С помощью ключей командной строки можно задать другой источник. Это текстовый файл, содержащий отдельное описание диапазонов адресов отправителя и получателя. Его формат описан в приложении Б.
Поиск порогового значения.
Перед началом поиска порогового значения в методе MaskRule::compute неявно вводится дополнительный фиктивный диапазон Others, который по определению аккумулирует все адреса, не удовлетворяющие другим диапазонам. Далее делается проход по всем соединениям, и для каждого диапазона адресов (включая фиктивный) подсчитывается суммарный вес соединений, лежащих в нем. Следующим шагом из списка диапазонов удаляются те, вес которых равен нулю. Таким диапазонам не соответствует ни одного соединения, и они должны быть удалены, чтобы не тормозить работу правила.
Если после удаления нулевых диапазонов не осталось ничего, кроме Others — это признак того, что мы попали в вершину с чисто внешним трафиком, о которой говорилось выше. В этом случае правило масок отключается. Флаг отключения распространяется на клоны правила, т.е. идет дальше в поддереве, которое начинается в текущей вершине. Метод compute отключенного правила масок ничего не делает и всегда сообщает, что все пакеты прошли правило успешно, но деления найдено не было. Следует обратить внимание также на то, что правила масок для адресов отправителей и получателей — различные правила. Одно из них может быть отключено, а другое — продолжать функционировать. Благодаря этому успешно разбираются соединения с одним из адресов из внешней сети, а другим — из внутренней.
После того, как исключены все вырожденные случаи, происходит поиск деления. Фактически, имеется массив натуральных чисел (весов диапазонов), и нужно разделить его на две части, как можно меньше отличающиеся друг от друга по сумме. Задача при первом рассмотрении кажется эквивалентной задаче о суммах подмножеств, которая является NP-полной [14]. Но это не так. В нашем случае накладывается дополнительное ограничение, существенно упрощающее решение: одно из подмножеств, образованных в результате деления, должно целиком состоять из элемента(ов) с максимальным значением. Если это не выполняется, то средневзвешенное время прохождения пакетов через дерево правил не будет минимальным, ведь при делении в вершинах редкие соединения будут соседствовать с частыми. Но совсем упростить задачу — брать только один диапазон максимального веса, если таких больше трех — нельзя, потому что тогда соединения с равной частотой будут обрабатываться с применением существенно различного количества проверок.
Деление построено следующим образом. Ищется максимальное значение веса диапазона. В качестве ЭВ для деления используется выражение . Если диапазон максимального веса всего один, то он является единственным элементом . Если таких диапазонов несколько, то в добавляется каждый второй
из них. Дополнительно нужно проверить фиктивный диапазон Others. Он не может быть задан явно, а только методом исключения. Поэтому он не может быть частью порогового значения. Если диапазонов с максимальным весом несколько, и Others — лишь один из них, то мы просто не включаем его в ,
а включаем другие. Если же Others — единственный диапазон с максимальным весом, то составляется из всех остальных
диапазонов, кроме него. Таким образом, мы как бы меняем ЭВ и его дополнение местами.
Сложность метода MaskRule::compute оценивается как , где n
— это мощность множества соединений, а m
— количество диапазонов (т.к. присутствует цикл по всем соединениям с вложенным циклом по всем диапазонам — когда мы вычисляем их вес). Можно считать, что .
Это может не выполняться для некоторых вершин дерева, близких к листьям (когда осталось уже немного соединений), но для большинства вершин это однозначно верно, причем m
во много раз меньше n.
Таким образом, сложность правила масок, как и сложность правила портов не превышает (на самом деле, она гораздо меньше). Это подтверждает то, что общая сложность построения дерева правил не превышает .
Необходимо сказать несколько слов о возможностях будущего улучшения алгоритма. Представляется разумным сравнивать вес диапазонов не точно, а допуская некоторую погрешность. Если мы имеем один диапазон максимальным весом 60 и несколько штук весом 59, то это, фактически, одна и та же частота. Уместно считать 60 и 59 равными весами. Однако точная величина допустимой погрешности неясна. Необходимо провести теоретические и практические исследования, чтобы понять, до какого момента выгодно закрывать глаза на разницу в весе диапазонов.
Перевод правила масок в текстовый формат.
Утилита iptables не позволяет задать больше одного диапазона для адреса отправителя или получателя в одном правиле. Чтобы иметь такую возможность, нужно воспользоваться разработкой под названием ipset [15], которая поставляется в виде модуля ядра Linux и является официальным дополнением к netfilter/iptables. Этот модуль, в числе прочего, решает проблему с проверкой адреса на принадлежность сразу нескольким диапазонам. Причем, по выражению создателей, реализация ipset обеспечивает «молниеносную» проверку записи на соответствие набору параметров. Поэтому далее работу с диапазонами с помощью ipset можно считать за одну проверку.
При вызове метода распечатки правила масок сначала проверяется, не является ли правило отключенным. Если да, то возвращается пустая строка. Именно поэтому во фрагменте правил, приведенном в главе об общей архитектуре генератора, нет проверки на адрес назначения в строке X
— все соединения, попавшие туда, имели внешний адрес назначения.
Если правило не отключено, то проверяется, сколько диапазонов фигурирует в его описании. При единственном диапазоне строка проверки выводится в формате «чистого» iptables. Если при этом еще и количество различных адресов во множестве равно единице, то вместо диапазона выводится точный адрес. Если диапазонов больше одного, для проверки используются возможности ipset.
10 Структура и использование генерируемых правил.
При запуске генератора пользователь должен в параметрах командной строки указать статистику, а также желаемое имя для файла правил. В дальнейшем предполагаем, что это имя — rules. Результатом работы генератора являются несколько файлов скриптов, содержащих команды для утилит iptables и ipset:
rules — файл с деревом правил на языке команд iptables. Выполнение этого файла с привилегиями администратора приведет к применению в системе содержащихся в нем правил. Перед запуском файла нужно запустить файл rules.ipset;
rules.ipset — генерирует наборы ipset, используемые в правилах. Должен быть выполнен до выполнения файла rules, иначе возникнут ошибки;
rules.rm — набор команд, предназначенный для корректного удаления из системы сгенерированных правил и наборов ipset.
Разделение команд iptables и наборов ipset на два файла может показаться не лучшей идеей. Это решение принято с целью обеспечения легкой правки файла rules администратором. В начало этого файла вынесено несколько команд, которые могут вызвать интерес (описано ниже).
Способы использования наборов правил.
Идеальным вариантом использования правил является размещение их на узле, через который непосредственно проходит трафик (на маршрутизаторе). В этом случае будет доступна полная функциональность.
Но такой вариант трудно реализуем, т.к. требует наличия ОС Linux на маршрутизаторе, что далеко не всегда возможно. Поэтому был организован и второй вариант использования, который существенно расширяет применимость правил, но несколько сужает доступную функциональность. Он основан на наличии у управляемых коммутаторов функции зеркалирования трафика с одного физического порта на другой. Используемая в сети лаборатории модель коммутатора поддерживает только зеркалирование отдельных портов, что делает невозможным анализ внутрисегментного трафика. При тестировании системы использовалось зеркалирование порта, через который анализируемый сегмент связывается с внешним миром. При использовании других моделей коммутаторов возможно полное прослушивание трафика в сегменте сети, как внутреннего, так и внешнего.
Рисунок 2. Вариант использования правил, основанный на зеркалировании пакетов.
Схема работы изображена на рисунке 2 (вариант, когда зеркалируется один порт). К порту, на который происходит зеркалирование, подключается компьютер с ОС Linux (ее можно развернуть внутри виртуальной машины) и установленными правилами. При таком использовании можно только следить за трафиком, управлять им непосредственно из правил нельзя. К тому же, возникает проблема с отслеживанием соединений.
По умолчанию сетевое устройство отбрасывает входящие пакеты, адресованные не ему (а именно таким и будет большая часть трафика при зеркалировании). Чтобы такие пакеты все-таки принимались и проходили в систему, нужно установить устройство в «неразборчивый режим» (promiscuous mode, [10]). Но даже при этом стандартное ядро Linux на ранних стадиях разбора будет отбрасывать пакеты, предназначенные для других машин. До ловушек netfilter они доходить не будут и в правилах iptables будут невидны. Текущая линейка ядер Linux имеет номер 2.6. Для ядер более ранних версий существовали патчи, решающие данную проблему. Например, нами был найден патч [17], создающий новую цепочку PROMISCUOUS, из которой можно иметь доступ к пакетам в promiscuous mode из правил iptables. Но нам не удалось заставить старые патчи работать на ядрах новой версии.
Проблема была решена с помощью создания собственного патча для ядер версии 2.6.х. Он крайне прост и лишь немного продлевает срок жизни пакетов в promiscuous mode в ядре Linux, чтобы можно было получить к ним доступ из таблицы mangle цепочки PREROUTING. К сожалению, из-за его несовершенства возможность отслеживания соединений (реализована в модуле netfilter'а под названием ip_conntrack) для таких пакетов недоступна. Причина необходимости отслеживания соединений была описана в главе, посвященной правилу портов и протоколов. Без модуля ip_conntrack соединения могут корректно отслеживаться только для протокола TCP благодаря его флагам. Поэтому при использовании набора правил через зеркалирование трафика UDP-пакеты анализируются только по адресам, но не по портам (т.е. невозможно определить, какие номера портов случайны, а какие — нет). В дальнейшем планируется создание полноценного патча для ядер Linux версии 2.6.х, который бы обеспечивал корректное отслеживание соединений для пакетов в promiscuous mode.
Структура правил.
Начальные команды файла rules не относятся к описанию дерева, а проводят подготовительные операции. Создается цепочка chain0 (корень дерева), и туда перенаправляются все нужные пакеты. Вариант использования правил задается в параметре командной строки генератора. Если это установка непосредственно на маршрутизатор, то в цепочку chain0 попадут только те пакеты, которые начинают новые соединения. Если это вариант с зеркалированием, то в chain0 попадут пакеты, которые начинают новые соединения по протоколу TCP, и все пакеты протоколов UDP и ICMP.
В следующих командах описываются цепочки chain_accept и chain_reject:
iptables -N chain_accept
iptables -A chain_accept -j ACCEPT
iptables -N chain_reject
iptables -A chain_reject -j LOG --log-level warn --log-prefix "IS:reject"
iptables -A chain_reject -j ACCEPT
В цепочку chain_accept приходят все нормальные пакеты, а в chain_reject — аномальные. Как видно, chain_accept не содержит никаких действий, а chain_reject записывает в системный журнал параметры пакетов с префиксом «IS:reject» и уровнем «warning». Администратор может изменить обработку нормальных и аномальных пакетов. Например, добавить журналирование нормального трафика или указать, что аномальный трафик должен блокироваться (сработает только в случае установки правил на маршрутизаторе).
После описания цепочек chain_accept и chain_reject начинается описание самого дерева правил. Внесение туда изменений вручную не предусматривается.
Файл rules.ipset содержит описание наборов диапазонов адресов для ipset и также не предполагает внесения изменений вручную. Названия наборов формируются по шаблону «mset» + целое число. Диапазоны в этом файле не повторяются, и нет деления на адреса отправителя и получателя (это делается в самих правилах).
Файл rules.rm удаляет правила из системы. Его изменение может понадобиться только в том случае, если в цепочки chain_accept и/или chain_reject была добавлена логика обработки, включающая в себя создание новых цепочек. В таком случае в файле rules.rm нужно прописать удаление этих цепочек, иначе придется удалять их вручную.
11 Оповещение об аномальной активности.
Для автоматического разбора системного журнала и оповещения администратора о появлении аномальной активности предусмотрен специальный скрипт. Для регулярных проверок можно настроить его периодический запуск (например, с помощью crontab [7]).
Из командной строки скрипт принимает два имени файла. Первый файл содержит журнал для анализа[2]
(он открывается только для чтения), во второй файл будет собираться информация об аномальных соединениях. В файле логов скрипт ищет записи с префиксом «IS:reject». Из их числа выбираются новые (те, которых еще нет во втором файле). Информация о них добавляется во второй файл и отправляется администратору по электронной почте. Есть возможность регулирования подробностей описания аномальных соединений. По умолчанию проверяются поля адресов и порта назначения.
12 Тестовые испытания и их результаты.
Тестирование системы проводилось на одном из сегментов локальной сети НГУ. Целью его было не только обнаружение и устранение ошибок, но и подтверждение возможности практического применения системы.
Основная статистика состояла из 292 тысяч уникальных соединений, собранных в результате непрерывного мониторинга локальной сети на протяжении недели. Построение дерева ЛРП заняло 46 минут и 35 секунд на компьютере, оснащенном процессором Intel Core2 Duo E4500 2.2ГГц и 1 Гб оперативной памяти. Такое время кажется нам удовлетворительным, учитывая то, что данную операцию нужно выполнить лишь единожды. Но если в дальнейшем это будет вызывать проблемы, имеется потенциал для улучшения данного параметра, так как серьезные работы по оптимизации по скорости построения дерева еще не проводились.
При поиске аномальной активности с помощью созданных правил удалось получить практические результаты, доказывающие полезность системы в реальных условиях, а именно выявить несколько фактов нецелевого использования ресурсов локальной сети (несанкционированные запуски клиентов пиринговых сетей на компьютерах лаборатории Parallels-НГУ).
При тестировании были замерены важные технические характеристики системы, во многом определяющие ее применимость на практике.
Количество генерируемых правил.
На начальных этапах работы были опасения, что количество правил, генерируемых программой, будет расти вместе с ростом размера статистики, что сведет на нет практическую ценность проекта. Но этого не происходит. Во время тестирования путем выбора случайных соединений из основной статистики было создано несколько дочерних статистик разных мощностей, и замерено количество правил, генерируемых для них. Результаты представлены на рисунке 3. Можно видеть, что происходит насыщение, и количество правил колеблется в пределах некоторого среднего значения, почти независимо от размера выборки.
Рисунок 3. Количество правил для статистик разных мощностей.
Может показаться, что генерируется слишком много правил (1200–1500). Однако следует учитывать, что большая их часть служит для обработки очень редко встречающихся соединений и, следовательно, пренебрежимо мало влияет на скорость анализа трафика.
Скорость анализа трафика.
С помощью iptables можно узнать, сколько раз сработало то или иное правило с момента его установки. Эта информация была использована для оценки скорости работы построенного набора правил: чем чаще соединение устанавливается, тем быстрее оно должно проходить через дерево.
Рисунок 4. Распределение соединений (в процентах от общего количества) между листьями разного уровня в дереве ЛРП.
На рисунке 4 можно наблюдать распределение соединений в процентах по листьям дерева в зависимости от уровня листа. Отражены только нормальные соединения. Для основной статистики уровни листьев дерева варьировались от 3 до 19. Как видно, в листья уровнями 9-19 за время тестирования правил пришло пренебрежимо малое количество соединений (суммарно 0,7% от всего трафика), а в листьях уровней 3 и 4 осело более 91% трафика. Из этого можно сделать вывод, что одна из главных целей — оптимизация дерева ЛРП по скорости обработки часто встречающихся соединений — была достигнута.
ЗАКЛЮЧЕНИЕ.
В ходе работы над проектом были проанализированы современные методы защиты локальных сетей. Был предложен новый подход к обеспечению безопасности и мониторингу активности в локальных сетях, полностью основанный на индивидуальных особенностях трафика каждой из них. Была поставлена задача разработать этот подход и исследовать его применимость на практике. Была выработана общая схема возможного воплощения подхода в программной системе, а также сформулированы требования к такой системе и разработаны необходимые алгоритмы. После выбора платформы и инструментария система была реализована.
Разработанное решение представляет собой комплекс утилит для операционных систем семейства Linux/UNIX. Комплекс был протестирован в локальной сети НГУ. В ходе тестовых испытаний доказана жизнеспособность выбранного подхода к обеспечению безопасности локальной сети, установлена практическая полезность системы, а также получены хорошие показатели по важнейшим ее параметрам.
Дальнейшее развитие системы будет идти по следующим направлениям:
обеспечение более дружественного интерфейса;
усовершенствование некоторых алгоритмов;
доработка варианта использования в качестве пассивного прослушивающего узла (поддержка отслеживания соединений для протоколов UDP и ICMP);
реализация поддержки дополнительных параметров соединений (учет количества соединений, объема трафика в соединениях, времени суток).
Подход, на котором основана система, не претендуют на то, чтобы заменить собой все остальные средства обеспечения бессбойной работы локальных сетей. Такой задачи не ставилось. Но успешные тестовые испытания позволяют надеяться на то, что наша разработка в будущем может занять свое место среди средств решения этой обширной задачи и быть востребованной на практике.
Литература.
1. Boris Kovalerchuk, Evgenii Vityaev and Robert Holtfreter. Correlation of Complex Evidence in Forensic Accounting Using Data Mining // Journal of Forensic Accounting 1524-5586, Vol.VIII — R.T. Edwards, Inc., 2007. — pp. 53-88.
2. Витяев Е.Е., Ковалерчук Б.К., Федотов А.М., Барахнин В.Б., Белов С.Д., Дурдин Д.С., Демин А.В. Обнаружение закономерностей и распознавание аномальных событий в потоке данных сетевого трафика // Вестник НГУ, Информационные технологии, Т. 6, вып. 2. — Новосибирск: НГУ, 2008. — с. 57-68.
3. Дурдин Д. С., Витяев Е. Е. Дополнительный Data Mining модуль для Microsoft SQL Server 2005 на основе системы Discovery // Вестник НГУ, Информационные технологии, Т. 6, вып. 2. — Новосибирск: НГУ, 2008. — с. 69-79.
4. Лбов Г.С. Методы обработки разнотипных экспериментальных данных. — Новосибирск: Наука, 1981. — 158 с.
5. Манохин А.Н. Методы распознавания, основанные на логических решающих функциях // Эмпирическое предсказание и распознавание образов. — Новосибирск, 1976. Вып. 67: Вычислительные системы. — с. 42-53.
6. Загоруйко Н.Г. Прикладные методы анализа данных и знаний. — Новосибирск: ИМ СО РАН, 1999. — 270 c.
7. Немет Э., Снайдер Г., Сибасс С., Хейн Трент Р. UNIX: руководство системного администратора. Для профессионалов. 3-е изд. — СПб.: Питер, 2002. — 928 с.
8. Бил Дж. Snort 2.1. Обнаружение вторжений. 2-е изд. — М.: Бином-пресс, 2006. — 656 с.
9. Насакин Р. Априорная подозрительность: Программные средства лечения паранойи // Компьютера. — 2006. — Т. 629, № 9. — с. 79-84.
10. Иртегов Д.В. Введение в сетевые технологии. — СПб.: БХВ-Петербург, 2004. — 560 с.
11. Стивенс У.Р. UNIX: разработка сетевых приложений. — СПб.: Питер, 2003. — 1088 с.
12. Tcpdump homepage. // [Электронный ресурс]. — Режим доступа: http://www.tcpdump.org, свободный.
13. Иртегов Д.В. Введение в операционные системы. — 2-е изд., перераб. и доп. — СПб.: БХВ-Петербург, 2008. — 1040 с.
14. Кормен Т., Лейзерсон Ч., Ривест Р.. Алгоритмы: построение и анализ.
— М.: МЦНМО, 1990. — 960 с.
15. Ipset homepage. // [Электронный ресурс]. — Режим доступа: http://ipset.netfilter.org, свободный.
16. Саттер Г. Решение сложных задач на С++. — М.: Издательский дом «Вильямс», 2008. — 400 с.
17. Zander S. Homepage. // [Электронный ресурс]. — Режим доступа: http://caia.swin.edu.au/cv/szander/netfilter.html, свободный.
Публикации
автора.
1. Савенко Д.В. Автоматическая генерация анализаторов трафика локальных сетей // Труды рабочего семинара «Наукоемкое программное обеспечение» в рамках 7-й Международной конференции памяти академика Ершова «Перспективы систем информатики», 2009 год. В печати.
2. Савенко Д.В. Построение оптимального дерева правил iptables // Труды 51-й научной конференции МФТИ «Современные проблемы фундаментальных и прикладных наук»: Часть VII. Управление и прикладная математика. Том 3. — М.: МФТИ, 2008. — с. 64-67.
3. Савенко Д.В. Автоматическая генерация анализаторов трафика локальных сетей // Материалы XLVII Международной научной студенческой конференции «Студент и научно-технический прогресс»: Информационные технологии. — Новосибирск: НГУ, 2009. — с. 46.
Приложение А.
(обязательное)
Некоторые интерфейсы системы.
В этом приложении представлены описания на языке С++ некоторых интерфейсов системы, которые описывались в тексте и могут вызвать вопросы при чтении.
Работа с базой данных: стратегии.
/* Стандартная стратегия для работы с типами в базе данных. Применима для простых типов (не содержащих указателей и сложной логики инициализации).*/
template<typename T>
class StdStrategy {
public:
/* Возвращает размер данных. */
static size_t size(const T& key) { return sizeof(key); }
/* Возвращает данные в виде константного указателя void*. Размер определется вызовом метода size(). */
static const void* data(const T& key) { return &key; }
/* Копирует данные из void* в переменную типа, с которым работает стратегия. */
static void put(T& d, const void* v, size_t size) {
assert(sizeof(d) <= size);
const T c = *(static_cast<const T*>(v));
d = c;
}
};
/* Спецификация стандартной стратегии для класса STL-строк. */
template<>
class StdStrategy<std::string> {
public:
static size_t size(const std::string& key) {
return key.length();
}
static const void* data(const std::string& key) {
return key.c_str();
}
static void put(std::string& s, const void* v, size_t size) {
s = std::string(static_cast<const char*>(v), size);
}
};
Работа с базой данных: контроллер.
/* Шаблон контроллера базы данных. Детали реализации опущены для экономии места, представлен только интерфейс.*/
template<
typename KeyType,
typename DataType,
template <typename> class KeyStrategy = StdStrategy,
template <typename> class DataStrategy = StdStrategy
>
class DBController {
public:
typedef KeyType Key;
typedef DataType Data;
/* Конструктор. Принимает имя файла базы данных. Если такого файла нет, он будет создан. */
DBController(const std::string& dbname) { ... }
/* Деструктор. Корректно закрывает базу данных. */
~DBController() { ... }
/* Возвращает данные по ключу. Выбрасывает исключение, если такого ключа нет в БД. */
Data get(const Key& key) { ... }
/* Записывает пару ключ/значение в БД. Если такой ключ уже существует, старое его значение будет утрачено. */
void put(const Key& key, const Data& data) { ... }
/* Проверяет, существует ли ключ в базе данных. */
bool exists(const Key& key) { ... }
/* Делает проход по всей базе данных и вызывает oper(key, value) для каждой пары ключ/значение. Тип Т должен быть пригоден для этой цели, иначе возникнет ошибка компиляции. */
template<typename T> void foreach(T& oper) { ... }
/* Очищает базу данных, удаляя все имеющиеся в ней пары ключ/значение. */
void truncate() {
impl_->truncate();
}
private:
/* . . . */
};
Множество пакетов.
/* Интерфейс, описывающий множество пакетов. */
class Set {
public:
/* Вернуть пакет по локальному номеру. */
virtual Packet at(SET_INDEX index) const = 0;
/* Получить размер множества. */
virtual SIZE size() const = 0;
virtual ~Set() {}
};
Правило.
// Интерфейс правила
class Rule {
public:
/* Тип итератора для доступа к подмножествам. SET_INDEX - это псевдоним типа uint32_t */
typedef std::vector<SET_INDEX>::iterator Iterator;
/* Клонирование правила */
virtual Rule* clone() const = 0;
/* Применение правила к переданному множеству. Все остальные методы, которые имеют дело с "текущим делением", не должны вызываться раньше этого. */
virtual SIZE compute(const std::auto_ptr<Set> subset) = 0;
/* Возвращает разницу в весе частей текущего деления. */
virtual SIZE getDiff() const = 0;
/* Возвращают левое и правое дочернее правило для текущего деления. */
virtual Rule* getLeftRule() const = 0;
virtual Rule* getRightRule() const = 0;
/* Возвращает "истину", если правило прошло (см. описание архитектуры генератора). */
virtual bool isPassed() const = 0;
/* Вовзращает "истину", если текущее деление невырожденное. */
virtual bool isDivided() const = 0;
/* Работа с флагом активности правила. */
virtual bool isActive() const = 0;
virtual void setActive(bool active) = 0;
/* Возвращает текущее деление в текстовом виде, пригодном для использования в командах iptables. */
virtual std::string printRule() const = 0;
/* Возвращают итераторы на начало и конец левого и правого подмножества текущего деления. */
virtual Iterator leftBegin() const = 0;
virtual Iterator leftEnd() const = 0;
virtual Iterator rightBegin() const = 0;
virtual Iterator rightEnd() const = 0;
virtual ~Rule() {}
};
Приложение Б.
(обязательное)
Файл описания диапазонов адресов.
Файл описания диапазонов адресов состоит из записей, разделенные пробельными символами. Записи могут быть двух видов:
строка «source» или «dest». Обозначает, что все последующие за этой строкой записи (пока не встретится противоположная строка, либо конец файла) относятся к адресам отправителя или получателя соответственно. Для этих двух параметров можно задать разные наборы диапазонов.
Все, что не является «source» или «dest», трактуется как пары «диапазон адресов» и «маска диапазона». Оба этих поля также могут быть разделены любыми пробельными символами. Они имеют стандартный формат записи адресов в виде четырех чисел от 0 до 255, разделенных точками (маска тоже записывается таким образом, нотация CIDR не поддерживается). Важна очередность задания параметров, поскольку именно с помощью нее система отличает диапазон от маски. Диапазон может идти вслед за флагом «source/dest», либо вслед за маской. Маска должна идти вслед за диапазоном. Флаг «source/dest» не может идти вслед за диапазоном (так как ожидается маска) — этот случай система отлавливает и сообщает об ошибке.
Пример.
source
10.3.0.0 255.255.0.0
193.124.208.0 255.255.255.0
dest
10.4.16.0 255.255.255.0
193.124.208.0 255.255.255.0
source
10.168.131.0 255.255.255.0
[1]
Класс, не хранящий данных, все методы которого — чисто виртуальные.
[2]
Имя файла с нужным журналом зависит от настроек демона syslogd. Обычно это /var/log/kern.log.