Как мы парсили информацию об уязвимостях

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Итак, существует большое количество платных сервисов, например vuldb.com или vulners.com, которые предоставляют информацию об уязвимостях по подписке. В этой статье я приведу бесплатные источники, с которыми за полгода мы собрали информацию об 3153 уязвимости, для их дальнейшего ресерча.

Если вы открыли эту статью, то я надеюсь что знакомы с такими понятиями как cve, cvss, cpe, но, на всякий случай сделал сущности кликабельными, чтобы Вы могли освежить память.

Далее в статье будет питоновский код, и там где будет слишком много строк, для Вашего удобства я буду прятать его в спойлеры

Теперь, когда Вы освежили знания, определимся со следующими вопросами:

  • Откуда собирать информацию об уязвимостях?

  • Какую именно информацию собирать об уязвимостях?

  • Где и как это всё хранить?

По ходу статью я отвечу на все вопросы. Ну что ж, приступим.

Информацию о новых уязвимостях мы собираем со следующих источников:

  • Бюллетени НКЦКИ

  • Сайт opencve.io

  • Сайт cvetrends.com

С вышеперечисленных сайтов собираются только идентификаторы уязвимостей (например CVE-2021-40444), после чего по каждому идентификатору делается запрос на сайт nvd.nist.gov (через питоновскую библиотеку) и уже оттуда мы парсим всю необходимую нам информацию.

Теперь подробнее о реализации скрапинга идентификаторов уязвимостей с агрегаторов информации:

Бюллетени НКЦКИ

Ну тут всё понятно - рекомендации по устранению уязвимостей от Национального координационного центра по компьютерным инцидентам.

Сами бюллетени выпускаются в формате pdf, поэтому функции для парсинга будут выглядеть так:

Пример кода для скрапинга
# parse bulletine NKCKI------------------------------------------------------------------------------------------------
def get_cve_list(name_list):
    cve_list = []
    cve_list_repeat = []
    for name in name_list:
        pdf_file = open(f'{PATH}/{name}', 'rb')
        read_pdf = PyPDF2.PdfFileReader(pdf_file)
        number_of_pages = read_pdf.getNumPages()
        data = ''
        for i in range(number_of_pages):
            page = read_pdf.getPage(i)
            page_content = page.extractText()
            data1 = json.dumps(page_content)
            data += data1

        rez = ''
        for n, item in enumerate(data, start=0):
            if item != '\\':
                rez += data[n]

        result = ''
        for n, item in enumerate(rez, start=0):
            if item != 'n':
                result += rez[n]
        rez1 = result.replace(" ", "")

        regex = re.findall(r'CVE-\d{4}-\d{4,8}', rez1)
        for cve in regex:
            cve_list_repeat.append(cve)
    for item in cve_list_repeat:
        if item not in cve_list:
            cve_list.append(item)
    return cve_list


def get_links_for_bulletine():
    links = []
    for page in range(3):
        r = requests.get("https://safe-surf.ru/specialists/bulletins-nkcki/?PAGEN_1={}".format(page))
        soup = BeautifulSoup(r.text, "html.parser")
        for vuln in soup.find_all("div", "blockBase blockBulletine"):
            bulletin_pdf_url = "https://safe-surf.ru{}".format(vuln.find('h4').find('a')['href'])
            links.append(bulletin_pdf_url)
    return links


def create_pdf_file(links):
    for str in links:
        get = requests.get(str)
        name = str.replace("https://safe-surf.ru/upload/VULN/", "")
        check_file = os.path.exists(f'{PATH}/{name}')
        if check_file == 0:
            with open(f'{PATH}/{name}', 'wb') as f:
                f.write(get.content)
        else:
            print(f'file {name} already exists')


def get_name_list(path):
    name_list = os.listdir(path)
    return name_list


def remove_pdf_file(path):
    for item in path:
        os.remove(item)

links = get_links_for_bulletine()
create_pdf_file(links)
name_list = get_name_list(PATH)  # Получение имен скаченных файлов для парсинга
cve_line = get_cve_list(name_list)  # Получение списка cve из биллютеня НКЦКИ
print(cve_line)

