Анализ угроз ФСТЭК

Jan. 13, 2020

Воздействие угроз на КЦД.png

Анализ угроз ФСТЭК с помощью библиотеки pandas

Проанализируем модель угроз, содержащуюся в банке данных угроз ФСТЭК (доступную по адресу https://bdu.fstec.ru/files/documents/thrlist.xlsx ). Не будем анализировать её содержание и описание. Проанализируем только данные которые она содержит без критики её содержания. Для анализа будем испольовать общедоступные инструменты Python-библиотеку для анализа данных Pandas. Графики будем отрисовывать с помощью Python-библиотеки визуализации данных matplotlib.

from openpyxl import load_workbook
import pandas as pd
import matplotlib.pyplot as plt

По ссылке приведенной ниже скачиваем файл thrlist.xlsx, содержащий все актуальные угрозы. Далее для анализа исспользуем файл от 09.01.2020. Я удалил не нужную первую строку файла все остальное не менял. Далее читаем файл с помощью функции read_excel.

df=pd.read_excel('./Downloads/thrlist.xlsx')

Переименуем все столбцы для удобного обращения с данными:

df.columns = ['id', 'name', 'description', 'threat_source', 'object', 'conf','integrity', 'availability','date_include', 'data_change']

Проверим типы данных:

df.dtypes

id                        int64
name                     object
description              object
threat_source            object
object                   object
conf                      int64
integrity                 int64
availability              int64
date_include     datetime64[ns]
data_change      datetime64[ns]
dtype: object

Важно проверить качество данные которые у Вас есть с помощь встроенных метода и isna() для определения пропущенных значений.

print(df.isna().sum())

id               0
name             0
description      0
threat_source    2
object           0
conf             0
integrity        0
availability     0
date_include     0
data_change      0
dtype: int64

Для проведения дальнейших расчетов нам необходимо вычилить следующие данные:

Видим что в столбце threat_source есть 2 незаполненных значения. Нам нужно либо их отбросить либо заполнить. Посмотрим какие это угрозы:

lost_treat_source=df[df['threat_source'].isna() == True]
print(lost_treat_source)

id                                               name  \
141  142  Угроза приостановки оказания облачных услуг вс...   
147  148  Угроза сбоя автоматического управления системо...   

                                           description threat_source  \
141  Угроза заключается в возможности снижения каче...           NaN   
147  Угроза заключается в возможности возникновения...           NaN   

                                                object  conf  integrity  \
141  Системное программное обеспечение, аппаратное ...     0          0   
147  Информационная система, система разграничения ...     1          0   

     availability date_include data_change  
141             1   2015-03-20  2019-02-11  
147             1   2015-03-20  2019-02-11

Итак мы имеем дело с 142 и 148 угрозами из банка данных ФСТЭК: 142 "Угроза приостановки оказания облачных услуг вследствие технических сбоев" Угроза заключается в возможности снижения качества облачных услуг (или даже отказа в их оказании конечным потребителям) из-за возникновения технических сбоев хотя бы у одного из поставщиков облачных услуг (входящих в цепь посредников при оказании облачных услуг их конечному потребителю), а также из-за возникновения существенных задержек или потерь в каналах передачи данных, арендуемых потребителем или поставщиками облачных услуг. Данная угроза обусловлена слабостями процедуры контроля за выполнением технического обслуживания и соблюдением режимов функционирования технических средств облачной информационной системы. Реализация данной угрозы возможна при условии отсутствия механизмов резервирования средств обработки, хранения и передачи информации, входящих в состав облачной информационной системы. В данном случае видимо можно рассматривать Внешнего нарушителя со средним потенциалом - работника поставщика облачных усуг.

148 "Угроза сбоя автоматического управления системой разграничения доступа хранилища больших данных" Угроза заключается в возможности возникновения ситуаций, связанных c ошибками автоматического назначения пользователям прав доступа (наделение дополнительными полномочиями, ошибочное наследование, случайное восстановление «неактивных» учётных записей т.п.). Данная угроза обусловлена слабостями мер контроля за большим количеством (от тысячи, а в некоторых случаях и до нескольких миллионов) учётных записей пользователей со стороны администраторов безопасности. Реализация данной угрозы возможна при условии возникновения сбоев или ошибок в работе системы разграничения доступа хранилища больших данных В данном случае речь идет о Внутреннем нарушителе со средним потенциалом - администратором безопасности. Поясненеие: Это не администратор системы и не разработчик поэтому я оценил его потенциал как средний.

Добавим для 142 угрозы Внешнего нарушителя со средним потенциалом, а для 148 Внутреннего нарушителя со средним потенциалом.

df.loc[141, 'threat_source'] = 'Внешний нарушитель со средним потенциалом'
df.loc[147, 'threat_source'] = 'Внутренний нарушитель со средним потенциалом'
print(df.isna().sum())

id               0
name             0
description      0
threat_source    0
object           0
conf             0
integrity        0
availability     0
date_include     0
data_change      0
dtype: int64

Посчитаем некоторые значения для построения графиков:

all_threat_count = len(df)
print('Количество угроз в текущей базе: '+ str(all_threat_count))

threat_C= len(df[df['conf']==1])
print('Количество угроз, воздействующих на конфиденциальность: ' + str(threat_C))

threat_C_only= len(df[(df['conf']==1) & (df['integrity']==0) & (df['availability']==0)])
print('Количество угроз, воздействующих только на конфиденциальность: ' + str(threat_C_only))

threat_I= len(df[df['integrity']==1])
print('Количество угроз, воздействующих на целостность: ' + str(threat_I))

threat_I_only= len(df[(df['conf']==0) & (df['integrity']==1) & (df['availability']==0)])
print('Количество угроз, воздействующих только на целостность: ' + str(threat_I_only))

threat_A= len(df[df['availability']==1])
print('Количество угроз, воздействующих на доступность: ' + str(threat_A))

threat_A_only= len(df[(df['conf']==0) & (df['integrity']==0) & (df['availability']==1)])
print('Количество угроз, воздействующих только на доступность: ' + str(threat_A))

threat_all_CIA= len(df[(df['conf']==1) & (df['integrity']==1) & (df['availability']==1)])
print('Количество угроз, воздействующих на КЦД: ' + str(threat_all_CIA))

Количество угроз в текущей базе: 216
Количество угроз, воздействующих на конфиденциальность: 141
Количество угроз, воздействующих только на конфиденциальность: 39
Количество угроз, воздействующих на целостность: 134
Количество угроз, воздействующих только на целостность: 14
Количество угроз, воздействующих на доступность: 154
Количество угроз, воздействующих только на доступность: 154
Количество угроз, воздействующих на КЦД: 81

Ниже приведена круговая диаграмма распределением угроз по воздействию на конфиденциальность целостность и доступность(одна угроза может воздействовать на разные свойства информации в том числе на все).

no_CIA= all_threat_count-(threat_all_CIA+threat_C_only+threat_I_only+threat_A_only)
    vals = [no_CIA, threat_all_CIA, threat_C_only, threat_I_only, threat_A_only ]
    labels = ["Другие", "Воздействующие на КЦД", "Только конфидециальности", "Только целостности", "Только доступность"]
    fig, ax = plt.subplots()
    ax.pie(vals, labels=labels, autopct='%1.1f%%')
    ax.axis("equal")
    plt.title('Процентное соотношение угроз по воздействию на КЦД')

Процентное соотношение угроз по воздействию на КЦД.png

Теперь нарисуем диаграмму Эйлера-Венна распределения угроз по воздействию на КЦД.

#пересечение множеств по КЦД
from matplotlib_venn import venn3
import matplotlib
from matplotlib.pyplot import figure

font = {'family' : 'sans',
        'size'   : 12}
matplotlib.rc('font', **font)
figure(num=None, figsize=(10, 6), dpi=100, facecolor='w', edgecolor='k')

# пересечение угроз конфиденциальности и целостности
threat_C_I = len(df[(df['conf']==1) & (df['integrity']==1) & (df['availability']==0)])

# пересечение угроз конфиденциальности и доступности
threat_C_A = len(df[(df['conf']==1) & (df['integrity']==0) & (df['availability']==1)])

# пересечение угроз целостности и доступности
threat_I_A = len(df[(df['conf']==0) & (df['integrity']==1) & (df['availability']==1)])

venn3(subsets = (threat_C_only, threat_I_only, threat_C_I, threat_A_only,threat_C_A,threat_I_A,threat_all_CIA), 
      set_labels = ('Угрозы конфиденциальности', 'Угрозы целостности', 'Угрозы доступности'), alpha = 0.4 )
plt.title('Воздейсвие угроз ФСТЭК на КЦД')

Воздействие угроз на КЦД.png

Рассчитаем значения по источнику уроз из банка данных угроз ФСТЭК для использования в дальнейшем анализе.

intruder_min_in_only = len(df[df['threat_source']=='Внутренний нарушитель с низким потенциалом'])
print('Количество угроз только с источником внутренний нарушитель с низким потенциалом: ' + str(intruder_min_in_only))

intruder_min_in_all = len(df[df['threat_source'].str.contains("Внутренний нарушитель с низким потенциалом", na=False)])
print('Общее количество угроз внутреннего нарушителя с низким потенциалом: ' + str(intruder_min_in_all))

intruder_mid_in_only = len(df[df['threat_source']=='Внутренний нарушитель со средним потенциалом' ])
print('Количество угроз только с источником внутренний нарушитель со средним потенциалом: ' + str(intruder_mid_in_only))

intruder_mid_in_all = len(df[df['threat_source'].str.contains('Внутренний нарушитель со средним потенциалом', na=False) ])
print('Общее количество угроз с источником внутренний нарушитель со средним потенциалом: ' + str(intruder_mid_in_all))

intruder_high_in_only = len(df[df['threat_source']=='Внутренний нарушитель с высоким потенциалом'])
print('Количество угроз внутреннего нарушителя с высоким потенциалом: ' + str(intruder_high_in_only))

intruder_high_in_all = len(df[df['threat_source'].str.contains('Внутренний нарушитель с высоким потенциалом', na=False)])
print('Общее количество угроз с источником внутреннего нарушителя с высоким потенциалом: ' + str(intruder_high_in_all))


intruder_min_out_only = len(df[df['threat_source']=='Внешний нарушитель с низким потенциалом'])
print('Количество угроз только с источником внешний нарушитель с низким потенциалом: ' + str(intruder_min_out_only))

intruder_min_out_all = len(df[df['threat_source'].str.contains("Внешний нарушитель с низким потенциалом", na=False)])
print('Общее количество угроз внешний нарушителя с низким потенциалом: ' + str(intruder_min_out_all))

intruder_mid_out_only = len(df[df['threat_source']=='Внешний нарушитель со средним потенциалом' ])
print('Количество угроз только с источником внешний  нарушитель со средним потенциалом: ' + str(intruder_mid_out_only))

intruder_mid_out_all = len(df[df['threat_source'].str.contains('Внешний нарушитель со средним потенциалом', na=False) ])
print('Общее количество угроз с источником внешний нарушитель со средним потенциалом: ' + str(intruder_mid_out_all))

intruder_high_out_only = len(df[df['threat_source']=='Внешний нарушитель с высоким потенциалом'])
print('Количество угроз внешний нарушителя с высоким потенциалом: ' + str(intruder_high_out_only))

intruder_high_out_all = len(df[df['threat_source'].str.contains('Внешний нарушитель с высоким потенциалом', na=False)])
print('Общее количество угроз с источником внешний нарушителя с высоким потенциалом: ' + str(intruder_high_out_all))

Количество угроз только с источником внутренний нарушитель с низким потенциалом: 35
Общее количество угроз внутреннего нарушителя с низким потенциалом: 91
Количество угроз только с источником внутренний нарушитель со средним потенциалом: 16
Общее количество угроз с источником внутренний нарушитель со средним потенциалом: 53
Количество угроз внутреннего нарушителя с высоким потенциалом: 3
Общее количество угроз с источником внутреннего нарушителя с высоким потенциалом: 3
Количество угроз только с источником внешний нарушитель с низким потенциалом: 36
Общее количество угроз внешний нарушителя с низким потенциалом: 87
Количество угроз только с источником внешний нарушитель со средним потенциалом: 26
Общее количество угроз с источником внешний нарушитель со средним потенциалом: 64
Количество угроз внешний нарушителя с высоким потенциалом: 8
Общее количество угроз с источником внешний нарушителя с высоким потенциалом: 12

Отобразим данные ввиде круговой диаграммы с отображением процентного соотношения угроз в зависимости от источника.

no_individyual= all_threat_count-(intruder_min_in_only+intruder_mid_in_only+intruder_high_in_only+intruder_min_out_only+intruder_mid_out_only+intruder_high_out_only)
vals = [no_individyual, intruder_min_in_only, intruder_mid_in_only, intruder_high_in_only, intruder_min_out_only, intruder_mid_out_only, intruder_high_out_only]
labels = ["Другие сочетания", "Вн. нар. низкий пот.", "Вн. нар. средний пот.", "Вн. нар. высокий пот.", "Внеш. нар. низкий пот.", "Внеш. нар. средний пот.", "Внеш. нар. высокий пот."]
fig, ax = plt.subplots()
ax.pie(vals, labels=labels, autopct='%1.1f%%', wedgeprops={'lw':1, 'ls':'--','edgecolor':"k"}, rotatelabels=False)
ax.axis("equal")
plt.title('Источники угроз в базе данных угроз ФСТЭК')

Источники угроз круговая диаграмма.png

Поскольку угрозы могут иметь несколько источников то покажем количество угроз из различных источников.

vals = [intruder_min_in_all, intruder_mid_in_all, intruder_high_in_all, intruder_min_out_all, intruder_mid_out_all, intruder_high_out_all]
labels = [ "Вн. нар. низкий пот.", "Вн. нар. средний пот.", "Вн. нар. высокий пот.", "Внеш. нар. низкий пот.", "Внеш. нар. средний пот.", "Внеш. нар. высокий пот."]
plt.barh(labels , vals)
plt.title('Количество угроз из различных источников')
plt.ylabel('Источники угроз')
plt.xlabel('Количество угроз')

Количество угроз из различных источников.png

Теперь нарисуем диаграмму Эйлера-Венна распределения угроз по источникам в зависимости от статуса внешний или внутренний нарушитель.

#пересечения внешних с внутренними.
from matplotlib_venn import venn2
figure(num=None, figsize=(10, 6), dpi=100, facecolor='w', edgecolor='k')
#все угрозы с внутренними нарушителями
all_in_threat = len(df[df['threat_source'].str.contains("Внутренний", na=False)])

#все угрозы с внешними нарушителями
all_out_threat = len(df[df['threat_source'].str.contains("Внешний", na=False)])

#все угрозы с внешними нарушителями
all_cross_threat = len(df[(df['threat_source'].str.contains("Внешний", na=False)) & (df['threat_source'].str.contains("Внутренний", na=False))])

venn2(subsets = (all_in_threat, all_out_threat, all_cross_threat), set_labels = ('Внутренний нарушитель', 'Внешний нарушитель'))

plt.title('Отношения между внутренними и внешними нарушителями')

Внутренние и внешние нарушители ФСТЭК.png

Покажем равзитие банка данных угроз во времени используя данные из банка данных. Так можно посмотреть с какой интенсивностью пополняется банк данных угроз ФСТЭК в различные годы.

from pandas.plotting import register_matplotlib_converters
plt.plot('date_include', 'id', data=df, color='tab:green')

plt.title('Пополнение угроз в банке данных угроз')
plt.ylabel('Количество угроз')
plt.xlabel('Год')

пополнение угроз в банке данных угроз.png

Поскольку база пополняется не часто то мы можем показать на рисунке конкретные даты пополнения и изменения угроза банка данных угроз ФСТЭК.

group_by_day = df.groupby([df['date_include'].dt.date]).count()
date= group_by_day.date_include

date.plot(kind='bar')
plt.ylabel('Количество угроз')
plt.xlabel('Дата добавления')
plt.title('Добавление угроз в базу данных угроз ФСТЭК')

добавление угроз в базу данных угроз ФСТЭК.png

group_by_day = df.groupby([df['data_change'].dt.date]).count()
date= group_by_day.date_include

date.plot(kind='bar')
plt.ylabel('Количество угроз')
plt.xlabel('Дата изменения')
plt.title('Даты изменений угроз в базе данных угроз ФСТЭК')

изменение банка данных угроз ФСТЭК.png

Далее необходимо проанализровать данные по Объекту воздействия. После его просмотра стало ясно, что значений там много а разделить их по разделителю(например, запятой) не получится. В столбце Объект воздействия ('object') имеются следующие проблемы: 1) Перечисления в скобках через запятую (если разделять данные по запятой то разделиться и содержимое скобок); 2) Деепричастные обороты; 3) Дублирование данных (например, написание через е и ё); К счастью таких данных не так много, поэтому можно не заморачиваться на регулярных выражениях а просто точечно поменять нужные ячейки.

