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

Квантовые вычисления предлагают альтернативные способы вычислений с использованием квантовых явлений. В последнее время вокруг этой технологии много ажиотажа и появилось довольно много алгоритмов квантового машинного обучения (QML). Однако поиск и обучение моделей квантового машинного обучения, демонстрирующих высокий уровень производительности, — непростая задача. Найти модель QML, которая обеспечивает такой уровень производительности на реальном наборе данных, действительно выдающаяся задача. Давайте взглянем на одну из этих моделей и ее реализацию в PennyLaneи Tensorflow. Мы будем обучать гибридную квантовую нейронную сеть (H-QNN) на наборе данных атак типа DDoS из статьи Квантовое машинное обучение для обнаружения вторжений распределенных атак типа «отказ в обслуживании: сравнительный обзор» [ 1]. Код в этой статье — это немного измененная версия из репозитория, приложенного к статье. В первой части мы сосредоточимся на предварительной обработке данных. Если вы хотите пропустить всю очистку данных, перейдите к следующей части, где мы реализуем и обучаем реальную модель на этих данных.

DDoS-атаки:

Распределенный отказ в обслуживании (DDoS) – это тип кибератаки, когда множество хостов пытаются подключиться к серверу жертвы, пока он не выйдет из строя и не сможет обработать законный запрос. . Это скоординированное действие из одной центральной точки, обычно выполняемое с помощью вредоносного программного обеспечения, которое заражает устройства неосведомленных владельцев:

Распознавание этого типа атаки важно на ранних этапах подключения к серверу. Это не позволяет злоумышленникам забирать ресурсы, перегружать и, наконец, закрывать веб-сайт или приложение. Нам нужны небольшие надежные модели, чтобы быстро классифицировать, например, пользовательский запрос как безопасный или потенциальный DDoS, не замедляя весь процесс.

Дополнительная литература: Что такое распределенная атака типа «отказ в обслуживании

Предварительная обработка данных:

Мы используем набор данных оценки DDoS (CIC-DDoS2019), который включает в себя результаты анализа сетевого трафика с помеченными потоками на основе метки времени, исходного и целевого IP-адресов, исходных и целевых портов, протоколов и атак. Все точки данных помечены как безопасные (бит данных представляет любую угрозу) и простой протокол обнаружения служб (тип DDoS-атаки). Чтобы загрузить данные:

После этого распакуйте и найдите в каталоге 01-12 DrDoS_SSDP.csv файл. Это наши данные.

Прежде чем переходить к кодированию, убедитесь, что у вас настроена среда. Вот наш стек:

Начнем с импорта всех необходимых пакетов:

# Data processing:
import numpy as np
import pandas as pd

# Utils:
import sklearn.decomposition
from sklearn.preprocessing import StandardScaler, MinMaxScaler

# Visuals:
import seaborn as sns
import matplotlib.pyplot as plt

Далее настроим глобальные переменные:

# Set random seed:
GLOBAL_SEED = 419514
np.random.seed(GLOBAL_SEED)

# Computation data format:
comp_dtype = 'float32'

# Increase pandas printing limits
pd.set_option('display.max_columns', 90)
pd.set_option('display.width', 1000)
pd.set_option('display.max_rows', 90)

Посмотрим размер наших данных:

data_read_path = os.path.join(".", "data", "raw", "CSV-01-12", "01-12", "DrDoS_SSDP.csv")
data_df = pd.read_csv(data_read_path)
data_df.drop(labels=['Unnamed: 0'], axis=1, inplace=True)
data_df.shape
(2611374, 87)

