NVD公開のREST APIを用いて脆弱性情報を取得する

# 背景
NVD (National Vulnerability Database) は、NISTが管理している脆弱性情報のデータベースであり、ソフトウェアやハードウェアの脆弱性情報を確認する際、NVDにお世話になることは非常に多いです。

そんなNVDですが、各CVEの脆弱性の情報が取得できればいいなあと思っていたところ、
NVDがREST APIを2019年9月に公開しているのを見つけました。
https://nvd.nist.gov/General/News/New-NVD-CVE-CPE-API-and-SOAP-Retirement

結構簡単に情報を取得できたので、情報共有も兼ねてプログラムを公開します。

# 実行環境
* Ubuntu 18.04 LTS
* Python 3.9.1

# ざっくりセキュリティ用語
厳密な意味はググれば出てくると思うので、ここではざっくりレベルで。

* CVE (Common Vulnerabilities and Exposures) : 特定の脆弱性に割り当てられる識別子
* CWE (Common Weakness Enumeration) : 脆弱性のカテゴリ。例えばCWE-89はSQLインジェクションを指す。
* CPE (Common Platform Enumeration) : 
     ハードウェア、ソフトウェアを識別するための仕様。CPEを使うことで、例えばどのOSSのどのバージョンで脆弱性の影響が出るか分かる。
* CVSS (Common Vulnerability Scoring System) : 脆弱性の深刻度を表す指標。深刻度のスコアや攻撃難度とか。NVDではCVSS v3とCVSS v2が公開されている

# REST API

あるCVEの情報を取得したい場合は下記のリクエストを実行します。

```
https://services.nvd.nist.gov/rest/json/cve/1.0/<cveId>
```

条件に合致するCVEの情報を取得したい場合は、下記のリクエストを実行します。
先程のリクエストは"cve"であることに対し、こちらは"cves"であることに注意。

```
https://services.nvd.nist.gov/rest/json/cves/1.0
```

例えば、CPEに合致するCVE情報を取得したい場合は下記のようなリクエストを送信すれば良いです。
例:OpenSSL1.1.1cのCVE情報を取得したい場合

```
https://services.nvd.nist.gov/rest/json/cves/1.0?cpeMatchString=cpe:2.3:a:openssl:openssl:1.1.1c:*:*:*:*:*:*:*
```


レスポンスはJSON形式で返ってきます。

詳しくは下記。
https://csrc.nist.gov/CSRC/media/Projects/National-Vulnerability-Database/documents/web%20service%20documentation/Automation%20Support%20for%20CVE%20Retrieval.pdf

## Current Descriptionを取得する
NVDに記載されている脆弱性の説明(Current Description)を取得したい場合は、レスポンスのJSONデータに下記のようにアクセスします。

```python
json_data['result']['CVE_Items'][x]['cve']['description']['description_data'][0]['value'] 
```
※ x はCVEリストのインデックス

## CWEを取得する
CWEを取得したい場合は、レスポンスのJSONデータに下記のようにアクセスします。

```python
json_data['result']['CVE_Items'][x]['cve']['problemtype']['problemtype_data'][0]['description'][0]['value']
```
※ x はCVEリストのインデックス

## CVSSv3 情報を取得する
BaseScoreを取得したい場合は、レスポンスのJSONデータに下記のようにアクセスします。

```python
json_data['result']['CVE_Items'][x]['impact']['baseMetricV3']['cvssV3']['baseScore']
```
※ x はCVEリストのインデックス

VectorString(例：AV:N/AC:M/Au:N/C:P/I:P/A:P)を取得したい場合は、レスポンスのJSONデータに下記のようにアクセスします。

```python
json_data['result']['CVE_Items'][x]['impact']['baseMetricV3']['cvssV3']['vectorString']
```
※ x はCVEリストのインデックス

## CVSSv2 情報を取得する
BaseScoreを取得したい場合は、レスポンスのJSONデータに下記のようにアクセスします。