Заменить объект 79 угрозы с "Виртуальные устройства хранения, обработки и передачи данных" на "Виртуальные устройства хранения/обработки и передачи данных"

Заменить описание объекта угрозы 164 "Облачная инфраструктура, созданная с использованием технологий виртуализации" на "Облачная инфраструктура, созданная с использованием технологий виртуализации".

Заключим деепричастный оборот в Объекте воздействия угрозы 180 в скобки: с "Технические средства воздушного кондиционирования, включая трубопроводные системы для циркуляции охлаждённого воздуха в ЦОД, программируемые логические контроллеры, распределённые системы контроля, управленческие системы и другие программные средства контроля", на : "Технические средства воздушного кондиционирования (включая трубопроводные системы для циркуляции охлаждённого воздуха в ЦОД), программируемые логические контроллеры, распределённые системы контроля, управленческие системы и другие программные средства контроля"

Заключим деепричастный оборот в Объекте воздействия угрозы 198 в скобки, поменяем: с "Система управления доступом, встроенная в операционную систему компьютера (программное обеспечение)" на "Система управления доступом (встроенная в операционную систему компьютера (программное обеспечение))".

Деепричастный оборот "Информационная система, иммигрированная в облако," который часто встречается в модели (например, угрозы 56, 134, 138, 141) заменим на "Информационноая система (иммигрированная в облако)".