Это довольно обширный набор данных с более чем 2 миллионами точек данных и 86 столбцами, не считая меток набора данных. Для небольшой надежной модели нам нужно будет выбрать только несколько функций в качестве входных данных. Для этого избавимся от:

  • Столбцы только с одним значением
  • Нерелевантные столбцы — временная метка, порт назначения, идентификатор источника. Мы можем использовать более сложные методы для извлечения полезной информации (например, пометить точки данных по часам из временных меток, чтобы увидеть корреляцию времени суток с атаками), но для простоты мы оставим это. Однако читатель волен экспериментировать. Возможно, это поможет обучить модель.
  • Столбцы со значениями NaN
  • Коррелированные столбцы — мы избавимся от столбцов с коэффициентом корреляции выше определенного порога.

Давайте получим базовую информацию о каждом столбце. Поскольку результаты довольно обширны; мы напечатаем только несколько.

def first_unique_values(column):
    unique_values = column.value_counts().index.values
    
    if column.isna().sum()>0:
        unique_values = np.append(np.nan, unique_values)
    sorted_unique_values = unique_values[:min(5, unique_values.shape[0])]
    
    return sorted_unique_values

def gen_basic_info_dataframe(df, typical_values = True):
    '''
        Making DataFrame with:
         -number of unique values
         -data type
         -percentage of NaN values 
         -list with max. 5 most common values in the column. 
            If NaN occur in the column it's first element in this list, 
            even if it's not the most common value.
    '''
    basic_info=pd.DataFrame(df.nunique(), columns=['num_unique_values'])

    basic_info['data_type']= df.dtypes
    basic_info['NaN_percentage'] = (df.isna().sum())*100/df.shape[0]
    if typical_values:
        basic_info['typical_values'] = df.apply(first_unique_values, axis=0)
    
    return basic_info
basic_info = gen_basic_info_dataframe(data_df)
basic_info[20:25]

Мы можем видеть некоторую функцию NaNs в функции Flow Bytes/s, мы исключим этот столбец из нашего обучающего ввода:

data_df.drop(labels=['Flow Bytes/s'], axis=1, inplace=True)

Теперь удалим столбцы с «неактуальной» информацией:

irrelevant_columns = [
    'Flow ID',
    ' Source IP',
    ' Source Port',
    ' Destination IP',
    ' Destination Port',
    ' Timestamp',
    'SimillarHTTP',
    ]

data_df.drop(labels=irrelevant_columns, axis=1, inplace=True)

После этого удалим столбцы с одним уникальным значением:

# get these columns names as a list:
one_value_columns = basic_info[basic_info['num_unique_values']==1].index.tolist()
print("Columns to drop:\n", one_value_columns)

data_df.drop(labels=one_value_columns, axis=1, inplace=True)
Columns to drop:
 [' Bwd PSH Flags', ' Fwd URG Flags', ' Bwd URG Flags', 'FIN Flag Count', ' PSH Flag Count', ' ECE Flag Count', 'Fwd Avg Bytes/Bulk', ' Fwd Avg Packets/Bulk', ' Fwd Avg Bulk Rate', ' Bwd Avg Bytes/Bulk', ' Bwd Avg Packets/Bulk', 'Bwd Avg Bulk Rate']

Наконец, мы исключаем коррелированные столбцы. Сгенерируем корреляционную матрицу:

corr_matrix = data_df.corr(method='pearson')

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

fig, ax = plt.subplots(figsize=(25, 25))
sns.heatmap(corr_matrix,
            square=True, 
            annot=True, 
            ax=ax)
plt.show()

Давайте сгенерируем пары для исключения из матрицы. Мы установим порог коэффициента корреляции на 0.94:

threshold = 0.94

filtered_corr_matrix = corr_matrix[np.abs(corr_matrix)>threshold]
unstack_f_cm = filtered_corr_matrix.unstack().dropna().reset_index()

# remove self-correlations:
corr_pairs = unstack_f_cm[unstack_f_cm['level_0']!=unstack_f_cm['level_1']].reset_index().drop(labels=['index'], axis=1)

print(f'{len(corr_pairs)} correlations between columns with coeff. over {threshold}.')
# set columns to drop:
corr_columns_to_drop = []
for id, row in corr_pairs.iterrows():
    if not (row['level_0'] in corr_columns_to_drop) and not (row['level_1'] in corr_columns_to_drop):            
        corr_columns_to_drop.append(row['level_0'])