# remove pdf buffer--------------------------------------------------------------------------------------------------- 
time.sleep(1)
path_to_remove_pdf = []
for item in name_list:
    path_to_remove_pdf.append(f'{PATH}/{item}')
remove_pdf_file(path_to_remove_pdf)

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

На выходе получается следующее:

['CVE-2022-30472', 'CVE-2022-30473', 'CVE-2022-30475', 'CVE-2022-30476', 'CVE-2022-30477', 'CVE-2022-30474', 'CVE-2022-30190', 'CVE-2022-1888', 'CVE-2022-1934', 'CVE-2022-26700', 'CVE-2022-26716', 'CVE-2022-26719', 'CVE-2022-26709', 'CVE-2022-30294', 'CVE-2022-26717', 'CVE-2022-30293', 'CVE-2022-26945', 'CVE-2022-27184', 'CVE-2022-28690', 'CVE-2022-30540', 'CVE-2022-29078', 'CVE-2021-23820', 'CVE-2022-22963', 'CVE-2022-22965', 'CVE-2021-3634', 'CVE-2022-1154', 'CVE-2022-1271', 'CVE-2022-23772', 'CVE-2022-25235', 'CVE-2022-25313', 'CVE-2022-25314', 'CVE-2022-25315', 'CVE-2022-27871', 'CVE-2022-28463', 'CVE-2021-3254', 'CVE-2022-1517', 'CVE-2022-1518', 'CVE-2022-1519', 'CVE-2022-1521', 'CVE-2022-29897', 'CVE-2022-29898', 'CVE-2022-31479', 'CVE-2022-31480', 'CVE-2022-31481', 'CVE-2022-26134']

Сайт opencve.io

Особенность данного сайта в том, что он предоставляет информацию только по тем продуктам и вендорам (тут пожалуй следует упомянуть про инвентаризацию ваших активов), которые вы выберете в своем профиле (да, нужна регистрация, но не нужно никакой платной подписки). Сам сайт также парсит nvd.nist.gov и либо заводит информацию о новой уязвимости (is a new CVE), либо обновляет информацию об уже заведенной (has changed):

сайт opencve.io
сайт opencve.io

Мы решили собирать информацию только о новых уязвимостях (is a new CVE), потому что обновляем информацию об уже заведенных сами:

Пример кода для скрапинга
# parse opencve.io---------------------------------------------------------------------------------------------------- 
def parsing_opencve():
    url1 = 'https://www.opencve.io/login/'
    url2 = 'https://www.opencve.io/login'
    csrf_token = ''
    s = requests.Session()
    response = s.get(url1)
    soup = BeautifulSoup(response.text, 'lxml')

    # Get CSRF
    for a in soup.find_all('meta'):
        if 'name' in a.attrs:
            if a.attrs['name'] == 'csrf-token':
                csrf_token = a.attrs['content']

    # Authentication
    s.post(
        url2,
        data={
            'username': USERNAME,
            'password': PASSWORD,
            'csrf_token': csrf_token,
        },
        headers={'referer': 'https://www.opencve.io/login'},
        verify=False
    )
    # Get new CVE
    cve_line = []
    for page_num in range(1, 20):
        pagination = f'https://www.opencve.io/?page={page_num}'
        resp = s.get(pagination)
        parse = BeautifulSoup(resp.text, 'lxml')
        for cve in parse.find_all('h3', class_='timeline-header'):
            index = cve.text.find('has changed')
            if index == -1:
                cve_line.append(cve.text.replace(' is a new CVE', ''))

    cve_line_no_replic = []
    for item in cve_line:
        if item not in cve_line_no_replic:
            cve_line_no_replic.append(item[:-1])
    return cve_line_no_replic

cve_line = parsing_opencve()  # Получение списка новых cve с сайта opencve.io
print(cve_line)

На вход скрипт принимает только креды (логин и пароль) от вашей учетной записи на opencve.io.

На выходе также, список идентификаторов уязвимостей:

['CVE-2022-29170', 'CVE-2022-28660', 'CVE-2022-20795', 'CVE-2022-22191', 'CVE-2022-22193', 'CVE-2022-22197', 'CVE-2022-22196', 'CVE-2022-22185', 'CVE-2022-22186', 'CVE-2022-22182', 'CVE-2022-22188', 'CVE-2022-22181', 'CVE-2022-22198', 'CVE-2022-24828', 'CVE-2022-24812', 'CVE-2022-26148', 'CVE-2022-22943', 'CVE-2022-0711', 'CVE-2022-24329', 'CVE-2021-29220', 'CVE-2022-21824', 'CVE-2021-44531', 'CVE-2021-44532', 'CVE-2021-44533', 'CVE-2021-22041', 'CVE-2021-22040', 'CVE-2022-21703', 'CVE-2022-21713', 'CVE-2022-21702', 'CVE-2022-23601', 'CVE-2022-22938', 'CVE-2021-46088', 'CVE-2022-22176', 'CVE-2022-22156', 'CVE-2022-22153', 'CVE-2022-22154', 'CVE-2022-22177', 'CVE-2022-22172', 'CVE-2022-22167', 'CVE-2022-22170', 'CVE-2022-22166', 'CVE-2022-22163', 'CVE-2022-22176', 'CVE-2022-22160', 'CVE-2022-22170', 'CVE-2022-22180', 'CVE-2022-22167', 'CVE-2022-22172', 'CVE-2022-22161', 'CVE-2022-22171', 'CVE-2022-22154', 'CVE-2022-22173', 'CVE-2022-22169', 'CVE-2022-22177', 'CVE-2022-22178', 'CVE-2022-22156', 'CVE-2022-22168', 'CVE-2022-21673', 'CVE-2022-23134', 'CVE-2022-23133', 'CVE-2022-23132', 'CVE-2022-23131', 'CVE-2021-22045', 'CVE-2020-19316', 'CVE-2021-43815', 'CVE-2021-43813', 'CVE-2021-43803', 'CVE-2021-43808', 'CVE-2021-43798', 'CVE-2021-41270', 'CVE-2021-41267', 'CVE-2021-41268', 'CVE-2021-3935']

Сайт cvetrends.com:

На данном сайте отображаются топ-10 уязвимостей на данный момент.
С него также "дергаем" идентификаторы уязвимостей:

Пример кода для скрапинга
# parse cvetrends.com--------------------------------------------------------------------------------------------------
def get_top_cve_list():
    link = 'https://cvetrends.com/api/cves/24hrs'
    r = requests.get(link)
    soup = BeautifulSoup(r.text, "lxml")
    regex = re.findall(r'"cve": "CVE-\d{4}-\d{4,8}"', soup.get_text())
    top_cve_list = []
    for item in regex:
        top_cve_list.append(re.search(r'CVE-\d{4}-\d{4,8}', item).group())

    return top_cve_list

top_cve_list = get_top_cve_list()  # Получение топа cve с cvetrends.com
print(top_cve_list)

На вход данный скрипт ничего не принимает, на выходе список идентификаторов:

['CVE-2022-30190', 'CVE-2022-26134', 'CVE-2022-32275', 'CVE-2022-32250', 'CVE-2022-30075', 'CVE-2022-32276', 'CVE-2202-26134', 'CVE-2022-29464', 'CVE-2020-3452', 'CVE-2021-40444']

Теперь по каждой уязвимости необходимо собрать нужную нам информацию:

  • Название уязвимости

  • Описание уязвимости

  • Дата публикации уязвимости

  • Дата выявления уязвимости

  • CPE для данной уязвимости

  • CVSSv3 Score

  • CVSSv3 Vector

  • Продукты и версии пакетов для которых актуальна уязвимость

  • Необходимые ссылки (далее в статье будет пояснение)

  • Наличие эксплоитов в открытых источниках

  • Наличие смягчающих мер (далее в статье также будет пояснение)

  • Иные нужные сущности

Теперь обо всём по порядку.

Название уязвимости

Ну тут всё очевидно. Название уязвимости это и есть идентификатор уязвимости, однако мы пошли чуть дальше и добавили несколько паттернов, чтобы даже по названию можно было сразу классифицировать уязвимость:

pattern = ['Stack-based buffer overflow', 'Arbitrary command execution', 'Obtain sensitive information',
           'Local privilege escalation', 'Security Feature Bypass', 'Out-of-bounds read', 'Out of bounds read',
           'Denial of service', 'Denial-of-service', 'Execute arbitrary code', 'Expose the credentials',
           'Cross-site scripting (XSS)', 'Privilege escalation', 'Reflective XSS Vulnerability',
           'Execution of arbitrary programs', 'Server-side request forgery (SSRF)', 'Stack overflow',
           'Execute arbitrary commands', 'Obtain highly sensitive information', 'Bypass security',
           'Remote Code Execution', 'Memory Corruption', 'Arbitrary code execution', 'CSV Injection',
           'Heap corruption', 'Out of bounds memory access', 'Sandbox escape', 'NULL pointer dereference',
           'Remote Code Execution', 'RCE', 'Authentication Error', 'Use-After-Free', 'Use After Free',
           'Corrupt Memory', 'Execute Untrusted Code', 'Run Arbitrary Code', 'heap out-of-bounds write',
           'OS Command injection', 'Elevation of Privilege', 'Race condition', 'Access violation', 'Infinite loop']

Например, на входе имеем `CVE-2021-40444, на выходе получаем:

CVE-2021-40444 - Remote Code Execution

Описание уязвимости

Тут уже мы воспользуемся волшебной библиотекой cpe для питона:

def get_cve_data(cve):
    r = nvdlib.getCVE(cve, cpe_dict=False)
    cve_name = ''
    cve_info = r.cve.description.description_data[0].value
    for item in pattern:
        if item.upper() in cve_info.upper():
            cve_name = cve + " - " + item
            break
        else:
            cve_name = cve

В переменную cve_info как раз записывается описание уязвимости. А вот цикл for item in pattern: тут нужен для того, чтобы добавить паттерн к названию уязвимости из примера выше.

Дата публикации и дата выявления уязвимости

В данном случае получаем переменные также с помощью библиотеки cpe в теле функции get_cve_data :

publishedDate = r.publishedDate[:-7]
lastModifiedDate = r.lastModifiedDate[:-7]

CPE

Код в теле функции get_cve_data:

cve_cpe_nodes = r.configurations.nodes
cpe_nodes = ast.literal_eval(str(r.configurations))

CVSSv3 Score и CVSSv3 Vector

Код в теле функции get_cve_data:

score = r.v3score
vector = r.v3vector

Продукты и версии пакетов для которых актуальна уязвимость

Код в теле функции get_cve_data :

product_vendor_list = []
product_image_list = []
version_list = []
for cpe in cpe_for_product_vendors:
    cpe_parsed = CPE(cpe)
    product = cpe_parsed.get_product()
    vendor = cpe_parsed.get_vendor()
    product_vendor = vendor[0] + " " + product[0] if product != vendor else product[0]
    product_vendor_list.append(product_vendor)
    product_image_list.append(product[0])
    version = cpe_parsed.get_version()
    if version[0] != '-' and version[0] != '*':
        version_list.append(f'{product[0]} - {version[0]}')

Необходимые ссылки

Тут под необходимыми ссылками понимаются следующие - ссылка на производителя, ссылка на решение, ссылка на эксплоит, ссылка на ресерч разбор уязвимости. На nvd.nist.gov это выглядит так:

Ссылки на сайте nvd
Ссылки на сайте nvd

У нас в коде это реализовано так:

links.append(r.url)

Наличие эксплоитов в открытых источниках

Для того, чтобы найти эксплоит или PoC (proof-of-concept) мы парсим теги на сайте nvd.nist.gov и пока два репозитория на гитхабе:

Теги на сайте nvd
Теги на сайте nvd
Пример кода для парсинга
# check https://github.com/nu11secur1ty/ ----------------------------------------------------------------------------- 
def get_exploit_info(cve):
    link = 'https://github.com/nu11secur1ty/CVE-mitre'
    link_2 = 'https://github.com/nu11secur1ty/CVE-mitre/tree/main/2022'
    default_link = ''
    poc_cve_list = []
    r = requests.get(link)
    soup = BeautifulSoup(r.text, "html.parser")
    for cve_id in soup.find_all("span", class_="css-truncate css-truncate-target d-block width-fit"):
        regex = re.findall(r'CVE-\d{4}-\d{4,8}', cve_id.text)
        if regex:
            poc_cve_list.append(str(regex[0]))

    r = requests.get(link_2)
    soup = BeautifulSoup(r.text, "html.parser")
    for cve_id in soup.find_all("span", class_="css-truncate css-truncate-target d-block width-fit"):
        regex = re.findall(r'CVE-\d{4}-\d{4,8}', cve_id.text)
        if regex:
            poc_cve_list.append(str(regex[0]))

    for item in poc_cve_list:
        if cve == item:
            default_link = f'https://github.com/nu11secur1ty/CVE-mitre/tree/main/{cve}'
    return default_link


# check https://github.com/trickest/cve/ ----------------------------------------------------------------------------- 
def get_exploit_info_2(cve):
    # print('get_exploit_info_2') # DEBUG
    year = cve.split('-')[1]
    link = f'https://github.com/trickest/cve/tree/main/{year}'
    r = requests.get(link)
    soup = BeautifulSoup(r.text, "html.parser")
    default_link = ''
    for cve_id in soup.find_all("span", class_="css-truncate css-truncate-target d-block width-fit"):
        if f'{cve}.md' == cve_id.text:
            default_link = f'**trickest/cve** - https://github.com/trickest/cve/tree/main/{year}/{cve}.md'
            break
    return default_link


exploit_links = []
for t in r.cve.references.reference_data:
    links.append(t.url)
    if 'Exploit' in t.tags:
        exploit_links.append(t.url)
if get_exploit_info(cve):  # check https://github.com/nu11secur1ty/
    exploit_links.append(get_exploit_info(cve))
if get_exploit_info_2(cve):  # check https://github.com/trickest/cve/
    exploit_links.append(get_exploit_info_2(cve))

Наличие смягчающих мер

Mitigations (меры по смягчению последствий) - это типовые наборы правил, позволяющих снизить риски от эксплуатации уязвимости. В данной ситуации мы парсим пульсы на сайте otx.alienvault.com. Пульсы - это ресерчи пользователей, в которых они описывают конкретные тактики и техники для эксплуатации уязвимости, а также IoC-ки, но это уже совсем другая история. Стоит заметить что данный способ парсинга не совсем точный, но если вы хотя бы немного ориентируетесь в матрице Mitre Att&ck, то эта информация будет для вас полезна.

Итак, скрипт:

Пример кода для скрапинга
# parse mitre.org----------------------------------------------------------------------------------------------------- 
def get_mitigations(tactic_id):
    # print("get_mitigations") # DEBUG
    tactic_url = f'https://attack.mitre.org/techniques/{tactic_id}'
    r_get_tactic_info = requests.get(tactic_url)
    s_get_tactic_info = BeautifulSoup(r_get_tactic_info.text, 'html.parser')

    #  0 - Procedure Examples
    #  1 - Mitigations
    #  2 - Detection
    try:
        mitigations = s_get_tactic_info.find_all("table", class_="table table-bordered table-alternate mt-2")[1].text
        buff_string_1 = str(mitigations).replace("ID", "").replace("Mitigation", "").replace("Description", "").replace(
            "\n\n", "")
        buff_list = buff_string_1.split('\n')
        buff_list.pop(0)  # Remove empty element, index - 0
        return_list = []
        for count in range(int(len(buff_list) / 3)):
            id_mit = buff_list[count * 3].rstrip().lstrip()
            name_mit = buff_list[count * 3 + 1].rstrip().lstrip()
            desc_mit = buff_list[count * 3 + 2].rstrip().lstrip()
            return_list.append(f'[{id_mit} - {name_mit}](https://attack.mitre.org/mitigations/{id_mit}) - {desc_mit}')
        return return_list
    except:
        pass


# parse alienvault.com------------------------------------------------------------------------------------------------ 
def get_ttp(cve):
    # print('get_ttp') # DEBUG
    headers_alienvault = {'X-OTX-API-KEY': API_KEY_ALIENVAULT}
    url = f'https://otx.alienvault.com/api/v1/indicators/cve/{cve}'
    pattern_default_answer = '{"detail": "endpoint not found"}'
    r_get_cve_info = requests.get(url, headers=headers_alienvault)
    s_get_cve_info = BeautifulSoup(r_get_cve_info.text, 'lxml')
    tactic_list = []
    if str(s_get_cve_info.p.contents).replace("['", "").replace("']", "") == pattern_default_answer:
        print(f'no information for {cve}')  # DEBUG
    else:
        content = json.loads(str(s_get_cve_info.p).replace("<p>", "").replace("</p>", ""))
        count = int(content['pulse_info']['count'])
        subs_list = []
        for i in range(count):
            subs_list.append(content['pulse_info']['pulses'][i]['subscriber_count'])
        tactic_list = []
        for i, item in enumerate(subs_list):
            max_test = max(subs_list)
            if content['pulse_info']['pulses'][i]['attack_ids']:
                for j in range(len(content['pulse_info']['pulses'][i]['attack_ids'])):
                    tactic_list.append(content['pulse_info']['pulses'][i]['attack_ids'][j]['id'])
                break
            else:
                subs_list.remove(max_test)
        else:
            return f'NO TTP'
    tactic_list = list(set(tactic_list))  # replace replications
    technique_list = []
    for i, item in enumerate(tactic_list):
        if len(str(item)) > 6:
            technique_list.append(str(item).replace(".", "/"))

    result_list = []
    if technique_list:
        for tactic_id in technique_list:
            if get_mitigations(tactic_id):
                for item in get_mitigations(tactic_id):
                    result_list.append(item)
        return list(set(result_list))
    else:
        for tactic_id in tactic_list:
            if get_mitigations(tactic_id):
                for item in get_mitigations(tactic_id):
                    result_list.append(item)
        return list(set(result_list))

На вход он принимает API-KEY от вашей учетной записи на сайте (да да, опять регистрация, но никакой платной подписки) и идентификатор уязвимости.
На выходе получаем список митигейшенов для конкретной уязвимости (в разметке markdown):

['[M1047 - Audit ](https://attack.mitre.org/mitigations/M1047)', '[M1017 - User Training ](https://attack.mitre.org/mitigations/M1017)', '[M1028 - Operating System Configuration ](https://attack.mitre.org/mitigations/M1028)', '[M1038 - Execution Prevention ](https://attack.mitre.org/mitigations/M1038)', '[M1040 - Behavior Prevention on Endpoint ](https://attack.mitre.org/mitigations/M1040)', '[M1018 - User Account Management ](https://attack.mitre.org/mitigations/M1018)', '[M1026 - Privileged Account Management ](https://attack.mitre.org/mitigations/M1026)', '[M1042 - Disable or Remove Feature or Program ](https://attack.mitre.org/mitigations/M1042)']

Иные нужные сущности

EPSS scoring (оценка вероятности использования уязвимости в реальных компьютерных атаках) очень полезная и интересная штука, советую почитать на досуге.

Код
def get_data_file():
    data_link = "https://epss.cyentia.com/epss_scores-current.csv.gz"
    response = requests.get(data_link)
    with open(buffer, 'wb') as f:
        f.write(response.content)
    with gzip.open(buffer, 'rb') as f_in:
        with open(filename, 'wb') as f_out:
            shutil.copyfileobj(f_in, f_out)


def info_data(filename):
    with open(filename) as csv_file:
        csv_reader = csv.reader(csv_file, delimiter=',')
        my_file = open(fileout, "w")
        for row in csv_reader:
            try:
                epss = str(round(float(str(100 / (1 / float(row[1])))[0:5]), 2))
                data = str(row[0] + ',' + epss + '\n')
                my_file.write(data)
            except:
                pass
        my_file.close()

filename = f'{PATH}/DATA.csv'
fileout = f'{PATH}/DATA1.csv'
buffer = f'{PATH}/tar.csv.gz'
get_data_file()
info_data(filename)
cve_line = []
procent_line = []
with open(fileout) as csv_file:
    csv_reader = csv.reader(csv_file, delimiter=',')
    for row in csv_reader:
        cve_line.append(row[0])
        procent_line.append(row[1])

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

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

Код
def get_kb(cve):

    msrc_url = f"https://api.msrc.microsoft.com/cvrf/v2.0/Updates('{cve}')"
    get_cvrf_link = requests.get(msrc_url, verify=False)
    id_for_cvrf = re.search(r'\d{4}-\w{3}', get_cvrf_link.text)
    cvrf_url = f'https://api.msrc.microsoft.com/cvrf/v2.0/document/{id_for_cvrf[0]}'
    get_info = requests.get(cvrf_url, verify=False)
    soup = BeautifulSoup(get_info.text, "lxml")

    parse_list = []
    buff = ''
    for item in soup.text:
        if item == '\n':
            parse_list.append(buff)
            buff = ''
        else:
            buff += item

    parse_string = ''
    for j, item in enumerate(parse_list):
        regex = re.findall(cve, parse_list[j])
        if regex:
            parse_string = parse_list[j]

    kb_list = re.findall(r'KB\d{7}', parse_string)
    not_remove_list_of_kb = []
    for kb in kb_list:
        if kb not in not_remove_list_of_kb:
            not_remove_list_of_kb.append(kb)

    link_list = []
    for kb in not_remove_list_of_kb:
        kb_url = f'https://catalog.update.microsoft.com/v7/site/Search.aspx?q={kb}'
        test = requests.get(kb_url, verify=False)
        if test.status_code == 200:
            url_get_product = f'https://www.catalog.update.microsoft.com/Search.aspx?q={kb}'
            get_product = requests.get(url_get_product, verify=False)
            soup_get_product = BeautifulSoup(get_product.text, "lxml")
            product_buff = ''
            for item in soup_get_product.find_all('a', class_='contentTextItemSpacerNoBreakLink'):
                product_buff = item.text
            product = product_buff.strip()
            # Output: Windows 10 Version 1809 for x86-based Systems
            #link_list.append(f'[{kb}]({kb_url}) - {(product.partition("for")[2])[:-12]}')
            if product:
                # Output: 2022-01 Cumulative Update for Windows 10 Version 1809 for x86-based Systems (KB5009557)
                link_list.append(f'[{kb}]({kb_url}) - {product[:-12]}')

    return link_list

На вход скрипт получается идентификатор уязвимости, на выходе список обновлений безопасности:

2022-04 Cumulative Update for Windows Server 2019 for x64-based Systems - https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB5012647
2022-04 Cumulative Update for Windows 10 Version 1909 for x64-based Systems - https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB5012591
2022-04 Dynamic Cumulative Update for Windows 10 Version 21H2 for x86-based Systems - https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB5012599
...

Теперь ответим на вопрос, где всё это хранить

Тут решений конечно же очень много от банальных csv файлов для каждой уязвимости, до высокоуровневых СУБД.

Мы же решили заводить каждую уязвимость в трекер задач YouTrack отдельной задачей (за тавтологию извиняюсь).

Пример:

Скрин

Ну вот примерно так, мы выстроили процесс vulnerability managment`a управления уязвимостями.

P.S. Автор не претендует на звание мастера программиста, а просто делится полезной информацией, которая в RU сегменте интернета слабо описана.

P.S.S. Мой репозиторий со всеми скриптами

Источник: https://habr.com/ru/post/670314/


Интересные статьи

Интересные статьи

В небольших организациях как правило, не достаточно уделяют внимания к построению логически стройной и хранящей в себе дополнительную информацию схемы IP-адресации. Впервые задумался об этом когда по...
В прошлой части мы поговорили о советах директору по разработке бизнес-процесса в Битрикс24, сейчас же я постараюсь дать советы руководителям отделов, поскольку по моему опыту почти всегд...
Кто бы что ни говорил, но я считаю, что изобретение велосипедов — штука полезная. Использование готовых библиотек и фреймворков, конечно, хорошо, но порой стоит их отложить и создать ...
1С Битрикс: Управление сайтом (БУС) - CMS №1 в России по версии портала “Рейтинг Рунета” за 2018 год. На рынке c 2003 года. За это время БУС не стоял на месте, обрастал новой функциональностью...
Если вы последние лет десять следите за обновлениями «коробочной версии» Битрикса (не 24), то давно уже заметили, что обновляется только модуль магазина и его окружение. Все остальные модули как ...