Для угрозы 213 напишем "учетные данные пользователя" как в остальных случаях в таблице через букву Ё "учётные данные пользователя". Заменим с: "Системное программное обеспечение, микропрограммное обеспечение, учетные данные пользователя" на: "Системное программное обеспечение, микропрограммное обеспечение, учётные данные пользователя".

Для угрозы 210 необходимо поменять описание объекта "Аппаратное устройство, микропрограммное, системное и прикладное программное обеспечение", т.к. слово обеспечение относится к "микропрограммное, системное и прикладное" заменим на "Аппаратное устройство, микропрограммное обеспечение, системное программное обеспечение, прикладное программное обеспечение"

для угрозы 197 необходимо поменять описание объекта с "Информация, хранящаяся на компьютере во временных файлах (программное обеспечение)" на Информация (хранящаяся на компьютере во временных файлах (программное обеспечение))

для угрозы 170 необходимо поменять описание объекта с "Объект файловой системы" на "Объекты файловой системы" как в остальных случаях.

для угрозы 89 необходимо поменять описание объекта с "Системное программное обеспечение, использующее реестр, реестр" на "Системное программное обеспечение (использующее реестр), реестр"

Все перечисления в скобках сделаем через союз "и": Угроза 202 "Мобильные устройства (аппаратное устройство, программное обеспечение)" на "Мобильные устройства (аппаратное устройство и программное обеспечение)"

