Анализ сетевого трафика: R, Zeek, SIGMA и DuckDB

Data Science в Инфобезе или observability на максималках

data engineering
pcap
трафик
DUCKDB
SIGMA
Author

i2z1@ddslab.ru

Published

April 12, 2024

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

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

Итак, задача: проанализировать дамп сетевого трафика и попытаться обнаружить вредоносную активность. Приступим!

Принципы проведения анализа или Методология исследования

Перед проведением анализа трафика сформулирую принципы, по которым я подбирал инструменты.

Поверхностный анализ существующих средств анализа сетевого трафика приведет к большому количеству существующих решений ( 1, 2, 3). Кроме того, в трафик можно смотреть глазами при помощи анализатора трафика Wireshark.

Цель проекта

  1. Не переизобретать велосипед, использовать существующие зрелые решения
  2. Автоматизировать анализ – он должен быть автоматизирован, выполняться автоматически с определенным качеством, однако позволяя контролировать отдельные процессы внутри, а также итеративно улучшать их в дальнейшем.
  3. Минимизировать операционную аналитическую деятельность в UI – аналитика должна быть воспроизводимой. Поподробнее об этом можно посмотреть здесь

В идеале – такой подход позволяет один раз провести инструментальный анализ, а дальше при изменении входных данных, автоматически получить новый результат.

Извлечение метаданных трафика с Zeek

Сетевой трафик для анализа

Где взять трафик для анализа? Если Вы уверены, что Вас атакуют хакеры, то можно его записать ;) Например, при помощи Wireshark, или в Linux – при помощи tcpdump.

Если Вы не нужны хакерам ;) Вас никто не атакует, то образцы трафика можно скачать:

  1. https://www.netresec.com/?page=MACCDC
  2. https://cyberdefenders.org/blueteam-ctf-challenges/packetmaze/

… или даже сгенерировать – https://github.com/alphasoc/flightsim

Запись трафика хранится в файлах pcap, pcapng и вариациях.

Метаданные

Для анализа нам прежде всего нужны характеристики сетевых пакетов: IP-адресы, порты, DNS и HTTP запросы и ответы и др. Для извлечения этой информации из записанного “сырого” трафика воспользуемся Zeek.

Zeek относится к классу средств IDS/NTA (Intrusion Detection System/Network Traffic Analyzer). Zeek умеет извлекать метаинформацию трафика и раскидывать ее в соответствующие папки по протоколам. При этом Zeek сохраняет связность метаинформации между уронями модели OSI, так что всегда можно проследить в каком конкретно пакете на сетевом уровне передавался тот или иной запрос протоколов прикладного уровня.

Zeek устанавливается на Linux и MacosX, если у Вас Windows, то потребуется виртуальная машина, например VirtualBox. Сайт Zeek – https://zeek.org/.

После его установки, метаданные из трафика извлекаются при помощи команды

zeek –C –r mypackets.pcap

Параметр -C отвечает за игнорирование “битых” пакетов. В результате работы Вы получите набор файлов (логов) с метаинформацией о сетевом трафике.

Язык программирования R

Язык программирования R отлично справляется с ролью аналитического ядра анализа метаинформации и служить “клеем”, связывая входы и выходы других инструментов. Для работы с R я использую RstudioIDE, и даже создаю для нее темы оформления

Для работы с сетвым анализатором Zeek, небезысвестный Боб Рудис создал для R пакет zeekr.

Для установки пакета R из Github нам понадобится другой пакет – devtools. Убедитесь, что он у Вас установлен.

После установки в консоли R пакета zeekr с помощью

devtools::install_github("https://github.com/hrbrmstr/zeekr")

можно проверить доступность Zeek – zeekr::find_zeek().

Импорт данных в R

Для сбора метаинформации из файлов с записанным трафиком в виде pcap в формат датафремов, с которым работает R, в пакете zeekr есть функция pcap_to_zeek():

library(zeekr)

loc <- zeekr::pcap_to_zeek("/path/to/file.pcap")

После извлечения метаинформации из трафика, импортируем ее в R:

zeek_list <- read_zeek_logs(loc)

zeek_list – это именованный список (list) из датафреймов Zeek.

Список элементов можно получить через names(zeek_list), а обратиться к его элементам – например, так:

zeek_list$conn

Enum network

С чего можно начать анализ?

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

Источники и приемники

Каждый узел сети является и источником сетевого трафика (source), и его получателем(destination). Узлы отправляют запросы, получают ответы.

Посмотрим структуру источников, используя библиотеку манипуляции данными dplyr:

library(dplyr)

con_df <- zeek$conn

ip_src <- con_df %>% 
  group_by(ip_src = id_orig_h) %>% 
  summarise(packets = n(), 
            orig_bytes = sum(orig_bytes),
            resp_bytes = sum(resp_bytes),
            connection_duration = mean(duration)) %>% 
  arrange(desc(packets))

ip_src
# A tibble: 5 × 5
  ip_src                    packets orig_bytes resp_bytes connection_duration
  <chr>                       <int>      <int>      <int>               <dbl>
1 45.15.89.1                  70585         NA         NA                NA  
2 45.15.89.180                  108     163473     170170                10.6
3 45.15.89.56                    22         NA         NA                NA  
4 45.15.89.33                     5         NA         NA                NA  
5 fe80::4167:163e:82a9:f2de       1         NA         NA                NA  

Почему я не конкретизирую исходные данные, которые анализирую? Потому что, это неважно – независимо от записанного трафика состав колонок логов Zeek будет один и тот же, т.е. программный код будет работать на любых данных.

Аналогично, приемники трафика (куда выкачивается трафик) можно посмотреть так:

