【Python】CVSS v3 Base Score 取得

(ネタをパクりました。大変申し訳ございません。)

CVEの番号から適当にCVSS v3のスコアを取ってくる | hacknote のPython版です。)

(CVEの識別子を与えると、NVDのページからCVSS V3 Base Score を取得してくるやつです。)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from StringIO import StringIO
from lxml import etree
import urllib3
import sys
import certifi
import re


class Foo:

    __NVD_DETAIL_URL = "https://web.nvd.nist.gov/view/vuln/detail?vulnId=%s"
    __CVSS_V3_SCORE_XPATH = '//div[@class="row" and contains(./span[@class="label"]/text(), "CVSS v3 Base Score")]/a/text()'
    __CVSS_V3_RATING_XPATH = '//div[@class="row" and contains(./span[@class="label"]/text(), "CVSS v3 Base Score")]/text()'

    u"""
        CVSS v3 Base Score 取得
    """
    @classmethod
    def extractCvssV3Score(cls, cve_id):

        # NVDの脆弱性詳細ページ取得
        url = cls.__NVD_DETAIL_URL % cve_id
        response = urllib3.PoolManager(
                cert_reqs = 'CERT_REQUIRED',
                ca_certs = certifi.where()
            ).request('GET', url).data
        f = StringIO(response)
        parser = etree.HTMLParser()
        doc = etree.parse(f, parser)

        # score (x.x)
        scores = doc.xpath(cls.__CVSS_V3_SCORE_XPATH)
        if len(scores) != 1:
            raise Exception("failed to extract cvss v3 score")
        score = scores[0]

        # rating (Critical High...)
        rating = None
        ratings = doc.xpath(cls.__CVSS_V3_RATING_XPATH)

        # 空白文字だけのゴミが含まれるため
        rating_pattern = r"^\s*(\S+)\s*$"
        for e in ratings:
            m = re.match(rating_pattern, e)
            if m:
                rating = m.group(1)
                break

        if not rating:
            raise Exception("failed to extract cvss v3 rating")

        return (score.strip(), rating.strip())
$ python
python> from xxx import Foo
python> Foo.extractCvssV3Score("CVE-2016-3074")
=> returns ('9.8', 'Critical')