df.loc[179, 'object'] = 'Технические средства воздушного кондиционирования (включая трубопроводные системы для циркуляции охлаждённого воздуха в ЦОД), программируемые логические контроллеры, распределённые системы контроля, управленческие системы и другие программные средства контроля'
df.loc[212, 'object'] = 'Системное программное обеспечение, микропрограммное обеспечение, учётные данные пользователя'
df.loc[201,'object'] = 'Мобильные устройства (аппаратное устройство и программное обеспечение)'
df.loc[209,'object'] = 'Аппаратное устройство, микропрограммное обеспечение, системное программное обеспечение, прикладное программное обеспечение'
df.loc[197,'object'] = 'Система управления доступом (встроенная в операционную систему компьютера (программное обеспечение))'
df.loc[196,'object'] = 'Информация (хранящаяся на компьютере во временных файлах (программное обеспечение))'
df.loc[163,'object'] = 'Облачная инфраструктура (созданная с использованием технологий виртуализации)'
df.loc[198,'object'] = 'Мобильное устройство и запущенные на  нем приложения (программное обеспечение и аппаратное устройство)'
df.loc[79,'object'] =  'Виртуальные устройства хранения/обработки и передачи данных'
df.loc[169,'object'] = 'Объекты файловой системы'
df.loc[88,'object'] = 'Системное программное обеспечение (использующее реестр), реестр'

