Вкладка «Основное», кнопки под полем типового объекта: - ⚙️ «Подобрать типовой объект (программа)»: скоринг по совпадению значимых слов названия/назначения объекта с перечнем 392 типовых отраслевых объектов (Распоряжение 360-р), IDF-веса (редкие слова важнее общих) → окно с вариантами, оператор выбирает и применяет (заполняет сферу + типовой объект) - 🤖 «Подобрать характеристики (ИИ)»: DeepSeek предлагает назначение, сферу, тип, архитектуру и типовой объект (из перечня 360-р) как рекомендацию → окно с полями, оператор подтверждает/правит Примеры: СКУД → «ИС, АСУ, ИТКС, обеспечивающие контроль и управление доступом на территорию»; 1С:УПП → «ИС для ведения бухгалтерского учета»
477 lines
25 KiB
Python
477 lines
25 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""DokoGen — автоматический подбор угроз безопасности информации (236 приказ).
|
||
|
||
Логика программы: по характеристикам объекта КИИ (тип, сеть, нарушитель)
|
||
подбирает актуальные угрозы из перечня БДУ ФСТЭК (threats_fstec.json).
|
||
Если перечень не скачан — использует базовый threats.json.
|
||
|
||
Правила эвристические; при наличии методического документа (например,
|
||
«Методика оценки угроз безопасности информации» ФСТЭК) — адаптируются под него.
|
||
"""
|
||
|
||
import os
|
||
import json
|
||
|
||
|
||
def load_threats(dict_dir=None):
|
||
"""Список угроз: сначала скачанный с ФСТЭК (с кодами УБИ), иначе базовый."""
|
||
if dict_dir is None:
|
||
dict_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dictionaries')
|
||
fstec = os.path.join(dict_dir, 'threats_fstec.json')
|
||
if os.path.exists(fstec):
|
||
try:
|
||
with open(fstec, encoding='utf-8') as f:
|
||
data = json.load(f)
|
||
if data:
|
||
return data
|
||
except Exception:
|
||
pass
|
||
base = os.path.join(dict_dir, 'threats.json')
|
||
try:
|
||
with open(base, encoding='utf-8') as f:
|
||
return json.load(f)
|
||
except Exception:
|
||
return []
|
||
|
||
|
||
# ---------------------------------------------------------------
|
||
# Ключевые слова (в названии угрозы) по признакам объекта
|
||
# ---------------------------------------------------------------
|
||
# Базовый набор — актуален для любого объекта КИИ
|
||
BASE_KEYWORDS = [
|
||
'вредоносн', 'несанкционированн', 'утечк', 'перехват', 'модификац',
|
||
'уничтожен', 'отказ в обслуживании', 'нсд', 'биос', 'пароль',
|
||
'криптографическ', 'шифрован', 'аутентификац',
|
||
]
|
||
|
||
# Объект имеет подключение к сети Интернет / сеть электросвязи
|
||
INTERNET_KEYWORDS = [
|
||
'удалённ', 'удаленн', 'сетев', 'фишинг', 'ddos', 'отказ в обслуживании',
|
||
'веб', 'электронн', 'почт', 'беспроводн', 'wi-fi', 'wifi', 'vpn',
|
||
'маршрутизац', 'протокол', 'интернет',
|
||
]
|
||
|
||
# Объект изолирован (нет подключения к Интернету)
|
||
ISOLATED_KEYWORDS = [
|
||
'съёмн', 'съемн', 'физическ', 'локальн', 'инсайд', 'носител',
|
||
'биос', 'желез', 'аппаратн',
|
||
]
|
||
|
||
# АСУ / технологический процесс
|
||
ASU_KEYWORDS = [
|
||
'технологическ', 'асутп', 'промышленн', 'scada', 'датчик',
|
||
'исполнительн', 'технологическ', 'производств', 'процесс',
|
||
]
|
||
|
||
# ИТКС / сети связи
|
||
NETWORK_KEYWORDS = [
|
||
'сеть', 'канал', 'протокол', 'маршрутизац', 'связ', 'передач',
|
||
'коммутац', 'трафик',
|
||
]
|
||
|
||
# Внутренний нарушитель
|
||
INSIDER_KEYWORDS = [
|
||
'инсайд', 'сотрудник', 'привилегированн', 'администратор',
|
||
'внутренн', 'персонал',
|
||
]
|
||
|
||
# Внешний нарушитель
|
||
EXTERNAL_KEYWORDS = [
|
||
'внешн', 'удалённ', 'удаленн', 'дистанционн',
|
||
]
|
||
|
||
|
||
def _is_asu(isys) -> bool:
|
||
t = (getattr(isys, 'object_type', '') or '').lower()
|
||
return 'асу' in t or 'автоматизированн' in t or 'технологическ' in t
|
||
|
||
|
||
def _is_network(isys) -> bool:
|
||
t = (getattr(isys, 'object_type', '') or '').lower()
|
||
return 'телекоммуникац' in t or 'сеть' in t
|
||
|
||
|
||
def _has_internet(isys) -> bool:
|
||
"""Есть ли подключение к сети Интернет / внешним сетям."""
|
||
if getattr(isys, 'is_internet', False):
|
||
return True
|
||
desc = ((getattr(isys, 'description', '') or '') + ' ').lower()
|
||
operator = (getattr(isys, 'net_operator', '') or '').lower()
|
||
net_category = (getattr(isys, 'net_category', '') or '').lower()
|
||
|
||
# Явные признаки ОТСУТСТВИЯ внешней сети
|
||
if 'отсутств' in operator: # оператор «Отсутствует»
|
||
return False
|
||
if 'отсутств' in desc and ('интернет' in desc or 'подключ' in desc):
|
||
return False
|
||
if 'не требуется' in desc and ('доменн' in desc or 'интернет' in desc):
|
||
return False
|
||
if 'изолирован' in desc:
|
||
return False
|
||
|
||
# Сеть общего пользования — это и есть внешняя сеть (Интернет)
|
||
if 'общего пользован' in net_category:
|
||
return True
|
||
|
||
# Указан реальный оператор/провайдер — внешняя связь есть
|
||
if operator and operator not in ('нет', 'отсутствует', 'не требуется', '-'):
|
||
return True
|
||
|
||
# Консервативно: без явных признаков внешней сети считаем, что её нет
|
||
return False
|
||
|
||
|
||
def _is_insider(isys) -> bool:
|
||
a = (getattr(isys, 'attacker_category', '') or '').lower()
|
||
return 'внутрен' in a
|
||
|
||
|
||
def _score(threat_lower, keywords):
|
||
"""Сколько ключевых слов встретилось в названии угрозы."""
|
||
return sum(1 for kw in keywords if kw in threat_lower)
|
||
|
||
|
||
def suggest_threats_program(isys, threats=None, limit=None):
|
||
"""Подбор угроз по характеристикам объекта КИИ (инвертированная логика).
|
||
|
||
По умолчанию актуальны ВСЕ угрозы из перечня (БДУ ФСТЭК).
|
||
Исключаются только заведомо неактуальные для данного объекта:
|
||
- нет сети Интернет → убираем угрозы удалённого доступа, веб, почты,
|
||
беспроводных сетей, DDoS, облачных и т.п.;
|
||
- объект типа АСУ → убираем угрозы BIOS, грид-систем, виртуализации
|
||
(у технологического оборудования их, как правило, нет).
|
||
Такой подход даёт полный список, а не «15 самых похожих».
|
||
|
||
Возвращает список строк (с кодами УБИ, если они есть в перечне).
|
||
"""
|
||
if threats is None:
|
||
threats = load_threats()
|
||
if not threats:
|
||
return []
|
||
|
||
has_internet = _has_internet(isys)
|
||
is_asu = _is_asu(isys)
|
||
is_network = _is_network(isys)
|
||
|
||
# --- Слова-исключения (если встретились в названии угрозы — она неактуальна) ---
|
||
exclude = []
|
||
|
||
if not has_internet:
|
||
# Объект изолирован: угрозы, реализуемые только через внешние сети
|
||
exclude += [
|
||
'удалённ', 'удаленн', 'дистанционн', 'веб', 'электронн', 'почт',
|
||
'фишинг', 'беспроводн', 'wi-fi', 'wifi', 'интернет', 'ddos', 'vpn',
|
||
'облачн', 'грид', 'внешн',
|
||
]
|
||
|
||
if is_asu:
|
||
# АСУ: нет BIOS/виртуализации/браузера в привычном понимании
|
||
exclude += ['биос', 'грид', 'гипервизор', 'виртуальн', 'браузер']
|
||
|
||
if not is_network and not is_asu:
|
||
# Обычная ИС: неактуальны чисто сетевые/телеком-угрозы без указания
|
||
# — оставляем всё, кроме специфики каналов связи, если их нет
|
||
pass
|
||
|
||
# --- Положительные ключевые слова: чем больше совпало — тем угроза актуальнее ---
|
||
pos = list(BASE_KEYWORDS)
|
||
if has_internet:
|
||
pos += INTERNET_KEYWORDS
|
||
else:
|
||
pos += ISOLATED_KEYWORDS
|
||
if is_asu:
|
||
pos += ASU_KEYWORDS
|
||
if is_network:
|
||
pos += NETWORK_KEYWORDS
|
||
if _is_insider(isys):
|
||
pos += INSIDER_KEYWORDS
|
||
else:
|
||
pos += EXTERNAL_KEYWORDS
|
||
# Убираем дубликаты
|
||
pos = list(dict.fromkeys(pos))
|
||
|
||
scored = []
|
||
for t in threats:
|
||
tl = str(t).lower()
|
||
if any(kw in tl for kw in exclude):
|
||
continue
|
||
sc = sum(1 for kw in pos if kw in tl)
|
||
scored.append((sc, t))
|
||
|
||
# Сортируем по релевантности (сначала — с наибольшим совпадением)
|
||
scored.sort(key=lambda x: -x[0])
|
||
result = [t for sc, t in scored if sc > 0]
|
||
|
||
if limit and len(result) > limit:
|
||
result = result[:limit]
|
||
return result
|
||
|
||
|
||
# ---------------------------------------------------------------
|
||
# Автозаполнение вкладки «Безопасность» по данным объекта (логическое)
|
||
# ---------------------------------------------------------------
|
||
|
||
# Ключевые слова: угроза → тип инцидента (словарь incidents.json)
|
||
INCIDENT_RULES = [
|
||
('отказ в обслуживании', 'отказ в обслуживании'),
|
||
('несанкционированн', 'несанкционированный доступ'),
|
||
('утечк', 'утечка данных (нарушение конфиденциальности)'),
|
||
('модификац', 'модификация (подмена) данных'),
|
||
('подмен', 'модификация (подмена) данных'),
|
||
('функционирован', 'нарушение функционирования технических средств'),
|
||
('техническ', 'нарушение функционирования технических средств'),
|
||
('использован', 'несанкционированное использование вычислительных ресурсов объекта'),
|
||
]
|
||
|
||
# Базовый набор орг. мер (всегда актуальны для значимого объекта)
|
||
BASE_ORG_MEASURES = [
|
||
'Установление контролируемой зоны',
|
||
'Контроль физического доступа к объекту',
|
||
'Разработка документов (регламентов, инструкций, руководств) по обеспечению безопасности объекта',
|
||
'Назначение ответственных за обеспечение безопасности объекта',
|
||
'Обучение и инструктаж персонала',
|
||
'Регламентация действий при компьютерных инцидентах',
|
||
'Утверждение перечня лиц, имеющих доступ к объекту',
|
||
]
|
||
|
||
# Базовый набор тех. мер
|
||
BASE_TECH_MEASURES = [
|
||
'Идентификация и аутентификация пользователей',
|
||
'Управление доступом',
|
||
'Ограничение программной среды',
|
||
'Антивирусная защита',
|
||
'Защита информации от несанкционированного доступа (СЗИ от НСД)',
|
||
'Регистрация и мониторинг событий безопасности',
|
||
'Резервное копирование и восстановление',
|
||
'Защита технических средств и линий связи',
|
||
]
|
||
|
||
# Средства защиты → доп. тех. меры
|
||
DEFENSE_TOOL_MEASURES = [
|
||
('межсетев', 'Межсетевое экранирование'),
|
||
('firewall', 'Межсетевое экранирование'),
|
||
('сетев', 'Межсетевое экранирование'),
|
||
('обнаруж', 'Обнаружение вторжений (СОВ)'),
|
||
('ids', 'Обнаружение вторжений (СОВ)'),
|
||
('ips', 'Обнаружение вторжений (СОВ)'),
|
||
('криптограф', 'Криптографическая защита информации (СКЗИ)'),
|
||
('vipnet', 'Криптографическая защита информации (СКЗИ)'),
|
||
('континент', 'Криптографическая защита информации (СКЗИ)'),
|
||
('виртуализ', 'Защита среды виртуализации'),
|
||
('vmware', 'Защита среды виртуализации'),
|
||
('hyper-v', 'Защита среды виртуализации'),
|
||
]
|
||
|
||
|
||
def auto_fill_security(isys, threats=None):
|
||
"""Логическое автозаполнение вкладки «Безопасность» по данным объекта КИИ.
|
||
|
||
Заполняет (только если поле пустое):
|
||
attacker_category — по наличию интернета (внешний/внутренний нарушитель);
|
||
attacker_level — Н1/Н2 (внутренний — базовые, внешний — базовые повышенные);
|
||
attacker_types — виды нарушителей по категории/интернету;
|
||
threats — подбор программой (скоринг по характеристикам);
|
||
incidents — типы инцидентов, выводимые из подобранных угроз;
|
||
org_measures / tech_measures — базовый набор + по СЗИ объекта.
|
||
|
||
Возвращает dict {поле: новое_значение} только для пустых полей.
|
||
"""
|
||
from .models import InformationSystem
|
||
res = {}
|
||
|
||
has_internet = _has_internet(isys)
|
||
|
||
# 1. Категория нарушителя
|
||
if not (getattr(isys, 'attacker_category', '') or '').strip():
|
||
res['attacker_category'] = 'Внешний нарушитель' if has_internet else 'Внутренний нарушитель'
|
||
|
||
# 2. Уровень возможностей
|
||
if not (getattr(isys, 'attacker_level', '') or '').strip():
|
||
res['attacker_level'] = 'Н2 — Базовые повышенные возможности' if has_internet else 'Н1 — Базовые возможности'
|
||
|
||
# 3. Виды нарушителей
|
||
if not (getattr(isys, 'attacker_types', '') or '').strip():
|
||
if has_internet:
|
||
types = [
|
||
'Отдельные физические лица (хакеры)',
|
||
'Преступные группы (криминальные структуры)',
|
||
'Конкурирующие организации',
|
||
'Авторизованные пользователи систем и сетей',
|
||
'Системные администраторы и администраторы безопасности',
|
||
'Бывшие (уволенные) работники (пользователи)',
|
||
]
|
||
else:
|
||
types = [
|
||
'Авторизованные пользователи систем и сетей',
|
||
'Системные администраторы и администраторы безопасности',
|
||
'Бывшие (уволенные) работники (пользователи)',
|
||
'Лица, обеспечивающие функционирование систем и сетей или обеспечивающих систем оператора (администрация, охрана, уборщики и др.)',
|
||
]
|
||
res['attacker_types'] = '\n'.join(types)
|
||
|
||
# 4. Угрозы — подбор программой
|
||
if not (getattr(isys, 'threats', '') or '').strip():
|
||
prog = suggest_threats_program(isys, threats=threats)
|
||
if prog:
|
||
res['threats'] = '\n'.join(prog)
|
||
|
||
# 5. Инциденты — из подобранных угроз
|
||
if not (getattr(isys, 'incidents', '') or '').strip():
|
||
threats_text = res.get('threats') or (getattr(isys, 'threats', '') or '')
|
||
if threats_text:
|
||
incidents = []
|
||
low = threats_text.lower()
|
||
for kw, incident in INCIDENT_RULES:
|
||
if kw in low and incident not in incidents:
|
||
incidents.append(incident)
|
||
if incidents:
|
||
res['incidents'] = '\n'.join(incidents)
|
||
|
||
# 6. Орг. меры — базовый набор
|
||
if not (getattr(isys, 'org_measures', '') or '').strip():
|
||
res['org_measures'] = '\n'.join(BASE_ORG_MEASURES)
|
||
|
||
# 7. Тех. меры — базовые + по СЗИ объекта
|
||
if not (getattr(isys, 'tech_measures', '') or '').strip():
|
||
tech = list(BASE_TECH_MEASURES)
|
||
defense = ' '.join(getattr(isys, 'defense_tools_list', []) or [])
|
||
dlow = defense.lower()
|
||
for kw, measure in DEFENSE_TOOL_MEASURES:
|
||
if kw in dlow and measure not in tech:
|
||
tech.append(measure)
|
||
res['tech_measures'] = '\n'.join(tech)
|
||
|
||
return res
|
||
|
||
|
||
# ---------------------------------------------------------------
|
||
# Подбор типового отраслевого объекта КИИ по характеристикам объекта
|
||
# ---------------------------------------------------------------
|
||
|
||
# Стоп-слова: не несут смысла при сопоставлении названия объекта и типового объекта
|
||
STOPWORDS = {
|
||
'информационные', 'информационно', 'телекоммуникационные', 'автоматизированные',
|
||
'системы', 'система', 'сети', 'сеть', 'обеспечивающие', 'обеспечивающих',
|
||
'предназначенные', 'предназначенных', 'управление', 'контроль', 'мониторинг',
|
||
'объекта', 'объектов', 'объект', 'объекты', 'кии', 'рф', 'российской',
|
||
'федерации', 'исполнительной', 'власти', 'органов', 'организаций', 'организации',
|
||
'подведомственных', 'функций', 'функции', 'осуществление', 'полномочий',
|
||
'обеспечение', 'процессов', 'процесса', 'процессы', 'данных', 'информации',
|
||
'сбор', 'хранение', 'обработка', 'представление', 'использование', 'передача',
|
||
'автоматизации', 'средств', 'программных', 'программно', 'аппаратных',
|
||
'технических', 'деятельность', 'деятельности', 'осуществляющих',
|
||
'размещенные', 'размещенных', 'расположенных', 'используемых', 'используемые',
|
||
'проведении', 'проведения', 'выполнении', 'выполнения', 'реализация', 'реализации',
|
||
'электронных', 'электронного', 'информационных', 'информационного',
|
||
# Общие словоформы, встречающиеся почти у всех типовых объектов:
|
||
'контроль', 'контроля', 'контролю', 'контролем', 'контроле',
|
||
'управление', 'управления', 'управлению', 'управлением', 'управлении',
|
||
'безопасность', 'безопасности', 'безопасностью', 'безопасном',
|
||
'обеспечение', 'обеспечения', 'обеспечению', 'обеспечением',
|
||
'предназначен', 'предназначена', 'предназначено', 'предназначены',
|
||
'предоставление', 'предоставления', 'предоставлению',
|
||
'взаимодействие', 'взаимодействия', 'взаимодействию',
|
||
'функционирование', 'функционирования', 'функционированию',
|
||
'деятельность', 'деятельности', 'деятельностью',
|
||
'автоматизирован', 'автоматизированной', 'автоматизированных',
|
||
'телекоммуникацион', 'телекоммуникационной', 'телекоммуникационных',
|
||
'информационн', 'информационной', 'информационных', 'информационное',
|
||
'осуществление', 'осуществления', 'осуществлению',
|
||
'для', 'систем', 'системы', 'система', 'ресурсов', 'ресурсы',
|
||
'целью', 'цели', 'помощи', 'помощь', 'также', 'или', 'иных', 'иное',
|
||
'при', 'на', 'по', 'в', 'с', 'и', 'не', 'то', 'числе',
|
||
}
|
||
|
||
|
||
def _tokenize(text):
|
||
"""Значимые слова текста (без стоп-слов)."""
|
||
import re as _re
|
||
words = _re.findall(r'[а-яёa-z0-9]+', (text or '').lower())
|
||
return [w for w in words if len(w) > 2 and w not in STOPWORDS]
|
||
|
||
|
||
def _load_all_typical(dict_dir=None):
|
||
"""Все типовые отраслевые объекты из typical_industry_objects.json (по сферам)."""
|
||
import os as _os
|
||
if dict_dir is None:
|
||
dict_dir = _os.path.join(_os.path.dirname(_os.path.abspath(__file__)), 'dictionaries')
|
||
path = _os.path.join(dict_dir, 'typical_industry_objects.json')
|
||
try:
|
||
with open(path, encoding='utf-8') as f:
|
||
data = json.load(f)
|
||
except Exception:
|
||
return []
|
||
out = []
|
||
for sphere, items in data.items():
|
||
if sphere == 'Иная сфера деятельности':
|
||
continue
|
||
for it in items:
|
||
out.append({'sphere': sphere, 'num': it.get('num', ''),
|
||
'name': it.get('name', '')})
|
||
return out
|
||
|
||
|
||
_IDF_CACHE = None
|
||
|
||
|
||
def _idf_weights(cands):
|
||
"""IDF-веса слов: чем реже слово в перечне типовых объектов, тем больше вес.
|
||
|
||
Общие слова («информационные», «управление», «контроль») встречаются почти
|
||
у всех 392 объектов и почти ничего не значат; редкие («доступом», «СКУД»,
|
||
«сигнализации») — сильно различают объекты.
|
||
"""
|
||
global _IDF_CACHE
|
||
if _IDF_CACHE is not None:
|
||
return _IDF_CACHE
|
||
import math
|
||
df = {}
|
||
for c in cands:
|
||
for w in set(_tokenize(c['name'])):
|
||
df[w] = df.get(w, 0) + 1
|
||
n = max(len(cands), 1)
|
||
weights = {w: math.log((n + 1) / (df.get(w, 0) + 1)) + 1.0 for w in df}
|
||
_IDF_CACHE = weights
|
||
return weights
|
||
|
||
|
||
def suggest_typical_object_program(isys, limit=10, sphere=None):
|
||
"""Подбор типового отраслевого объекта КИИ по характеристикам объекта.
|
||
|
||
Логика: значимые слова из названия/назначения/описания объекта КИИ
|
||
сопоставляются со значимыми словами названий типовых объектов
|
||
(перечень Распоряжения ПРФ № 360-р, 392 объекта). Скоринг по IDF:
|
||
редкие слова (например, «доступом», «сигнализации») весят больше общих.
|
||
Пример: объект «СКУД» / «контроль и управление доступом» →
|
||
«ИС, АСУ, ИТКС, обеспечивающие контроль и управление доступом на территорию».
|
||
|
||
Возвращает список dict: {sphere, num, name, score} — по убыванию релевантности.
|
||
"""
|
||
text = ' '.join([
|
||
getattr(isys, 'name', '') or '',
|
||
getattr(isys, 'purpose', '') or '',
|
||
getattr(isys, 'description', '') or '',
|
||
])
|
||
obj_words = set(_tokenize(text))
|
||
if not obj_words:
|
||
return []
|
||
|
||
cands = _load_all_typical()
|
||
if sphere:
|
||
cands = [c for c in cands if c['sphere'] == sphere]
|
||
|
||
weights = _idf_weights(cands)
|
||
|
||
scored = []
|
||
for c in cands:
|
||
tw = set(_tokenize(c['name']))
|
||
common = tw & obj_words
|
||
if common:
|
||
score = sum(weights.get(w, 1.0) for w in common)
|
||
scored.append({'sphere': c['sphere'], 'num': c['num'],
|
||
'name': c['name'], 'score': round(score, 2),
|
||
'matched': sorted(common)})
|
||
|
||
scored.sort(key=lambda x: (-x['score'], x['name']))
|
||
return scored[:limit]
|