ip_dst <- con_df %>% 
  group_by(ip_dst = id_resp_h) %>% 
  summarise(packets = n(), 
            orig_bytes = sum(orig_bytes),
            resp_bytes = sum(resp_bytes),
            connection_duration = mean(duration)) %>% 
  arrange(desc(packets))

ip_dst
# A tibble: 6 × 5
  ip_dst       packets orig_bytes resp_bytes connection_duration
  <chr>          <int>      <int>      <int>               <dbl>
1 45.15.89.33    70712         NA         NA                NA  
2 45.15.89.255       3         NA         NA                NA  
3 45.15.89.56        3        707      21330                11.8
4 224.0.0.251        1         NA         NA                NA  
5 45.15.89.1         1         NA         NA                NA  
6 ff02::fb           1         NA         NA                NA  

Сетевые порты

Давайте посмотрим какие сетевые службы используются в записанном трафике – это можно определить по номерам используемых портов

  con_df %>% 
  group_by(id_resp_h, id_resp_p) %>% 
  summarise(packets = n(), total_traffic = sum(resp_bytes)) %>% 
  filter(total_traffic != 0) %>% 
  arrange(desc(packets))
`summarise()` has grouped output by 'id_resp_h'. You can override using the
`.groups` argument.
# A tibble: 5 × 4
# Groups:   id_resp_h [2]
  id_resp_h   id_resp_p packets total_traffic
  <chr>           <int>   <int>         <int>
1 45.15.89.33        80     395      17256443
2 45.15.89.33        22     109        170126
3 45.15.89.56      4444       2           144
4 45.15.89.33         0       1           270
5 45.15.89.56      8000       1         21186

SIGMA

SIGMA – это формат описания правил детектирования событий информационной безопасности в логах информационных систем. По сути, это сигнатуры атак на наши системы. Поскольку Zeek преобразовал трафик в метаинформацию, которая по сути лог, то мы можем применить имееющиеся сигнатуры SIGMA к нашим данным и попробывать найти сетевые атаки.

Универсальность правил SIGMA заключается в том, что с помощью специального конвертера мы можем перевести любое ее правило в формат популярных SIEM систем или даже в обычный SQL запрос к базе данных, в корой мы можем хранить наши логи. Этот конвертер работает, если для него есть соответствующий плагин, который умеет переводить правила из родного формата SIGMA (это yaml) в формат интересующей нас системы анализа логов. Далее с помощью этого запросы можно обнаружить признаки атаки на нашу систему.

Посмотреть список поддерживаемых плагинов sigma можно с помощью

sigma plugin list

Нас будет интересовать плагин sqlite. Установим его:

sigma plugin install sqlite

В репозитории имеющихся правил для SIGMA https://github.com/SigmaHQ/sigma, в папке rules имеется подпапка network, а в ней – zeek. Это то, что нам нужно!

DuckDB

Чтобы воспользоваться найденными правилами детектирования, нам нужен подходящий бэкенд, который понимает SQL.

Для этого как нельзя лучше подойдет аналитическая СУБД DuckDB – https://duckdb.org/.

DuckDB – это аналог SQLite в мире анализа данных. Колоночная OLAP СУБД, представленная единым бинарником, умеет работать с данными, размер которых превышает объем RAM. Кроме того, отлично работает с данными в формате Parquet. При этом, данные могут хранится удаленно, DuckDB умеет подгружать данные по мере необходимости по HTTPS или S3.

DuckDB и R

В R использовать DuckDB возможно с через пакет duckdb. Устанавливается из CRAN при помощи install.packages("duckdb").

После установки мы можем загрузить наши логи трафика в DuckDB и затем использовать SQL запросы, которые для нас сгенерирует конвертор SIGMA из соответсвующих правил.

Приступаем! Скопировать датафрейм R в базу данных можно либо с использованием функции copy_to(), либо dbWriteTable():

library(duckdb)
library(dplyr)

con <- dbConnect(duckdb::duckdb(), dbdir = ":memory:")

copy_to(con, zeek$conn, name = "conn")
copy_to(con, zeek$dns, name = "dns")

dbWriteTable(con, "files", zeek$files)
dbWriteTable(con, "http", zeek$http)
dbWriteTable(con, "ssh", zeek$ssh)
dbWriteTable(con, "packet_filter", zeek$packet_filter)

Задействуем SIGMA

Теперь, когда наши данные внутри DuckDB, можно перейти к генерации SQL запросов, которые помогут выявить на основе наших данных возможные атаки.

Для генерации SQL запроса используем команду

sigma convert --target sqlite /path/to/rules/network/zeek/myrule.yml
Типы правил SIGMA

Обратите внимание: каждое правило SIGMA может применяться только к одному из тех датафреймов, которые мы получили в результате работы Zeek. К какому конкретно, определяет поле type в самом YAML правиле.

Получив на выходе SQL запрос, можем теперь его испытать. Запросы к БД можно выполнять с помощью функции dbGetQuery(). При этом, нужно заполнить заглушку <TABLE_NAME> в запросе правильным названием БД. Если правило создано для dns логов, то применять мы его будем к таблице dns.

sql_req <- "SELECT * FROM dns WHERE query LIKE '%seed%' ESCAPE '\' AND query LIKE '%.nkn.org%' ESCAPE '\'"

dbGetQuery(con, sql_req)

На выходе получили таблицу с 0 строк, что значит что результатом запроса является пустое множество. То есть признаков компрометации нет!

Для автоматизации всего процесса достаточно обернуть наш код в функции и прогнать по всем имеющимся сигнатурам. Этот вопрос хочу осветить в будущем отдельно.