df['object'] = df['object'].str.replace('Информационная система, иммигрированная в облако', 'Информационноая система (иммигрированная в облако)', regex=False, case = False)
df['object'] = df['object'].str.replace('информационная система, иммигрированная в облако', 'информационноая система (иммигрированная в облако)', regex=False, case = False)
df['object'] = df['object'].str.replace('информационная система, иммигрированная в облако,', 'информационноая система (иммигрированная в облако),', regex=False, case = False)

Мы нормализовали данные теперь разобьём их по объектам воздействия.Для этого используем следующую функцию:

def tidy_split(df, column, sep='|', keep=False):
    """
    Split the values of a column and expand so the new DataFrame has one split
    value per row. Filters rows where the column is missing.

    Params
    ------
    df : pandas.DataFrame
        dataframe with the column to split and expand
    column : str
        the column to split and expand
    sep : str
        the string used to split the column values
    keep : bool
        whether to retain the presplit value as it own row

    Returns
    -------
    pandas.DataFrame
        Returns a dataframe with the same columns as `df`.
    """
    indexes = list()
    new_values = list()
    df = df.dropna(subset=[column])
    for i, presplit in enumerate(df[column].astype(str)):
        values = presplit.split(sep)
        if keep and len(values) > 1:
            indexes.append(i)
            new_values.append(presplit)
        for value in values:
            indexes.append(i)
            new_values.append(value)
    new_df = df.iloc[indexes, :].copy()
    new_df[column] = new_values
    return new_df
extended_df = tidy_split(df, 'threat_source', sep=', ')
extend_df = tidy_split(extended_df, 'object', sep=', ')
extend_df['object'] = extend_df['object'].str.capitalize()

Теперь наша талица содержит 760 угроз, каждая строка указывает на конкретный вид нарушителя и объект воздействия из базы ФСТЭК.

import random
import matplotlib.colors as mcolors
others_object= len(extend_df)-extend_df['object'].value_counts().sort_values(ascending=False).head(10).sum()

val = extend_df['object'].value_counts().sort_values(ascending=False).head(10)
others = len(extend_df)-extend_df['object'].value_counts().sort_values(ascending=False).head(10).sum() 

vals = val.to_dict()
vals.update({'Другие':others})
colors = random.choices(list(mcolors.CSS4_COLORS.values()),k = len(vals))

fig, ax = plt.subplots()
ax.pie(vals.values(), labels=vals.keys(), autopct='%1.1f%%', colors = colors  )
ax.axis("equal")
plt.title('Топ 10 объектов воздействия, с учетом разложения по нарушителям')

ТОП 10 объектов воздействия.png

На этом анализ закончен, удалось проанализировать модель ФСТЭК по всем параметрам.