```python
json_data['result']['CVE_Items'][x]['impact']['baseMetricV2']['cvssV2']['baseScore']
```
※ x はCVEリストのインデックス

VectorStringを取得したい場合は、レスポンスのJSONデータに下記のようにアクセスします。

```python
json_data['result']['CVE_Items'][x]['impact']['baseMetricV2']['cvssV2']['vectorString']
```
※ x はCVEリストのインデックス


# CPEの条件を満たすCVEの情報を取得
上記を踏まえて、CPEに合致するCVEの情報を取得し、出力するプログラムを実装しました。

```python:get_software_vuln.py
#!/usr/bin/env python
import requests
import json
import argparse
import textwrap


def main():
    #  コマンドライン引数Parse
    parser = argparse.ArgumentParser()
    parser.add_argument('cpe_name', help='CPE Name')
    args = parser.parse_args()

    #  CPEに対応した脆弱性情報をNVDからJSON形式で取得
    cpe_name = args.cpe_name
    api = 'https://services.nvd.nist.gov/rest/json/cves/1.0?cpeMatchString={cpe_name}'
    uri = api.format(cpe_name=cpe_name)
    response = requests.get(uri)
    json_data = json.loads(response.text)

    vulnerabilities = json_data['result']['CVE_Items']
    for vuln in vulnerabilities:
        cve_id = vuln['cve']['CVE_data_meta']['ID']  # CVE-IDを取得
        current_description = vuln['cve']['description']['description_data'][0]['value']  # Current Descriptionを取得
        cwe_id = vuln['cve']['problemtype']['problemtype_data'][0]['description'][0]['value']  # CWE-IDを取得

        #  CVSS v3の情報があればBaseScoreとVectorStringを取得
        if 'baseMetricV3' in vuln['impact']:
            cvssv3_base_score = vuln['impact']['baseMetricV3']['cvssV3']['baseScore']
            cvssv3_vector_string = vuln['impact']['baseMetricV3']['cvssV3']['vectorString']
            
        else:
            cvssv3_base_score = None
            cvssv3_vector_string = None

        #  CVSS v2のBaseScoreとVectorStringを取得
        cvssv2_base_score = vuln['impact']['baseMetricV2']['cvssV2']['baseScore']
        cvssv2_vector_string = vuln['impact']['baseMetricV2']['cvssV2']['vectorString']

        #  出力
        print('---------')
        text = textwrap.dedent('''
        CVE-ID:{cve_id}
        CWE-ID:{cwe_id}
        CVSSv3 BaseScore:{cvssv3_base_score} CVSSv3 VectorString:{cvssv3_vector_string}
        CVSSv2 BaseScore:{cvssv2_base_score} CVSSv2 VectorString: {cvssv2_vector_string}
        Current Description:
        {current_description}
        ''')
        print(text.format(cve_id=cve_id, cwe_id=cwe_id, cvssv3_base_score=cvssv3_base_score, cvssv3_vector_string=cvssv3_vector_string,
                          cvssv2_base_score=cvssv2_base_score, cvssv2_vector_string=cvssv2_vector_string, current_description=current_description))
        print('---------')


main()

```

下記のように実行できます。

```terminal
python get_software_vulns.py <CPE>
```

例：openssl 1.1.1cについて実行する場合

```
python get_software_vulns.py cpe:2.3:a:openssl:openssl:1.1.1c:*:*:*:*:*:*:*
```

# GitHub
https://github.com/riikunn1004/NVDAPI
にてコードを公開しています。参考まで。

下記のPythonプログラムを公開しています。
get_software_vuln.py：上記のプログラム
get_cve_info.py : 指定したCVEの情報を取得するプログラム


# まとめ
REST APIを用いて脆弱性情報を取得する方法について述べました。
今回記載した内容は一部なので、詳しくはNVDが公開しているドキュメントをご覧ください。
脆弱性情報を管理するためのツールはいろいろとありますが、
このREST APIを活用して自身のニーズにあったツールを自分で開発するのも有りですね。