FSTEK

FSTEC threat handling program


FSTEK ver.2

List of applicable threats

Download threats via URL or from your local computer. Select objects of influence and violators.
Get a list of applicable threats.

I created a new version of the program.
Now you can download threats using the built-in URL (https://bdu.fstec.ru/files/documents/thrlist.xlsx). You can change this URL. You can also download the list of threats from your local computer.
When you select a list of threats by URL or from a local computer (using a radiobutton), the interface changes dynamically. This is created through the properties of pack_forget() objects.


You can then download all threats from the list of threats file or select "Load Threat Sources and Impacts." By selecting the second option, an additional window will open with manual selection of threat objects and impacts. The window with the selection of threat sources and impacts is below.

In the window with the selection of threat sources and impacts, you can use the search. You can select several objects at once and move them. Click the "Download Threats" button. Now you have received a list of threats that match the selected parameters.

As in the previous version, you can see a description of each threat. Read her description. You can save the threats that are required. When you click the "Open saved document" button, a World file with previously selected threats opens. The document and table are generated using the docx library.


FSTEK

Download the list of FSTEC threats in Excel format, select the ones you need, save in Word format.

My colleagues get information about threats from FSTEC website. They download threats from the FSTEC website and analyze the data.
Searching for the needed threat, scrolling through the information, reading the description of the threat, and copying the information into Word takes a long time. They also need to create a table in Word with a specific format.
I have optimized this process a bit.
Steps:
1. Download the list of threats from the FSTEC website https://bdu.fstec.ru/files/documents/thrlist.xlsx
2. Run exe file.

3. Load the thrlist.xlsx file into the program.


The menu will display a list of threats. Clicking on a threat displays its description in the lower window.

You can select multiple threats from the list of threats. Clicking on a threat automatically displays its description.
After selecting the list of threats, click "Save selected threats".
After that, the table file will be automatically created in the directory where the FSTEK.exe program is located. When you click on the "Open saved document" button, the doc file will automatically open.


FSTEK
Code
																
#LIBRARIES
import os #operating system module
from tkinter import * #Graphical User Interfaces
from openpyxl import Workbook, load_workbook
from tkinter import filedialog as fd
from textwrap import wrap
import docx
from docx.enum.text import WD_ALIGN_PARAGRAPH #Specifies the start type of a section break
from docx.enum.section import WD_SECTION, WD_ORIENT
from docx.shared import Pt, Cm, Inches

my_list = {}

# ФУНКЦИИ
def make_rows_bold(*rows):
    for row in rows:
        for cell in row.cells:
            for paragraph in cell.paragraphs:
                for run in paragraph.runs:
                    run.font.bold = True
                    run.font.name = 'Times new Roman'
                    paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER

# EXCEL ------------------------------------------------------------------------------------------------------------------

#Путь расположению автономного файла
#Получить текущую директорию расположения программы
program_path=os.getcwd()
#Добавить к текущему пути название файла с угрозами
path=program_path+'\\thrlist.xlsx'

# ФУНКЦИИ -------------------------------------------------------------------------------------------------------------------
def open_file():
    browse_text.set("loading ...")
    # askopenfilename(): открывает диалоговое окно для выбора файла и возвращает путь к выбранному файлу. Если файл не выбран, возвращается пустая строка ""
    file_name = fd.askopenfilenames()
    if file_name != "":
        file_name = ''.join(file_name)
        get_excel_text(file_name)  # Вызов функции по конвертации Excel в текст
        label_file["text"] = "Загружен файл: " + file_name
        # myTuple = ['h', 'e', 'l', 'l', 'o']
        # s = ''.join(myTuple)
        browse_text.set("Загружен")
    else:
        label_file["text"] = "Файл не загружен"
        browse_text.set("Загрузить")


def get_excel_text(file):
    wb = load_workbook(file)  # Открыть файл с угрозами
    #wb = openpyxl.workbook.Workbook(encoding='utf-8')
    ws = wb.active
    for i in range(3, ws.max_row + 1):
        my_listbox.insert(END, "Номер : " + str(ws['A' + str(i)].value)+" Название : "+ws['B' + str(i)].value) #Работает
        my_list[i] = {}
        my_list[i]['number'] = str(ws['A' + str(i)].value)
        #_value = _row[0].value.encode('utf-8')
        my_list[i]['name'] = ws['B' + str(i)].value
        my_list[i]['desc'] = ws['C' + str(i)].value.replace('_x000D_','')


from docx.oxml import OxmlElement

def select_all():

    result = ''
    result2 = {}
    key = 0
    result2[key] = {}

    doc1 = docx.Document()
    doc1.add_section(start_type=WD_SECTION.NEW_PAGE)
    doc1.sections[1].orientation = WD_ORIENT.LANDSCAPE
    doc1.sections[1].orientation = WD_ORIENT.LANDSCAPE
    doc1.sections[1].page_width = doc1.sections[0].page_height
    doc1.sections[1].page_height = doc1.sections[0].page_width
    doc1.sections[1].bottom_margin = Pt(0)  # Нижний отступ

    for item in my_listbox.curselection():
        result = result + str(my_listbox.get(item)) + '\n'

    select = list(my_listbox.curselection())

    for i in range(len(select)):
        result2[key] = {}
        result2[key]['number'] = my_list[select[i] + 3]['number']
        result2[key]['name'] = my_list[select[i] + 3]['name']
        result2[key]['desc'] = my_list[select[i] + 3]['desc']
        key += 1


    # добавляем таблицу 3x3
    #table = doc1.add_table(rows=6, cols=6)
    table = doc1.add_table(len(result2)+1, cols=6)
    # применяем стиль для таблицы
    table.style = 'Table Grid'

    #пробуем дублирование
    # set header values

    # заполняем таблицу данными

    table.cell(0, 0).text = "Ид. УБИ"
    table.cell(0, 1).text = "Наименование УБИ"
    table.cell(0, 2).text = "Описание"
    table.cell(0, 3).text = "Источник угрозы"
    table.cell(0, 4).text = "Объект воздействия"
    table.cell(0, 5).text = "Оценка актуальности"
    table.rows[0].cells[0].paragraphs[0].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
    table.rows[0].cells[0].paragraphs[0].runs[0].font.name = 'Times new Roman'

    table.autofit = False
    table.allow_autofit = False
    table.cell(0, 0).width = Inches(1.0)
    table.cell(0, 1).width = Inches(2.0)
    table.cell(0, 2).width = Inches(2.0)
    table.cell(0, 3).width = Inches(1.0)
    table.cell(0, 4).width = Inches(1.0)
    table.cell(0, 5).width = Inches(1.0)

    tbl_header = OxmlElement('w:tblHeader')  # create new oxml element flag which indicates that row is header row
    first_row_props = table.rows[0]._element.get_or_add_trPr()  # get if exists or create new table row properties el
    first_row_props.append(tbl_header)  # now first row is the header row


    if len(result2)>0:
        for i in range(0, len(result2)):

            table.cell(i+1, 0).text = result2[i]['number']
            table.cell(i + 1, 0).paragraphs[0].runs[0].font.name = 'Times new Roman'
            table.cell(i+1, 1).text = result2[i]['name']
            table.cell(i + 1, 1).paragraphs[0].runs[0].font.name = 'Times new Roman'
            table.cell(i + 1, 1).paragraphs[0].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
            table.cell(i+1, 2).text = result2[i]['desc']
            table.cell(i+1, 2).paragraphs[0].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
            table.cell(i+1, 2).paragraphs[0].runs[0].font.name = 'Times new Roman'

        make_rows_bold(table.rows[0])

    label_save["text"] = "Сохранен файл: " + program_path.replace('\\', '/') + '/table.docx'

    doc1.save('table.docx')

def save_all():
    if len(my_list) > 0:

        doc1 = docx.Document()
        doc1.add_section(start_type=WD_SECTION.NEW_PAGE)
        doc1.sections[1].orientation = WD_ORIENT.LANDSCAPE
        doc1.sections[1].orientation = WD_ORIENT.LANDSCAPE
        doc1.sections[1].page_width = doc1.sections[0].page_height
        doc1.sections[1].page_height = doc1.sections[0].page_width
        doc1.sections[1].bottom_margin = Pt(0)  # Нижний отступ

        # добавляем таблицу 3x3
        table = doc1.add_table(rows=len(my_list)+1, cols=6)
        # применяем стиль для таблицы
        table.style = 'Table Grid'

        # заполняем таблицу данными

        table.cell(0, 0).text = "Ид. УБИ"
        table.cell(0, 1).text = "Наименование УБИ"
        table.cell(0, 2).text = "Описание"
        table.cell(0, 3).text = "Источник угрозы"
        table.cell(0, 4).text = "Объект воздействия"
        table.cell(0, 5).text = "Оценка актуальности"
        table.rows[0].cells[0].paragraphs[0].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
        table.rows[0].cells[0].paragraphs[0].runs[0].font.name = 'Times new Roman'

        tbl_header = OxmlElement('w:tblHeader')  # create new oxml element flag which indicates that row is header row
        first_row_props = table.rows[
            0]._element.get_or_add_trPr()  # get if exists or create new table row properties el
        first_row_props.append(tbl_header)  # now first row is the header row

        table.autofit = False
        table.allow_autofit = False
        table.cell(0, 0).width = Inches(1.0)
        table.cell(0, 1).width = Inches(2.0)
        table.cell(0, 2).width = Inches(2.0)
        table.cell(0, 3).width = Inches(1.0)
        table.cell(0, 4).width = Inches(1.0)
        table.cell(0, 5).width = Inches(1.0)

        #----------------------------------------------------------------------
        for i in range(3, (len(my_list)+3)):
            #print(my_list[i]['number'])
            table.cell(i-2, 0).text = my_list[i]['number']
            table.cell(i - 2, 0).paragraphs[0].runs[0].font.name = 'Times new Roman'
            table.cell(i-2, 1).text = my_list[i]['name']
            table.cell(i - 2, 1).paragraphs[0].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
            table.cell(i - 2, 1).paragraphs[0].runs[0].font.name = 'Times new Roman'
            table.cell(i-2, 2).text = my_list[i]['desc']
            table.cell(i-2, 0).paragraphs[0].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
            table.cell(i-2, 2).paragraphs[0].runs[0].font.name = 'Times new Roman'

        make_rows_bold(table.rows[0])
        doc1.save('table.docx')
        label_save["text"] = "Сохранен файл: " + program_path.replace('\\', '/') + '/table.docx'

click_before=()
click_after=()


def CurSelet(evt):
    global click_before
    global click_after

    global label_desk
    global wrapped_text
    global s

    value = str((my_listbox.get(ACTIVE)))

    if my_listbox.curselection()!=():
        last_item = my_listbox.curselection()[-1]

        # определение раздницы
    click_before = click_after
    click_after = my_listbox.curselection()
    now_click = (tuple(set(click_before) ^ set(click_after)))
    text = my_list[now_click[0] + 3]['desc']
    #text = my_list[3]['desc']

    # Вычисляем среднюю ширину символа #работает
    average_char_width = label_desk.winfo_width() / len(text)

    # Приблизительно рассчитываем количество символов, которое помещается в окне
    chars_per_line = int(label_desk.winfo_width() / average_char_width)

    # В цикле уменьшаем это количество, пока текст не станет помещаться

    lent = len(text)
    if lent < 65:
        label_desk['text'] = text
    else:
        # while chars_per_line > 72: #работает
        while chars_per_line > 136:
            wrapped_text = '\n'.join(wrap(text, chars_per_line))
            # print("wrapped_text = ", wrapped_text, "chars_per_line =", chars_per_line)
            label_desk['text'] = wrapped_text
            # root.update()
            chars_per_line -= 1

        label_desk['text'] = wrapped_text

#создать словарь. Вытащить все в него. И оперировать с описанием


def click_button():
    #os.system('C:/Users/valentinakiseleva/Desktop/2/26.01.2023/table.docx')
    #label_save["text"] = "Сохранен файл: " + program_path.replace('\\', '/') + '/table.docx'
    os.system(program_path.replace('\\', '/') + '/table.docx')

# ГРФИЧЕСКИЙ ИНТЕРФЕЙС ----------------------------------------------------------------------------------------------------------

# ГЛАВНОЕ ОКНО
root = Tk()
root.title("БДУ Угроз ФСТЭК России")  # Заголовок
root.geometry("1150x700")  # Ширина/Высота окна

# ГРФИЧЕСКИЕ ОБЪЕКТЫ

# ФРЕЙМ ДЛЯ УГРОЗ С LISTBOX
my_frame = Frame(
    root)  # создать объект фрейм от класса Frame и разм на главном окне. Фрейм нужен для группировки объектов
my_frame.place(x=5, y=120)  #

# LISTBOX С УГРОЗАМИ И СКРОЛОМ
my_scrollbar = Scrollbar(my_frame, orient=VERTICAL)  # создать объект прокрутки
my_listbox = Listbox(my_frame, width=180, yscrollcommand=my_scrollbar.set,
                     selectmode=MULTIPLE)  # создать объект my_listbox и разместить на my_frame
my_scrollbar.config(command=my_listbox.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
my_listbox.pack(pady=15)

# ФРЕЙМ ДЛЯ LABEL "СПИСОК УГРОЗ"
labelframe = LabelFrame(root)
labelframe.place(x=6, y=110)
# метка "Список угроз"
label_names = Label(labelframe, text="Список угроз", width=155, bg="#DCDCDC")
label_names.pack()

# ФРЕЙМ ДЛЯ LABEL "ОПИСАНИЕ УГРОЗЫ"
labelframe = LabelFrame(root)
labelframe.place(x=5, y=375)
# метка с названием "Описание угрозы"
label_names = Label(labelframe, text="Описание угрозы", width=156, bg="#DCDCDC")
label_names.pack()

# ФРЕЙМ ДЛЯ LABEL С ТЕКСТОМ ОПИСАНИЯ УГРОЗЫ"
label_desk = LabelFrame(root)
label_desk.place(x=5, y=400)
# метка с описанием угрозы
label_desk = Label(label_desk, text="Описание угрозы", width=121, height=15,
                   anchor="nw", font=('times',
                                      13))  # выровнить текст описания слева вверху, параметр anchor="nw"
label_desk.pack()

# # ФРЕЙМ ДЛЯ LABEL "ЗАГРУЖАТЬ УГРОЗЫ ИЗ"
# метка "Список угроз"
canvas= Canvas(root, bd=2, highlightthickness=2)
canvas.place(x=5, y=10)
label2 = Label(canvas, text="Загрузить файл с угрозами", borderwidth=0, font=("Сourier new", 12, 'bold'))
label2.pack(side=LEFT, padx=0, pady=0)

canvas_file= Canvas(root, bd=2, highlightthickness=2,width=1000, height=5)
canvas_file.place(x=5, y=40)
label_file = Label(canvas_file, text="Сейчас файл не загружен", width=1000, height=1, anchor="nw", font=("Сourier new", 10))
label_file.pack()



# КНОПКА "Загрузить"
browse_text = StringVar()
browse_btn = Button(root, textvariable=browse_text,
                    command=lambda: open_file(), font=("Сourier new", 12),
                    bg="#828282", fg="white", height=1, width=15)

browse_text.set("Загрузить")  # set - заносит значение в метапеременную
# browse_btn.grid(column=4,row=0)
#browse_btn.place(x=1150, y=120)
browse_btn.place(x=5, y=70)

"#959d9d"

# КНОПКА "Сохранить выбранные угрозы"
my_button4=Button(root, text="Сохранить выбранные угрозы", command=select_all, font=("Сourier new", 12),
                    bg="#828282", fg="white", height=1, width=27)
my_button4.place(x=5, y=300)

# КНОПКА "Открыть сохраненный документ"
my_button5=Button(root, text="Открыть сохраненный документ", command=click_button, font=("Сourier new", 12), bg="#828282", fg="white", height=1, width=27)
my_button5.place(x=270, y=300)
# КНОПКА "Сохранить все угрозы"
my_button6=Button(root, text="Сохранить все угрозы", command=save_all, font=("Сourier new", 12),
                    bg="#828282", fg="white", height=1, width=27)
my_button6.place(x=535, y=300)

canvas_save= Canvas(root, bd=2, highlightthickness=2,width=1000, height=5)
canvas_save.place(x=5, y=340)
label_save = Label(canvas_save, text="Выбранные угрозы не сохранены в файл", width=1000, height=1, anchor="nw", font=("Сourier new", 10))
label_save.pack()

# ДЕЙСТВИЯ ------------------------------------------
#my_listbox.bind('<>',CurSelet)

my_listbox.bind('<<ListboxSelect>>',CurSelet)
root.mainloop()  # Отрисовка главного окна

FSTEK.exe
FSTEK.py

FSTEK - load list from URL

Load list of threats from URL

Уou can download the list of threats by the URL. The URL has already been added to the program. You can select it manually.
By this code you can download the list of threats from the Internet or the corporate network with a proxy.

  • Сertificate verification is skipped.
  • URL validation and download file type check added.
  • The program will show the directory of the downloaded file.

Code
	
import tkinter as tk
import requests
import os
import re
import mimetypes

downloadurl = 'https://bdu.fstec.ru/files/documents/thrlist.xlsx'

#Функция проверки URL загрузки угроз ФСТЭК
def isValidURL(str):
    regex = ("((http|https)://)(www.)?" +
             "[a-zA-Z0-9@:%._\\+~#?&//=]" +
             "{2,256}\\.[a-z]" +
             "{2,6}\\b([-a-zA-Z0-9@:%" +
             "._\\+~#?&//=]*)")

    p = re.compile(regex)

    if (str == None):
        return False

    if (re.search(p, str)):
        return True
    else:
        return False

#Функция проверки URL, что загружаемый файл является типом Excel
def isExcelMime(str):
    #Встроенный тип файла xlsx
    xlsx=('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', None)

    if ((mimetypes.guess_type(str)) == xlsx):
        return True
    else:
        return False

def show_message():

    #label["text"] = entry.get()  # получаем введенный текст
    lab_after_change["text"] = " URL изменен"  # получаем введенный текст
    global downloadurl
    print(utl_entry.get())
    downloadurl = utl_entry.get()
    print(type(downloadurl))
    filename = downloadurl[downloadurl.rfind('/') + 1:]  # rfind - найти индекс самого последнего символа '/'
    print(filename)

# загрузка thrlist по блокам и запись в файл
def loadfromweb(req_get, downloadurl):
    filename = req_get.url[downloadurl.rfind('/') + 1:]  # rfind - найти индекс самого последнего символа '/'
    print(filename)
    with open(filename, 'wb') as f:
        for chunk in req_get.iter_content(chunk_size=8192):
            if chunk:
                f.write(chunk)
    print(os.getcwd())

# изменить цвет кнопки при наведении на нее мышкой
def changeOnHover(button, colorOnHover, colorOnLeave):
    button.bind("", func=lambda e: button.config(  # 2
        background=colorOnHover))
    button.bind("", func=lambda e: button.config(  # 3 #4
        background=colorOnLeave))

def click_button():
    global downloadurl

    if ((isValidURL(downloadurl) == True) and (isExcelMime(downloadurl) == True)):
        #print("Yes")
        try:
            req = requests.get(downloadurl, headers={'User-agent': 'your bot 0.1'},
                               verify=False)  # без проверки сертификата
            # если ответ успешен, исключения задействованы не будут
            req.raise_for_status()
        except Exception as err:
            proxies = {'https': 'http://10.105.0.217:3128'}
            req = requests.session()
            req.proxies.update(proxies)
            req_get = req.get(downloadurl, headers={'User-agent': 'your bot 0.1'},
                              verify=False)
            loadfromweb(req_get, downloadurl)  # В функцию  loadfromweb передается req_get -сессия
            lab_file2["text"]="Файл загружен в директорию " + os.getcwd()
        # else выполняется в случае успеха в блоке try
        else:
            loadfromweb(req, downloadurl)  # В функцию  loadfromweb передается req - запрос
            lab_file2["text"] = "Файл загружен в директорию " + os.getcwd()

    else:
        #print("No")
        lab_file2["text"] = " URL для загрузки файла Excel некорректен"


root = tk.Tk()
root.title('Загрузка угроз ФСТЭК')

root.geometry("1100x320")  # Размер окна интерфейса

# Виджет для загрузки файла с угрозами
canv_load = tk.Canvas(root, width=200, height=400, borderwidth=3, relief='ridge', bg='#DCDCDC')
canv_load.pack(padx=20, pady=20,
               anchor=tk.NW)  # отступ виджета от границ контейнера по горизонтали/вертикали NW-верхний левый угол

lab_load = tk.Label(canv_load, text="Загрузить файл с угрозами", width=100, height=1, anchor="nw",
                    # NW-верхний левый угол
                    font=("Сourier new", 12, 'bold'), bg='#DCDCDC')
lab_load.pack(padx=10,pady=(10, 5))  # pady - верхний/нижний отступ виджета от границ контейнера по вертикали

lab_url = tk.Label(canv_load, text="Файл будет загружен с указанного URL.",width=100, height=1,anchor="nw",font=("Сourier new", 10),bg='#DCDCDC')
lab_url.pack(padx=10, pady=0,anchor="nw")
lab_url2 = tk.Label(canv_load, text="При необходимости введите новый URL.",width=100, height=1,anchor="nw",font=("Сourier new", 10), bg='#DCDCDC')
lab_url2.pack(padx=8, pady=5,anchor="nw")

#Label для отображения URL после изменения пользователем
lab_after_change = tk.Label(canv_load, width=100, height=1,anchor="nw",font=("Сourier new", 10), bg='#DCDCDC')
lab_after_change.pack(padx=5, pady=0,anchor="nw")

#Поле для ввода URL
utl_entry = tk.Entry(canv_load,width=100, font=("Times new roman", 12))
utl_entry.insert(0, downloadurl)
utl_entry.pack(padx=10, pady=0,anchor="nw")

#Кнопка для изменения URL пользователем
change_url_btn = tk.Button(canv_load, text="Изменить URL",font=("Сourier new", 12), bg="#828282", fg="white",height=1, width=15, command=show_message)
change_url_btn.pack(padx=10, pady=0,anchor="nw")

#Label для отображения состояния загрузки файл угроз (Загружен или есть ошибка URL)
lab_file2 = tk.Label(canv_load, text="Сейчас файл не загружен",width=100, height=1,anchor="nw",font=("Сourier new", 10),
                    bg='#DCDCDC')
lab_file2.pack(padx=10, pady=(5, 0),anchor="nw")

#Кнопка для загрузки Угроз
browse_btn = tk.Button(canv_load, text="Загрузить", font=("Сourier new", 12), bg="#828282", fg="white",
                       height=1, width=15, command=click_button)
browse_btn.pack(padx=10, pady=12, anchor="nw")

#Функция для изменения цвета кнопки при наведении на нее мышкой
changeOnHover(browse_btn, "#C7D0CC", "#828282")  # изменить цвет кнопки при наведении на нее мышкой
changeOnHover(change_url_btn, "#C7D0CC", "#828282")  # изменить цвет кнопки при наведении на нее мышкой

root.mainloop()  # Нарисовать интерфейс программы