print(f'{len(corr_columns_to_drop)} columns to drop.')
data_df.drop(labels=corr_columns_to_drop, axis=1, inplace=True)
124 correlations between columns with coeff. over 0.94.
31 columns to drop.

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

Подготовка тренировочных данных:

Во-первых, нам нужно уменьшить количество выборок в нашем наборе данных. Поскольку наша квантовая модель значительно медленнее, чем классическая нейронная сеть, а обучение всех данных заняло целую вечность, мы взяли 10,000 выборки из более чем 2,600,000. Набор данных сильно несбалансирован (всего ~0.03% точек данных, помеченных как BENIGN), мы возьмем все образцы, помеченные как не представляющие угрозы, и случайным образом выберем остальные образцы, чтобы получить общее число 10,000.

n_samples = 10000

Мы выберем случайным образом n выборок из более чем 2 600 000 строк:

  • все (763) образцы помечены как "BENIGN"
  • n-763 образцов с тегом DrDoS_SSDP
# choose indices from both classes:
benign_ids = data_df[data_df[' Label']=='BENIGN'].index
ddos_ids = data_df[data_df[' Label']=='DrDoS_SSDP'].index

ddos_samples_ids = np.random.choice(ddos_ids, (n_samples-763))
benign_samples_ids = np.array(benign_ids)
# merge chosen indices:
samples_ids = np.concatenate((ddos_samples_ids, benign_samples_ids))

selected_data_df = data_df.iloc[samples_ids]
selected_data_df[' Label'].value_counts()
DrDoS_SSDP    9237
BENIGN         763
Name:  Label, dtype: int64

Наконец, нам нужно изменить метки со строки на целые числа, где 0s означает безопасную точку данных, а 1s означает DDoS-атаку:

y = np.array(selected_data_df[' Label'].astype('category').cat.codes.astype(int))

Извлечение функций PCA:

Затем нам нужно уменьшить наши входные данные для модели. Наш набор данных, даже с ранее удаленными коррелированными столбцами, имеет 35 функций. Чтобы создать надежную квантовую модель, нам нужно выполнить извлечение признаков. С помощью PCA, установленного на 10,000 ранее выбранных образцах, мы можем перейти к 2 характеристикам в диапазонах от -1до 1.

n_features = 2

# Standardize all the features:
x = StandardScaler().fit_transform(np.array(selected_data_df.drop(columns=[' Label'], inplace=False)))

# PCA fitting and transforming the data:
pca = sklearn.decomposition.PCA(n_components=n_features)
pca.fit(x)
x = pca.transform(x)

# Normalize the output to the range (-1, +1):
minmax_scale = MinMaxScaler((-1, 1)).fit(x)
x = minmax_scale.transform(x)
x = x.astype(comp_dtype)

# Plot results:
for k in range(0, 2):
    x_axis_data = x[np.array(y) == k, 0]
    y_axis_data = x[np.array(y) == k, 1]
    label = 'Benign' if k == 0 else 'DDoS'
    print(f'{label}: {x_axis_data.shape}')
    plt.scatter(x_axis_data, y_axis_data, label=label)

plt.title("DDoS_SSDP Dataset (Dimensionality Reduced With PCA)")
plt.legend()
plt.show()
Benign: (763,)
DDoS: (9237,)

Вот и все! В следующей части мы обучим на этих данных гибридную модель квантовой нейронной сети.

Краткое содержание:

В этой статье мы предварительно обработали табличный набор данных о кибербезопасности. Мы подготовили данные для обучения небольших квантовых моделей с удалением некоторых функций и выполнением анализа основных компонентов (PCA).

Использованная литература:

[1] Квантовое машинное обучение для обнаружения распределенных атак типа «отказ в обслуживании: сравнительный обзор», E. Д. Паярес и Х. К. Мартинес-Сантос, 2021 г.