diff --git a/dokogen/dictionaries/attacker_categories.json b/dokogen/dictionaries/attacker_categories.json new file mode 100644 index 0000000..08b757b --- /dev/null +++ b/dokogen/dictionaries/attacker_categories.json @@ -0,0 +1,5 @@ +[ + "внешний нарушитель", + "внутренний нарушитель", + "внешний и внутренний нарушитель" +] diff --git a/dokogen/dictionaries/criteria_indicators.json b/dokogen/dictionaries/criteria_indicators.json new file mode 100644 index 0000000..1487a52 --- /dev/null +++ b/dokogen/dictionaries/criteria_indicators.json @@ -0,0 +1,7 @@ +[ + "Социальная значимость — количество граждан, на которые может повлиять нарушение", + "Политическая значимость — международный или внутриполитический резонанс", + "Экономическая значимость — возможный размер ущерба (в рублях)", + "Экологическая значимость — возможный ущерб окружающей среде", + "Значимость для обороны страны, безопасности государства и правопорядка" +] diff --git a/dokogen/dictionaries/incidents.json b/dokogen/dictionaries/incidents.json new file mode 100644 index 0000000..9096d78 --- /dev/null +++ b/dokogen/dictionaries/incidents.json @@ -0,0 +1,8 @@ +[ + "отказ в обслуживании", + "несанкционированный доступ", + "утечка данных (нарушение конфиденциальности)", + "модификация (подмена) данных", + "нарушение функционирования технических средств", + "несанкционированное использование вычислительных ресурсов объекта" +] diff --git a/dokogen/dictionaries/org_measures.json b/dokogen/dictionaries/org_measures.json new file mode 100644 index 0000000..a5a5b2e --- /dev/null +++ b/dokogen/dictionaries/org_measures.json @@ -0,0 +1,9 @@ +[ + "Установление контролируемой зоны", + "Контроль физического доступа к объекту", + "Разработка документов (регламентов, инструкций, руководств) по обеспечению безопасности объекта", + "Назначение ответственных за обеспечение безопасности объекта", + "Обучение и инструктаж персонала", + "Регламентация действий при компьютерных инцидентах", + "Утверждение перечня лиц, имеющих доступ к объекту" +] diff --git a/dokogen/dictionaries/tech_measures.json b/dokogen/dictionaries/tech_measures.json new file mode 100644 index 0000000..55805aa --- /dev/null +++ b/dokogen/dictionaries/tech_measures.json @@ -0,0 +1,14 @@ +[ + "Идентификация и аутентификация пользователей", + "Управление доступом", + "Ограничение программной среды", + "Антивирусная защита", + "Защита информации от несанкционированного доступа (СЗИ от НСД)", + "Регистрация и мониторинг событий безопасности", + "Межсетевое экранирование", + "Обнаружение вторжений (СОВ)", + "Криптографическая защита информации (СКЗИ)", + "Защита среды виртуализации", + "Резервное копирование и восстановление", + "Защита технических средств и линий связи" +] diff --git a/dokogen/dictionaries/threats.json b/dokogen/dictionaries/threats.json new file mode 100644 index 0000000..db774ce --- /dev/null +++ b/dokogen/dictionaries/threats.json @@ -0,0 +1,12 @@ +[ + "Угроза несанкционированного доступа к информационным ресурсам объекта", + "Угроза нарушения функционирования объекта (отказ в обслуживании)", + "Угроза утечки (нарушения конфиденциальности) информации", + "Угроза модификации (подмены) данных", + "Угроза внедрения вредоносного кода", + "Угроза несанкционированного использования вычислительных ресурсов", + "Угроза перехвата (прослушивания) сетевого трафика", + "Угроза социальной инженерии", + "Угроза использования уязвимостей программного обеспечения", + "Угроза физического воздействия на технические средства" +] diff --git a/dokogen/dictionaries/threats_fstec.json b/dokogen/dictionaries/threats_fstec.json new file mode 100644 index 0000000..f1335e3 --- /dev/null +++ b/dokogen/dictionaries/threats_fstec.json @@ -0,0 +1,229 @@ +[ + "Угроза автоматического распространения вредоносного кода в грид-системе", + "Угроза агрегирования данных, передаваемых в грид-системе", + "Угроза использования слабостей криптографических алгоритмов и уязвимостей в программном обеспечении их реализации", + "Угроза аппаратного сброса пароля BIOS", + "Угроза внедрения вредоносного кода в BIOS", + "Угроза внедрения кода или данных", + "Угроза воздействия на программы с высокими привилегиями", + "Угроза восстановления и/или повторного использования аутентификационной информации", + "Угроза восстановления предыдущей уязвимой версии BIOS", + "Угроза выхода процесса за пределы виртуальной машины", + "Угроза деавторизации санкционированного клиента беспроводной сети", + "Угроза деструктивного изменения конфигурации/среды окружения программ", + "Угроза деструктивного использования декларированного функционала BIOS", + "Угроза длительного удержания вычислительных ресурсов пользователями", + "Угроза доступа к защищаемым файлам с использованием обходного пути", + "Угроза доступа к локальным файлам сервера при помощи URL", + "Угроза доступа/перехвата/изменения HTTP cookies", + "Угроза загрузки нештатной операционной системы", + "Угроза заражения DNS-кеша", + "Угроза злоупотребления возможностями, предоставленными потребителям облачных услуг", + "Угроза злоупотребления доверием потребителей облачных услуг", + "Угроза избыточного выделения оперативной памяти", + "Угроза изменения компонентов информационной (автоматизированной) системы", + "Угроза изменения режимов работы аппаратных элементов компьютера", + "Угроза изменения системных и глобальных переменных", + "Угроза искажения XML-схемы", + "Угроза искажения вводимой и выводимой на периферийные устройства информации", + "Угроза использования альтернативных путей доступа к ресурсам", + "Угроза использования вычислительных ресурсов суперкомпьютера «паразитными» процессами", + "Угроза использования информации идентификации/аутентификации, заданной по умолчанию", + "Угроза использования механизмов авторизации для повышения привилегий", + "Угроза использования поддельных цифровых подписей BIOS", + "Угроза использования слабостей кодирования входных данных", + "Угроза использования слабостей протоколов сетевого/локального обмена данными", + "Угроза использования слабых криптографических алгоритмов BIOS", + "Угроза исследования механизмов работы программы", + "Угроза исследования приложения через отчёты об ошибках", + "Угроза исчерпания вычислительных ресурсов хранилища больших данных", + "Угроза исчерпания запаса ключей, необходимых для обновления BIOS", + "Угроза конфликта юрисдикций различных стран", + "Угроза межсайтового скриптинга", + "Угроза межсайтовой подделки запроса", + "Угроза нарушения доступности облачного сервера", + "Угроза нарушения изоляции пользовательских данных внутри виртуальной машины", + "Угроза нарушения изоляции среды исполнения BIOS", + "Угроза нарушения процедуры аутентификации субъектов виртуального информационного взаимодействия", + "Угроза нарушения работоспособности грид-системы при нетипичной сетевой нагрузке", + "Угроза нарушения технологии обработки информации путём несанкционированного внесения изменений в образы виртуальных машин", + "Угроза нарушения целостности данных кеша", + "Угроза неверного определения формата входных данных, поступающих в хранилище больших данных", + "Угроза невозможности восстановления сессии работы на ПЭВМ при выводе из промежуточных состояний питания", + "Угроза невозможности миграции образов виртуальных машин из-за несовместимости аппаратного и программного обеспечения", + "Угроза невозможности управления правами пользователей BIOS", + "Угроза недобросовестного исполнения обязательств поставщиками облачных услуг", + "Угроза незащищённого администрирования облачных услуг", + "Угроза некачественного переноса инфраструктуры в облако", + "Угроза неконтролируемого копирования данных внутри хранилища больших данных", + "Угроза неконтролируемого роста числа виртуальных машин", + "Угроза неконтролируемого роста числа зарезервированных вычислительных ресурсов", + "Угроза неконтролируемого уничтожения информации хранилищем больших данных", + "Угроза некорректного задания структуры данных транзакции", + "Угроза некорректного использования прозрачного прокси-сервера за счёт плагинов браузера", + "Угроза некорректного использования функционала программного и аппаратного обеспечения", + "Угроза некорректной реализации политики лицензирования в облаке", + "Угроза неопределённости в распределении ответственности между ролями в облаке", + "Угроза неопределённости ответственности за обеспечение безопасности облака", + "Угроза неправомерного ознакомления с защищаемой информацией", + "Угроза неправомерного/некорректного использования интерфейса взаимодействия с приложением", + "Угроза неправомерных действий в каналах связи", + "Угроза непрерывной модернизации облачной инфраструктуры", + "Угроза несанкционированного восстановления удалённой защищаемой информации", + "Угроза несанкционированного выключения или обхода механизма защиты от записи в BIOS", + "Угроза несанкционированного доступа к активному и (или) пассивному виртуальному и (или) физическому сетевому оборудованию из физической и (или) виртуальной сети", + "Угроза несанкционированного доступа к аутентификационной информации", + "Угроза несанкционированного доступа к виртуальным каналам передачи", + "Угроза несанкционированного доступа к гипервизору из виртуальной машины и (или) физической сети", + "Угроза несанкционированного доступа к данным за пределами зарезервированного адресного пространства, в том числе выделенного под виртуальное аппаратное обеспечение", + "Угроза несанкционированного доступа к защищаемым виртуальным машинам из виртуальной и (или) физической сети", + "Угроза несанкционированного доступа к защищаемым виртуальным машинам со стороны других виртуальных машин", + "Угроза несанкционированного доступа к защищаемым виртуальным устройствам из виртуальной и (или) физической сети", + "Угроза несанкционированного доступа к локальному компьютеру через клиента грид-системы", + "Угроза несанкционированного доступа к сегментам вычислительного поля", + "Угроза несанкционированного доступа к системе по беспроводным каналам", + "Угроза несанкционированного доступа к системе хранения данных из виртуальной и (или) физической сети", + "Угроза несанкционированного доступа к хранимой в виртуальном пространстве защищаемой информации", + "Угроза несанкционированного изменения аутентификационной информации", + "Угроза несанкционированного использования привилегированных функций BIOS", + "Угроза несанкционированного копирования защищаемой информации", + "Угроза несанкционированного редактирования реестра", + "Угроза несанкционированного создания учётной записи пользователя", + "Угроза несанкционированного удаления защищаемой информации", + "Угроза несанкционированного удалённого внеполосного доступа к аппаратным средствам", + "Угроза несанкционированного управления буфером", + "Угроза несанкционированного управления синхронизацией и состоянием", + "Угроза несанкционированного управления указателями", + "Угроза несогласованности политик безопасности элементов облачной инфраструктуры", + "Угроза несогласованности правил доступа к большим данным", + "Угроза обнаружения открытых портов и идентификации привязанных к ним сетевых служб", + "Угроза обнаружения хостов", + "Угроза обхода некорректно настроенных механизмов аутентификации", + "Угроза общедоступности облачной инфраструктуры", + "Угроза опосредованного управления группой программ через совместно используемые данные", + "Угроза определения типов объектов защиты", + "Угроза определения топологии вычислительной сети", + "Угроза отказа в загрузке входных данных неизвестного формата хранилищем больших данных", + "Угроза отказа в обслуживании системой хранения данных суперкомпьютера", + "Угроза отключения контрольных датчиков", + "Угроза ошибки обновления гипервизора", + "Угроза перебора всех настроек и параметров приложения", + "Угроза перегрузки грид-системы вычислительными заданиями", + "Угроза передачи данных по скрытым каналам", + "Угроза передачи запрещённых команд на оборудование с числовым программным управлением", + "Угроза перезагрузки аппаратных и программно-аппаратных средств вычислительной техники", + "Угроза переполнения целочисленных переменных", + "Угроза перехвата вводимой и выводимой на периферийные устройства информации", + "Угроза перехвата данных, передаваемых по вычислительной сети", + "Угроза перехвата привилегированного потока", + "Угроза перехвата привилегированного процесса", + "Угроза перехвата управления гипервизором", + "Угроза перехвата управления средой виртуализации", + "Угроза повреждения системного реестра", + "Угроза повышения привилегий", + "Угроза подбора пароля BIOS", + "Угроза подделки записей журнала регистрации событий", + "Угроза подключения к беспроводной сети в обход процедуры аутентификации", + "Угроза подмены беспроводного клиента или точки доступа", + "Угроза подмены действия пользователя путём обмана", + "Угроза подмены доверенного пользователя", + "Угроза подмены резервной копии программного обеспечения BIOS", + "Угроза подмены содержимого сетевых ресурсов", + "Угроза подмены субъекта сетевого доступа", + "Угроза получения предварительной информации об объекте защиты", + "Угроза получения сведений о владельце беспроводного устройства", + "Угроза потери доверия к поставщику облачных услуг", + "Угроза потери и утечки данных, обрабатываемых в облаке", + "Угроза потери информации вследствие несогласованности работы узлов хранилища больших данных", + "Угроза потери управления облачными ресурсами", + "Угроза потери управления собственной инфраструктурой при переносе её в облако", + "Угроза преодоления физической защиты", + "Угроза приведения системы в состояние «отказ в обслуживании»", + "Угроза привязки к поставщику облачных услуг", + "Угроза приостановки оказания облачных услуг вследствие технических сбоев", + "Угроза программного выведения из строя средств хранения, обработки и (или) ввода/вывода/передачи информации", + "Угроза программного сброса пароля BIOS", + "Угроза пропуска проверки целостности программного обеспечения", + "Угроза прямого обращения к памяти вычислительного поля суперкомпьютера", + "Угроза распространения несанкционированно повышенных прав на всю грид-систему", + "Угроза сбоя автоматического управления системой разграничения доступа хранилища больших данных", + "Угроза сбоя обработки специальным образом изменённых файлов", + "Угроза сбоя процесса обновления BIOS", + "Угроза сканирования веб-сервисов, разработанных на основе языка описания WSDL", + "Угроза удаления аутентификационной информации", + "Угроза усиления воздействия на вычислительные ресурсы пользователей при помощи сторонних серверов", + "Угроза установки уязвимых версий обновления программного обеспечения BIOS", + "Угроза утраты вычислительных ресурсов", + "Угроза утраты носителей информации", + "Угроза физического выведения из строя средств хранения, обработки и (или) ввода/вывода/передачи информации", + "Угроза форматирования носителей информации", + "Угроза «форсированного веб-браузинга»", + "Угроза хищения средств хранения, обработки и (или) ввода/вывода/передачи информации", + "Угроза чрезмерного использования вычислительных ресурсов суперкомпьютера в ходе интенсивного обмена межпроцессорными сообщениями", + "Угроза эксплуатации цифровой подписи программного кода", + "Угроза перехвата исключения/сигнала из привилегированного блока функций", + "Угроза распространения состояния «отказ в обслуживании» в облачной инфраструктуре", + "Угроза включения в проект не достоверно испытанных компонентов", + "Угроза внедрения системной избыточности", + "Угроза заражения компьютера при посещении неблагонадёжных сайтов", + "Угроза «кражи» учётной записи доступа к сетевым сервисам", + "Угроза наличия механизмов разработчика", + "Угроза неправомерного шифрования информации", + "Угроза скрытного включения вычислительного устройства в состав бот-сети", + "Угроза распространения «почтовых червей»", + "Угроза «спама» веб-сервера", + "Угроза «фарминга»", + "Угроза «фишинга»", + "Угроза нарушения технологического/производственного процесса из-за временны́х задержек, вносимых средством защиты", + "Угроза неподтверждённого ввода данных оператором в систему, связанную с безопасностью", + "Угроза несанкционированного использования системных и сетевых утилит", + "Угроза несанкционированной модификации защищаемой информации", + "Угроза отказа подсистемы обеспечения температурного режима", + "Угроза перехвата одноразовых паролей в режиме реального времени", + "Угроза физического устаревания аппаратных компонентов", + "Угроза перехвата управления автоматизированной системой управления технологическими процессами", + "Угроза агрегирования данных, обрабатываемых с помощью мобильного устройства", + "Угроза несанкционированного изменения параметров настройки средств защиты информации", + "Угроза внедрения вредоносного кода через рекламу, сервисы и контент", + "Угроза несанкционированного воздействия на средство защиты информации", + "Угроза подмены программного обеспечения", + "Угроза маскирования действий вредоносного кода", + "Угроза внедрения вредоносного кода за счет посещения зараженных сайтов в сети Интернет", + "Угроза внедрения вредоносного кода в дистрибутив программного обеспечения", + "Угроза использования уязвимых версий программного обеспечения", + "Угроза утечки информации за счет применения вредоносным программным обеспечением алгоритмов шифрования трафика", + "Угроза несанкционированного использования привилегированных функций мобильного устройства", + "Угроза удаленного запуска вредоносного кода в обход механизмов защиты операционной системы", + "Угроза контроля вредоносной программой списка приложений, запущенных на мобильном устройстве", + "Угроза хищения аутентификационной информации из временных файлов cookie", + "Угроза скрытной регистрации вредоносной программой учетных записей администраторов", + "Угроза перехвата управления мобильного устройства при использовании виртуальных голосовых ассистентов", + "Угроза хищения информации с мобильного устройства при использовании виртуальных голосовых ассистентов", + "Угроза утечки пользовательских данных при использовании функций автоматического заполнения аутентификационной информации в браузере", + "Угроза несанкционированной установки приложений на мобильные устройства", + "Угроза утечки информации с неподключенных к сети Интернет компьютеров", + "Угроза несанкционированного изменения вредоносной программой значений параметров программируемых логических контроллеров", + "Угроза нарушения работы компьютера и блокирования доступа к его данным из-за некорректной работы установленных на нем средств защиты", + "Угроза отказа в работе оборудования из-за изменения геолокационной информации о нем", + "Угроза несанкционированного доступа к параметрам настройки оборудования за счет использования «мастер-кодов» (инженерных паролей)", + "Угроза нецелевого использования вычислительных ресурсов средства вычислительной техники", + "Угроза несанкционированного доступа к защищаемой памяти \r\nядра процессора", + "Угроза нарушения работы информационной системы, вызванного обновлением используемого в ней программного обеспечения", + "Угроза использования непроверенных пользовательских данных при формировании конфигурационного файла, используемого программным обеспечением администрирования информационных систем", + "Угроза перехвата управления информационной системой", + "Угроза обхода многофакторной аутентификации", + "Угроза несвоевременного выявления и реагирования компонентами информационной (автоматизированной) системы (в том числе средствами защиты информации) на события безопасности информации", + "Угроза несанкционированного доступа к системе при помощи сторонних сервисов", + "Угроза получения несанкционированного доступа к приложениям, установленным на Smart-картах", + "Угроза использования скомпрометированного доверенного источника обновлений программного обеспечения", + "Угроза раскрытия информации о модели машинного обучения", + "Угроза хищения обучающих данных", + "Угроза нарушения функционирования («обхода») средств, реализующих технологии искусственного интеллекта", + "Угроза модификации модели машинного обучения путем искажения («отравления») обучающих данных", + "Угроза подмены модели машинного обучения", + "Угроза несанкционированного доступа к контейнерам, предоставляющего пользователям расширенные привилегии", + "Угроза нарушения целостности (подмены) контейнеров", + "Угроза нарушения изоляции контейнеров", + "Угроза внедрения вредоносного программного обеспечения в контейнеры", + "Угроза модификации (подмены) образов контейнеров" +] \ No newline at end of file diff --git a/dokogen/dictionaries/threats_fstec_date.txt b/dokogen/dictionaries/threats_fstec_date.txt new file mode 100644 index 0000000..ed514eb --- /dev/null +++ b/dokogen/dictionaries/threats_fstec_date.txt @@ -0,0 +1 @@ +2026-08-04 \ No newline at end of file diff --git a/dokogen/fstec_threats.py b/dokogen/fstec_threats.py new file mode 100644 index 0000000..2b39a70 --- /dev/null +++ b/dokogen/fstec_threats.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +"""DokoGen — загрузка перечня угроз безопасности информации с сайта ФСТЭК (БДУ). + +Скачивает https://bdu.fstec.ru/threat (таблица угроз, с пагинацией), +парсит названия угроз и сохраняет в dictionaries/threats_fstec.json. +Рядом хранится дата актуализации (threats_fstec_date.txt). +""" + +import os +import re +import json +import ssl +import urllib.request +import urllib.parse +from datetime import datetime + +BDU_URL = 'https://bdu.fstec.ru/threat' +USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + + +def _ssl_ctx(): + """SSL-контекст: сначала обычная проверка, при проблемах — без проверки.""" + try: + ctx = ssl.create_default_context() + return ctx + except Exception: + pass + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + return ctx + + +def _fetch(url, timeout=30): + """GET с User-Agent, возвращает текст или None.""" + req = urllib.request.Request(url, headers={'User-Agent': USER_AGENT}) + try: + with urllib.request.urlopen(req, timeout=timeout, context=_ssl_ctx()) as resp: + return resp.read().decode('utf-8', errors='ignore') + except Exception: + # повтор с отключённой проверкой сертификата (некоторые сети/прокси) + try: + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp: + return resp.read().decode('utf-8', errors='ignore') + except Exception: + return None + + +def _parse_threats(html): + """Извлекает названия угроз из HTML-таблицы БДУ. + + Строки вида: ...УБИ. 001Угроза ...... + Возвращает список названий (без кода). + """ + names = [] + if not html: + return names + for row in re.findall(r']*>(.*?)', html, re.S): + cells = re.findall(r']*>(.*?)', row, re.S) + cleaned = [re.sub(r'<[^>]+>', '', c).strip() for c in cells] + # формат: [код, название, ...] — берём ячейку, которая не похожа на код УБИ + for cell in cleaned: + if not cell: + continue + if re.fullmatch(r'УБИ\.?\s*[\d.]+', cell): + continue + if cell in ('№', 'Идентификатор', 'Наименование', 'Дата', 'Уровень опасности'): + continue + names.append(cell) + break + return names + + +def download_fstec_threats(dict_dir=None, max_pages=8, page_size=100): + """Скачивает перечень угроз с bdu.fstec.ru. + + Возвращает (ok, сообщение, список_угроз). При успехе сохраняет: + - dictionaries/threats_fstec.json — список названий угроз + - dictionaries/threats_fstec_date.txt — дата актуализации (ГГГГ-ММ-ДД) + """ + if dict_dir is None: + dict_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dictionaries') + + threats = [] + for page in range(1, max_pages + 1): + url = f"{BDU_URL}?size={page_size}&page={page}" + html = _fetch(url) + if not html: + break + names = _parse_threats(html) + if not names: + break + for n in names: + if n not in threats: + threats.append(n) + # если на странице меньше page_size — это последняя + if len(names) < page_size: + break + + if not threats: + return False, "Не удалось загрузить угрозы с сайта ФСТЭК (bdu.fstec.ru)", [] + + try: + os.makedirs(dict_dir, exist_ok=True) + with open(os.path.join(dict_dir, 'threats_fstec.json'), 'w', encoding='utf-8') as f: + json.dump(threats, f, ensure_ascii=False, indent=2) + today = datetime.now().strftime('%Y-%m-%d') + with open(os.path.join(dict_dir, 'threats_fstec_date.txt'), 'w', encoding='utf-8') as f: + f.write(today) + except Exception as e: + return False, f"Ошибка сохранения: {e}", threats + + return True, f"Загружено угроз: {len(threats)}", threats + + +def get_threats_date(dict_dir=None): + """Дата последней актуализации списка угроз (или None).""" + if dict_dir is None: + dict_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dictionaries') + path = os.path.join(dict_dir, 'threats_fstec_date.txt') + if os.path.exists(path): + try: + with open(path, encoding='utf-8') as f: + return f.read().strip() + except Exception: + pass + return None + + +def load_threats(dict_dir=None): + """Загружает список угроз: сначала скачанный с ФСТЭК, иначе базовый threats.json.""" + 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: + return json.load(f) + 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 [] diff --git a/dokogen/models.py b/dokogen/models.py index 126f4a7..b53c10f 100644 --- a/dokogen/models.py +++ b/dokogen/models.py @@ -53,12 +53,13 @@ class InformationSystem: # Безопасность (236 приказ) attacker_category: str = "" # Категория нарушителя (внешний/внутренний) - threats: str = "" # Основные угрозы безопасности информации - incidents: str = "" # Типы компьютерных инцидентов - criteria_values: str = "" # Полученные значения по показателям критериев значимости + attacker_characteristics: str = "" # Краткая характеристика возможностей нарушителя + threats: str = "" # Основные угрозы безопасности информации (\n — разделитель) + incidents: str = "" # Типы компьютерных инцидентов (\n — разделитель) + criteria_values: str = "" # Показатели критериев значимости (\n — разделитель) criteria_justification: str = "" # Обоснование полученных значений - org_measures: str = "" # Организационные меры - tech_measures: str = "" # Технические меры + org_measures: str = "" # Организационные меры (\n — разделитель) + tech_measures: str = "" # Технические меры (\n — разделитель) @dataclass diff --git a/dokogen/ui.py b/dokogen/ui.py index 42bf218..6c1efa2 100644 --- a/dokogen/ui.py +++ b/dokogen/ui.py @@ -941,25 +941,134 @@ class DokoGenApp: setattr(self, attr, box) sw_frame.columnconfigure(0, weight=1) - # ===== Вкладка «Безопасность (236)» ===== + # ===== Вкладка «Безопасность» ===== sec_tab = ttk.Frame(nb2) nb2.add(sec_tab, text="Безопасность") sec_frame = ttk.Frame(sec_tab, padding=8) sec_frame.pack(fill=tk.BOTH, expand=True) - sec_fields = [ - ("Категория нарушителя:", "is_attacker_category_entry"), - ("Основные угрозы БИ:", "is_threats_entry"), - ("Типы компьютерных инцидентов:", "is_incidents_entry"), - ("Показатели критериев значимости:", "is_criteria_values_entry"), - ("Обоснование значений показателей:", "is_criteria_justification_entry"), - ("Организационные меры:", "is_org_measures_entry"), - ("Технические меры:", "is_tech_measures_entry"), - ] - for i, (label, attr) in enumerate(sec_fields): - ttk.Label(sec_frame, text=label).grid(row=i, column=0, sticky=tk.W, pady=2) - entry = ttk.Entry(sec_frame, width=70) - entry.grid(row=i, column=1, padx=8, sticky=tk.EW, pady=1) - setattr(self, attr, entry) + + # --- Категория нарушителя (словарик) --- + ttk.Label(sec_frame, text="Категория нарушителя:").grid(row=0, column=0, sticky=tk.W, pady=2) + self.is_attacker_category_combo = ttk.Combobox(sec_frame, width=68, state='readonly') + self.is_attacker_category_combo.grid(row=0, column=1, padx=8, sticky=tk.EW, pady=1) + try: + import json as _json + _ac = os.path.join(os.path.dirname(__file__), 'dictionaries', 'attacker_categories.json') + with open(_ac, encoding='utf-8') as _f: + self.is_attacker_category_combo['values'] = [''] + _json.load(_f) + except Exception: + pass + + # --- Характеристика нарушителя --- + ttk.Label(sec_frame, text="Характеристика нарушителя:").grid(row=1, column=0, sticky=tk.NW, pady=2) + self.is_attacker_characteristics_listbox = tk.Listbox(sec_frame, height=3, exportselection=False) + self.is_attacker_characteristics_listbox.grid(row=1, column=1, sticky=tk.EW, padx=(8, 4), pady=1) + sec_char_btn = ttk.Frame(sec_frame) + sec_char_btn.grid(row=1, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(sec_char_btn, text="+ Добавить", + command=lambda: self._add_sw_item(self.is_attacker_characteristics_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_char_btn, text="- Удалить", + command=lambda: self._del_sw_item(self.is_attacker_characteristics_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_char_btn, text="✏️ Редактировать", + command=lambda: self._edit_sw_item(self.is_attacker_characteristics_listbox)).pack(fill=tk.X, pady=1) + + # --- Основные угрозы БИ (словарик + загрузка с ФСТЭК) --- + ttk.Label(sec_frame, text="Основные угрозы БИ:").grid(row=2, column=0, sticky=tk.W, pady=2) + self.is_threats_listbox = tk.Listbox(sec_frame, height=5, exportselection=False) + self.is_threats_listbox.grid(row=2, column=1, sticky=tk.EW, padx=(8, 4), pady=1) + sec_thr_btn = ttk.Frame(sec_frame) + sec_thr_btn.grid(row=2, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(sec_thr_btn, text="+ Добавить", + command=lambda: self._add_sw_item(self.is_threats_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_thr_btn, text="- Удалить", + command=lambda: self._del_sw_item(self.is_threats_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_thr_btn, text="✏️ Редактировать", + command=lambda: self._edit_sw_item(self.is_threats_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_thr_btn, text="📚 Из словаря", + command=lambda: self._select_sw_from_dict(self.is_threats_listbox, 'threats_fstec.json')).pack(fill=tk.X, pady=1) + ttk.Button(sec_thr_btn, text="⬇️ Загрузить угрозы ФСТЭК", + command=self._download_fstec_threats).pack(fill=tk.X, pady=1) + self._threats_date_label = ttk.Label(sec_frame, text="", foreground='gray') + self._threats_date_label.grid(row=3, column=1, sticky=tk.W, padx=8, pady=(0, 2)) + + # --- Типы компьютерных инцидентов (словарик) --- + ttk.Label(sec_frame, text="Типы компьютерных инцидентов:").grid(row=4, column=0, sticky=tk.W, pady=2) + self.is_incidents_listbox = tk.Listbox(sec_frame, height=4, exportselection=False) + self.is_incidents_listbox.grid(row=4, column=1, sticky=tk.EW, padx=(8, 4), pady=1) + sec_inc_btn = ttk.Frame(sec_frame) + sec_inc_btn.grid(row=4, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(sec_inc_btn, text="+ Добавить", + command=lambda: self._add_sw_item(self.is_incidents_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_inc_btn, text="- Удалить", + command=lambda: self._del_sw_item(self.is_incidents_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_inc_btn, text="✏️ Редактировать", + command=lambda: self._edit_sw_item(self.is_incidents_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_inc_btn, text="📚 Из словаря", + command=lambda: self._select_sw_from_dict(self.is_incidents_listbox, 'incidents.json')).pack(fill=tk.X, pady=1) + + # --- Показатели критериев значимости (словарик) --- + ttk.Label(sec_frame, text="Показатели критериев значимости:").grid(row=5, column=0, sticky=tk.W, pady=2) + self.is_criteria_values_listbox = tk.Listbox(sec_frame, height=4, exportselection=False) + self.is_criteria_values_listbox.grid(row=5, column=1, sticky=tk.EW, padx=(8, 4), pady=1) + sec_crit_btn = ttk.Frame(sec_frame) + sec_crit_btn.grid(row=5, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(sec_crit_btn, text="+ Добавить", + command=lambda: self._add_sw_item(self.is_criteria_values_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_crit_btn, text="- Удалить", + command=lambda: self._del_sw_item(self.is_criteria_values_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_crit_btn, text="✏️ Редактировать", + command=lambda: self._edit_sw_item(self.is_criteria_values_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_crit_btn, text="📚 Из словаря", + command=lambda: self._select_sw_from_dict(self.is_criteria_values_listbox, 'criteria_indicators.json')).pack(fill=tk.X, pady=1) + + # --- Обоснование значений показателей --- + ttk.Label(sec_frame, text="Обоснование значений показателей:").grid(row=6, column=0, sticky=tk.NW, pady=2) + self.is_criteria_justification_listbox = tk.Listbox(sec_frame, height=3, exportselection=False) + self.is_criteria_justification_listbox.grid(row=6, column=1, sticky=tk.EW, padx=(8, 4), pady=1) + sec_just_btn = ttk.Frame(sec_frame) + sec_just_btn.grid(row=6, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(sec_just_btn, text="+ Добавить", + command=lambda: self._add_sw_item(self.is_criteria_justification_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_just_btn, text="- Удалить", + command=lambda: self._del_sw_item(self.is_criteria_justification_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_just_btn, text="✏️ Редактировать", + command=lambda: self._edit_sw_item(self.is_criteria_justification_listbox)).pack(fill=tk.X, pady=1) + + # --- Организационные меры (словарик) --- + ttk.Label(sec_frame, text="Организационные меры:").grid(row=7, column=0, sticky=tk.W, pady=2) + self.is_org_measures_listbox = tk.Listbox(sec_frame, height=3, exportselection=False) + self.is_org_measures_listbox.grid(row=7, column=1, sticky=tk.EW, padx=(8, 4), pady=1) + sec_org_btn = ttk.Frame(sec_frame) + sec_org_btn.grid(row=7, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(sec_org_btn, text="+ Добавить", + command=lambda: self._add_sw_item(self.is_org_measures_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_org_btn, text="- Удалить", + command=lambda: self._del_sw_item(self.is_org_measures_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_org_btn, text="✏️ Редактировать", + command=lambda: self._edit_sw_item(self.is_org_measures_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_org_btn, text="📚 Из словаря", + command=lambda: self._select_sw_from_dict(self.is_org_measures_listbox, 'org_measures.json')).pack(fill=tk.X, pady=1) + + # --- Технические меры (словарик) --- + ttk.Label(sec_frame, text="Технические меры:").grid(row=8, column=0, sticky=tk.W, pady=2) + self.is_tech_measures_listbox = tk.Listbox(sec_frame, height=3, exportselection=False) + self.is_tech_measures_listbox.grid(row=8, column=1, sticky=tk.EW, padx=(8, 4), pady=1) + sec_tech_btn = ttk.Frame(sec_frame) + sec_tech_btn.grid(row=8, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(sec_tech_btn, text="+ Добавить", + command=lambda: self._add_sw_item(self.is_tech_measures_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_tech_btn, text="- Удалить", + command=lambda: self._del_sw_item(self.is_tech_measures_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_tech_btn, text="✏️ Редактировать", + command=lambda: self._edit_sw_item(self.is_tech_measures_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_tech_btn, text="📚 Из словаря", + command=lambda: self._select_sw_from_dict(self.is_tech_measures_listbox, 'tech_measures.json')).pack(fill=tk.X, pady=1) + + # --- Кнопка ИИ-проверки --- + ttk.Button(sec_frame, text="🤖 Проверить безопасность через ИИ", + command=self._check_security_ai).grid( + row=9, column=0, columnspan=3, sticky=tk.W, pady=(10, 0)) + sec_frame.columnconfigure(1, weight=1) # ===== Вкладка «Средства защиты» ===== @@ -985,6 +1094,8 @@ class DokoGenApp: ttk.Button(btnf, text="- Удалить", command=self._del_user_from_is).pack(side=tk.LEFT, padx=2) self._set_is_edit_state(False) + # Дата актуализации списка угроз ФСТЭК (если уже скачивался) + self._update_threats_date_label() # ---------- ВКЛАДКА "КОМИССИЯ" ---------- def _build_commission_tab(self): @@ -1228,13 +1339,14 @@ class DokoGenApp: ('is_hw_sw_listbox', 'hw_sw'), ('is_os_sw_listbox', 'os_sw'), ('is_app_sw_listbox', 'app_sw'), - ('is_attacker_category_entry', 'attacker_category'), - ('is_threats_entry', 'threats'), - ('is_incidents_entry', 'incidents'), - ('is_criteria_values_entry', 'criteria_values'), - ('is_criteria_justification_entry', 'criteria_justification'), - ('is_org_measures_entry', 'org_measures'), - ('is_tech_measures_entry', 'tech_measures'), + ('is_attacker_category_combo', 'attacker_category'), + ('is_attacker_characteristics_listbox', 'attacker_characteristics'), + ('is_threats_listbox', 'threats'), + ('is_incidents_listbox', 'incidents'), + ('is_criteria_values_listbox', 'criteria_values'), + ('is_criteria_justification_listbox', 'criteria_justification'), + ('is_org_measures_listbox', 'org_measures'), + ('is_tech_measures_listbox', 'tech_measures'), ]: w = getattr(self, attr, None) if w is not None: @@ -1462,13 +1574,14 @@ class DokoGenApp: ('is_hw_sw_listbox', 'hw_sw'), ('is_os_sw_listbox', 'os_sw'), ('is_app_sw_listbox', 'app_sw'), - ('is_attacker_category_entry', 'attacker_category'), - ('is_threats_entry', 'threats'), - ('is_incidents_entry', 'incidents'), - ('is_criteria_values_entry', 'criteria_values'), - ('is_criteria_justification_entry', 'criteria_justification'), - ('is_org_measures_entry', 'org_measures'), - ('is_tech_measures_entry', 'tech_measures'), + ('is_attacker_category_combo', 'attacker_category'), + ('is_attacker_characteristics_listbox', 'attacker_characteristics'), + ('is_threats_listbox', 'threats'), + ('is_incidents_listbox', 'incidents'), + ('is_criteria_values_listbox', 'criteria_values'), + ('is_criteria_justification_listbox', 'criteria_justification'), + ('is_org_measures_listbox', 'org_measures'), + ('is_tech_measures_listbox', 'tech_measures'), ]: w = getattr(self, attr, None) if w is not None: @@ -1538,6 +1651,7 @@ class DokoGenApp: os_sw=src.os_sw, app_sw=src.app_sw, attacker_category=src.attacker_category, + attacker_characteristics=src.attacker_characteristics, threats=src.threats, incidents=src.incidents, criteria_values=src.criteria_values, @@ -1645,6 +1759,124 @@ class DokoGenApp: if user: self.users_listbox.insert(tk.END, user) + def _download_fstec_threats(self): + """Скачать перечень угроз с сайта ФСТЭК (БДУ) и обновить список.""" + try: + from .fstec_threats import download_fstec_threats, get_threats_date + except ImportError as e: + messagebox.showerror("Ошибка", f"Модуль fstec_threats не найден: {e}") + return + + self.log("⬇️ Загрузка перечня угроз с bdu.fstec.ru…") + ok, msg, threats = download_fstec_threats() + if not ok: + messagebox.showerror("Загрузка угроз", + f"Не удалось загрузить:\n{msg}\n\n" + "Проверьте доступ к интернету или попробуйте позже.") + self.log(f"❌ Загрузка угроз: {msg}") + return + + # Обновляем label даты актуализации + self._update_threats_date_label() + # Если список угроз уже открыт — можно показать диалог выбора + messagebox.showinfo("Загрузка угроз", + f"{msg}\n\nСписок сохранён. Теперь нажмите «📚 Из словаря» " + "в разделе «Основные угрозы БИ», чтобы выбрать нужные угрозы.") + self.log(f"✅ {msg}") + + def _update_threats_date_label(self): + """Обновить подпись даты актуализации списка угроз.""" + try: + from .fstec_threats import get_threats_date + d = get_threats_date() + except Exception: + d = None + if hasattr(self, '_threats_date_label'): + if d: + self._threats_date_label.config( + text=f"Актуально на: {d}", foreground='#2e7d32') + else: + self._threats_date_label.config( + text="⚠ Список угроз ещё не скачан — нажмите «⬇️ Загрузить угрозы ФСТЭК»", + foreground='#c62828') + + def _check_security_ai(self): + """Проверка вкладки «Безопасность» через ИИ (DeepSeek/OpenAI-совместимый API).""" + if self._current_is_idx is None: + messagebox.showinfo("Безопасность", "Сначала выберите объект КИИ в списке слева") + return + settings = load_api_settings() + if not settings.get('ai_api_key'): + messagebox.showwarning("Настройки", "Не задан API-ключ ИИ.\nМеню «Файл → Настройки»") + return + + # Собираем текущие данные безопасности объекта + self._collect_is() + isys = self.model.information_systems[self._current_is_idx] + security_data = { + 'name': isys.name or '', + 'description': isys.description or '', + 'category': isys.category or '', + 'sphere': isys.sphere or '', + 'object_type': isys.object_type or '', + 'architecture': isys.architecture or '', + 'attacker_category': isys.attacker_category or '', + 'attacker_characteristics': isys.attacker_characteristics or '', + 'threats': isys.threats or '', + 'incidents': isys.incidents or '', + 'criteria_values': isys.criteria_values or '', + 'criteria_justification': isys.criteria_justification or '', + 'org_measures': isys.org_measures or '', + 'tech_measures': isys.tech_measures or '', + } + self.log(f"🤖 Запрос к ИИ ({settings.get('ai_model') or 'deepseek-chat'}): проверка безопасности") + self._verify_log("Проверка безопасности через ИИ…") + + def _run(): + try: + from .verify import check_security_ai + result, err = check_security_ai( + security_data, + settings['ai_api_key'], + settings.get('ai_model'), + settings.get('ai_base_url'), + ) + self.root.after(0, lambda: self._security_ai_done(result, err)) + except Exception as e: + self.root.after(0, lambda: self._security_ai_done(None, str(e))) + + threading.Thread(target=_run, daemon=True).start() + + def _security_ai_done(self, result, err): + """Обработка ответа ИИ по безопасности.""" + if err: + self._verify_log(f"❌ {err}") + self.log(f"❌ Проверка безопасности: {err}") + return + d = result['data'] + lines = [f"Источник: {result['source']}"] + lines.append(f"Оценка: {d.get('rating', 'warning')}") + notes = d.get('notes') or [] + if notes: + lines.append("\nЗамечания:") + for n in notes: + lines.append(f" • {n}") + props = d.get('proposals') or {} + if any(props.values()): + lines.append("\nПредложения ИИ:") + for key, label in [('attacker_category', 'Нарушитель'), + ('threats', 'Угрозы'), + ('incidents', 'Инциденты'), + ('criteria_values', 'Показатели'), + ('criteria_justification', 'Обоснование'), + ('org_measures', 'Орг. меры'), + ('tech_measures', 'Тех. меры')]: + v = props.get(key) + if v: + lines.append(f" {label}: {v}") + self._verify_log('\n'.join(lines)) + self.log(f"🤖 ИИ: оценка {d.get('rating', 'warning')}, замечаний: {len(notes)}") + def _fill_subject_from_company(self): """Заполнить поля субъекта данными организации (если объект в той же организации).""" if self._current_is_idx is None: @@ -2204,6 +2436,7 @@ class DokoGenApp: os_sw=is_data.get('os_sw', ''), app_sw=is_data.get('app_sw', ''), attacker_category=is_data.get('attacker_category', ''), + attacker_characteristics=is_data.get('attacker_characteristics', ''), threats=is_data.get('threats', ''), incidents=is_data.get('incidents', ''), criteria_values=is_data.get('criteria_values', ''), diff --git a/dokogen/variables.py b/dokogen/variables.py index fc63897..a23fd8e 100644 --- a/dokogen/variables.py +++ b/dokogen/variables.py @@ -324,6 +324,7 @@ def _build_loop_data(company: Company, is_list: List) -> Dict[str, List[Dict]]: 'os_sw': getattr(isys, 'os_sw', '') or '—', 'app_sw': getattr(isys, 'app_sw', '') or '—', 'attacker_category': getattr(isys, 'attacker_category', '') or '—', + 'attacker_characteristics': getattr(isys, 'attacker_characteristics', '') or '—', 'threats': getattr(isys, 'threats', '') or '—', 'incidents': getattr(isys, 'incidents', '') or '—', 'criteria_values': getattr(isys, 'criteria_values', '') or '—', diff --git a/dokogen/verify.py b/dokogen/verify.py index 016cb93..3a48ab2 100644 --- a/dokogen/verify.py +++ b/dokogen/verify.py @@ -474,6 +474,48 @@ def compare_company_values(old_data, new_data): return diffs +def check_security_ai(security_data, api_key, model='deepseek-chat', base_url=None): + """ИИ-проверка заполненной вкладки «Безопасность» объекта КИИ (236 приказ). + + Возвращает (результат, ошибка): результат — dict с 'оценка', 'замечания', 'предложения'. + """ + if not api_key: + return None, "Не задан API-ключ ИИ (Файл → Настройки → API)" + system = ( + "Ты — эксперт по безопасности критической информационной инфраструктуры (187-ФЗ, " + "приказ ФСТЭК России от 25.12.2017 № 239 «Об утверждении Требований по обеспечению " + "безопасности значимых объектов КИИ» и приказ № 236). Проверь заполненные сведения " + "об объекте КИИ и дай рекомендации. Верни ТОЛЬКО JSON: " + '{"оценка": "ok|warning|error", ' + '"замечания": ["..."], ' + '"предложения": {"attacker_category": "...", "threats": "...", "incidents": "...", ' + '"criteria_values": "...", "criteria_justification": "...", ' + '"org_measures": "...", "tech_measures": "..."}} ' + "В «предложениях» укажи, что добавить/исправить по каждому разделу, если нужно, иначе пустые строки." + ) + user = json.dumps(security_data, ensure_ascii=False) + js, err = _ai_json(api_key, model, base_url, system, user) + if err: + return None, err + notes = js.get('замечания') or [] + if isinstance(notes, str): + notes = [notes] + proposals = js.get('предложения') or {} + if not isinstance(proposals, dict): + proposals = {} + return { + 'source': 'ИИ', + 'source_url': 'openai-совместимый API', + 'disclaimer': AI_DISCLAIMER, + 'kind': 'security', + 'data': { + 'rating': js.get('оценка', 'warning'), + 'notes': notes, + 'proposals': proposals, + }, + }, None + + def check_company_ai(company_data, api_key, model='deepseek-chat', base_url=None): """ИИ-проверка реквизитов организации.