Pythonで始めるテストツール製作(6)NVDからcpeNameで脆弱性一覧を取得する

## 1.はじめに
NVD（National Vulnerability Database＠アメリカ国立標準技術研究所）のWebサイトでCPE Nameで脆弱性を検索[^1]できるものの一画面あたりの表示件数が最大20件だったりソートがPublish DateまたはModified Dateの昇順降順のみと機能的には最低限となっています。そこでREST APIで脆弱性情報を取得してCSVファイルへ保存しExcelで活用できるようにします。

:::note
[Pythonで始めるテストツール製作(5)NVDからCVSSメトリクスを取得しTextileの表形式に整形する](https://qiita.com/pbjpkas/items/5bd6082a0742121d2d4a)もご参考ください。
:::

## 2.作成するプログラム
次のようなプログラムを作成します。

- 検索の手順
  - cpeNameを入力して検索する
  - NVDの[Vulnerability APIs](https://nvd.nist.gov/developers/vulnerabilities)より一回で取得できる件数は最大で2,000件のためresultsPerPageとstartIndexを組み合わせて検索する
- 検索結果の保存
  - cpeNameと実行した日時がわかるファイル名を自動で付ける
    - cpeNameのうち、":"、"."、"*"はそれぞれ"-"、"R"、"X"に文字を置換する
    - 日時はYYYYMMDD-HHMMSS形式とする
  - BOM付きUTF-8にする（生成されたCSVファイルをExcelで利用するため）
- CSVファイルに保存する情報
  - cpeName
  - resultPerPage、startIndex、totalResults、format、version、timestampの戻り値
  - 各CVEの次のデータ
    - id
    - published
    - lastModified
    - vulnStatus
    - description
    - weaknesses
    - CVE情報へのリンク（URL）
  - 各CVEのCVSS Ver 2、3.0、3.1、4.0のcvssMetricsの次のデータ
    - type
    - vectorString
    - baseScore
    - baseSeverity
    - exploitabilityScore（CVSS Ver 2、3.0、3.1のみ）
    - impactScore（CVSS Ver 2、3.0、3.1のみ）
    - データが存在しない場合はN/Aを記述する
    - cvssMetricsのリストの要素数が二つの場合はPrimaryの方を保存する
  - 各CVEのcvssMetricsの代表値
    - 取得したcvssMetricsのなかで一番新しいCVSSバージョンのものの値

## 3.ソースコード
動作確認：Python 3.13.0 / Microsoft Windows 11 Pro 23H2
実行方法：py mytools_cve_list.py
追加ライブラリ：[requests](https://pypi.org/project/requests/) Ver. 2.32.3

```python:mytools_cve_list.py
# mytools_cve_list.py by ka's@pbjpkas 2025
# MIT License
#
# references
#   National Vulnerability Database
#     https://nvd.nist.gov/developers/vulnerabilities
#   NVD API Key
#     https://nvd.nist.gov/general/news/API-Key-Announcement
#     **** It is also recommended that users "sleep" their scripts for six seconds between requests. ****
#   NVD公開のREST APIを用いて脆弱性情報を取得する
#     https://qiita.com/riikunn_ryo/items/97e385ed0a78dc28534f
#   PythonでWeb APIを叩いてJSONをパースする
#     https://qiita.com/bow_arrow/items/4dcab3389c892baba1a5
#   Pythonで辞書のキー・値の存在を確認、取得（検索）
#     https://note.nkmk.me/python-dict-in-values-items/
#
# 動作確認
#   Python 3.13.0 / Microsoft Windows 11 Pro 23H2
#   requests 2.32.3
#

# py -m pip install requests
import requests
import json
import datetime  # ファイル名に時刻を含めるために使用
import time      # time.sleep(6)で使用
import codecs
import csv

def get_cve_list(cpeName, resultsPerPageNum, startIndexNum):
    url = 'https://services.nvd.nist.gov/rest/json/cves/2.0?cpeName=' + cpeName + '&resultsPerPage=' + str(resultsPerPageNum) + '&startIndex=' + str(startIndexNum)
    print(url)
    response = requests.get(url)
    return response


def get_cve_list_by_cpeName(cpeName):
    print(cpeName)
    
    now = datetime.datetime.now()
    fileName =  cpeName.replace(':','-')
    fileName = fileName.replace('.','R') # JIS C 5201-1:2021 小数点がある場合には，小数点を英大文字Rで表し…
    fileName = fileName.replace('*','X')
    fileName = fileName + '-' + now.strftime('%Y%m%d-%H%M%S')  + '.csv'
    print(fileName)
    
    resultsPerPageNum = 500
    
    # BOM付きUTF-8にする（生成されたCSVファイルをExcelで利用するため）
    with codecs.open(fileName, 'w', 'utf-8-sig') as file:
        cveList = csv.writer(file, delimiter=',', lineterminator='\n', quotechar='"')
        cveList.writerow([cpeName])
        
        response = get_cve_list(cpeName, resultsPerPageNum, 0)
        jsonData = response.json()
        #print(jsonData)
        #print('----------')
        
        resultsPerPage = jsonData['resultsPerPage']
        startIndex     = jsonData['startIndex']
        totalResults   = jsonData['totalResults']
        format         = jsonData['format']
        version        = jsonData['version']
        timestamp      = jsonData['timestamp']
        cveList.writerow(['resultsPerPage', 'startIndex', 'totalResults', 'format', 'version', 'timestamp'])
        cveList.writerow([ resultsPerPage ,  startIndex ,  totalResults ,  format ,  version ,  timestamp ])
        #print           (  resultsPerPage ,  startIndex ,  totalResults ,  format ,  version ,  timestamp  )
        #print('----------')
        
        cveList.writerow(
        ['No','cve_id','cve_published','cve_lastModified','cve_vulnStatus','cve_descriptions','cve_weaknesses','cve_url',
         'vXXtype','vXXvectorString','vXXbaseScore','vXXbaseSeverity','vXXexploitabilityScore','vXXimpactScore',
         'v20type','v20vectorString','v20baseScore','v20baseSeverity','v20exploitabilityScore','v20impactScore',
         'v30type','v30vectorString','v30baseScore','v30baseSeverity','v30exploitabilityScore','v30impactScore',
         'v31type','v31vectorString','v31baseScore','v31baseSeverity','v31exploitabilityScore','v31impactScore',
         'v40type','v40vectorString','v40baseScore','v40baseSeverity'
        ])
        
        #print(int(totalResults/resultsPerPageNum)+1)
        #print('----------')
        for i in range(int(totalResults/resultsPerPageNum)+1):
            # 二回目以降のアクセスは6秒空けてから
            if i:
                time.sleep(6)
                response = get_cve_list(cpeName, resultsPerPageNum, resultsPerPageNum*i)
                jsonData = response.json()
                resultsPerPage = jsonData['resultsPerPage']
            
            for j in range(resultsPerPage):
                No = resultsPerPageNum * i + j
                cve_id           = jsonData['vulnerabilities'][j]['cve']['id']
                cve_published    = jsonData['vulnerabilities'][j]['cve']['published']
                cve_lastModified = jsonData['vulnerabilities'][j]['cve']['lastModified']
                cve_vulnStatus   = jsonData['vulnerabilities'][j]['cve']['vulnStatus']
                cve_descriptions = jsonData['vulnerabilities'][j]['cve']['descriptions'][0]['value']
                
                # weaknessesのリストの要素数が一つの場合はPrimary/Secondary問わずそこにあるものを読む
                #print(len(jsonData['vulnerabilities'][j]['cve']['weaknesses']))
                if len(jsonData['vulnerabilities'][j]['cve']['weaknesses']) == 1:
                    k = 0
                else:
                    # weaknessesのリストの要素数が二つの場合はPrimaryとSecondaryがあるのでPrimaryの方を読む
                    if jsonData['vulnerabilities'][j]['cve']['weaknesses'][0]['type'] == 'Primary':
                        #print('weaknesses 0 Primary')
                        k = 0
                    else:
                        #print('weaknesses 1 Primary')
                        k = 1
                cve_weaknesses   = jsonData['vulnerabilities'][j]['cve']['weaknesses'][k]['description'][0]['value']
                
                cve_url          = 'https://nvd.nist.gov/vuln/detail/' + cve_id
                
                # vXXhogeはもっとも新しいCVSSバージョンのメトリクスを格納するための入れ物
                # ひとまず'N/A'で初期化しより新しいCVSSバージョンのメトリクスで上書きする
                vXXtype                = 'N/A'
                vXXvectorString        = 'N/A'
                vXXbaseScore           = 'N/A'
                vXXbaseSeverity        = 'N/A'
                vXXexploitabilityScore = 'N/A'
                vXXimpactScore         = 'N/A'
                
                if jsonData['vulnerabilities'][j]['cve']['metrics'].get('cvssMetricV2') == None:
                    #print('No cvssMetricV2')
                    v20type                    = 'N/A'
                    v20vectorString            = 'N/A'
                    v20baseScore               = 'N/A'
                    v20baseSeverity            = 'N/A'
                    v20exploitabilityScore     = 'N/A'
                    v20impactScore             = 'N/A'
                    #v20acInsufInfo             = 'N/A'
                    #v20obtainAllPrivilege      = 'N/A'
                    #v20obtainUserPrivilege     = 'N/A'
                    #v20obtainOtherPrivilege    = 'N/A'
                    #v20userInteractionRequired = 'N/A'
                else:
                    # cvssMetricV20のリストの要素数が一つの場合はPrimary/Secondary問わずそこにあるものを読む
                    #print(len(jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2']))
                    if len(jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2']) == 1:
                        k = 0
                    else:
                        # cvssMetricV20のリストの要素数が二つの場合はPrimaryとSecondaryがあるのでPrimaryの方を読む
                        if jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][0]['type'] == 'Primary':
                            #print('V20 0 Primary')
                            k = 0
                        else:
                            #print('V20 1 Primary')
                            k = 1
                    
                    v20type                    = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['type']
                    v20vectorString            = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['cvssData']['vectorString']
                    v20baseScore               = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['cvssData']['baseScore']
                    v20baseSeverity            = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['baseSeverity']
                    v20exploitabilityScore     = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['exploitabilityScore']
                    v20impactScore             = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['impactScore']
                    #v20acInsufInfo             = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['acInsufInfo']
                    #v20obtainAllPrivilege      = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['obtainAllPrivilege']
                    #v20obtainUserPrivilege     = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['obtainUserPrivilege']
                    #v20obtainOtherPrivilege    = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['obtainOtherPrivileg']
                    #v20userInteractionRequired = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['userInteractionRequired']
                    vXXtype                    = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['type']
                    vXXvectorString            = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['cvssData']['vectorString']
                    vXXbaseScore               = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['cvssData']['baseScore']
                    vXXbaseSeverity            = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['baseSeverity']
                    vXXexploitabilityScore     = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['exploitabilityScore']
                    vXXimpactScore             = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV2'][k]['impactScore']
                
                if jsonData['vulnerabilities'][j]['cve']['metrics'].get('cvssMetricV30') == None:
                    #print('No cvssMetricV30')
                    v30type                = 'N/A'
                    v30vectorString        = 'N/A'
                    v30baseScore           = 'N/A'
                    v30baseSeverity        = 'N/A'
                    v30exploitabilityScore = 'N/A'
                    v30impactScore         = 'N/A'
                else:
                    # cvssMetricV30のリストの要素数が一つの場合はPrimary/Secondary問わずそこにあるものを読む
                    #print(len(jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30']))
                    if len(jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30']) == 1:
                        k = 0
                    else:
                        # cvssMetricV30のリストの要素数が二つの場合はPrimaryとSecondaryがあるのでPrimaryの方を読む
                        if jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30'][0]['type'] == 'Primary':
                            #print('V30 0 Primary')
                            k = 0
                        else:
                            #print('V30 1 Primary')
                            k = 1
                    
                    v30type                = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30'][k]['type']
                    v30vectorString        = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30'][k]['cvssData']['vectorString']
                    v30baseScore           = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30'][k]['cvssData']['baseScore']
                    v30baseSeverity        = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30'][k]['cvssData']['baseSeverity']
                    v30exploitabilityScore = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30'][k]['exploitabilityScore']
                    v30impactScore         = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30'][k]['impactScore']
                    vXXtype                = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30'][k]['type']
                    vXXvectorString        = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30'][k]['cvssData']['vectorString']
                    vXXbaseScore           = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30'][k]['cvssData']['baseScore']
                    vXXbaseSeverity        = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30'][k]['cvssData']['baseSeverity']
                    vXXexploitabilityScore = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30'][k]['exploitabilityScore']
                    vXXimpactScore         = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV30'][k]['impactScore']
                
                if jsonData['vulnerabilities'][j]['cve']['metrics'].get('cvssMetricV31') == None:
                    #print('No cvssMetricV31')
                    v31type                = 'N/A'
                    v31vectorString        = 'N/A'
                    v31baseScore           = 'N/A'
                    v31baseSeverity        = 'N/A'
                    v31exploitabilityScore = 'N/A'
                    v31impactScore         = 'N/A'
                else:
                    # cvssMetricV31のリストの要素数が一つの場合はPrimary/Secondary問わずそこにあるものを読む
                    #print(len(jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31']))
                    if len(jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31']) == 1:
                        k = 0
                    else:
                        # cvssMetricV31のリストの要素数が二つの場合はPrimaryとSecondaryがあるのでPrimaryの方を読む
                        if jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31'][0]['type'] == 'Primary':
                            #print('V31 0 Primary')
                            k = 0
                        else:
                            #print('V31 1 Primary')
                            k = 1
                    
                    v31type                = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31'][k]['type']
                    v31vectorString        = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31'][k]['cvssData']['vectorString']
                    v31baseScore           = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31'][k]['cvssData']['baseScore']
                    v31baseSeverity        = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31'][k]['cvssData']['baseSeverity']
                    v31exploitabilityScore = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31'][k]['exploitabilityScore']
                    v31impactScore         = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31'][k]['impactScore']
                    vXXtype                = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31'][k]['type']
                    vXXvectorString        = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31'][k]['cvssData']['vectorString']
                    vXXbaseScore           = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31'][k]['cvssData']['baseScore']
                    vXXbaseSeverity        = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31'][k]['cvssData']['baseSeverity']
                    vXXexploitabilityScore = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31'][k]['exploitabilityScore']
                    vXXimpactScore         = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV31'][k]['impactScore']
                
                if jsonData['vulnerabilities'][j]['cve']['metrics'].get('cvssMetricV40') == None:
                    #print('No cvssMetricV40')
                    v40type                = 'N/A'
                    v40vectorString        = 'N/A'
                    v40baseScore           = 'N/A'
                    v40baseSeverity        = 'N/A'
                else:
                    # cvssMetricV40のリストの要素数が一つの場合はPrimary/Secondary問わずそこにあるものを読む
                    #print(len(jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV40']))
                    if len(jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV40']) == 1:
                        k = 0
                    else:
                        # cvssMetricV40のリストの要素数が二つの場合はPrimaryとSecondaryがあるのでPrimaryの方を読む
                        if jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV40'][0]['type'] == 'Primary':
                            #print('V40 0 Primary')
                            k = 0
                        else:
                            #print('V40 1 Primary')
                            k = 1
                    
                    v40type                = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV40'][k]['type']
                    v40vectorString        = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV40'][k]['cvssData']['vectorString']
                    v40baseScore           = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV40'][k]['cvssData']['baseScore']
                    v40baseSeverity        = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV40'][k]['cvssData']['baseSeverity']
                    vXXtype                = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV40'][k]['type']
                    vXXvectorString        = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV40'][k]['cvssData']['vectorString']
                    vXXbaseScore           = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV40'][k]['cvssData']['baseScore']
                    vXXbaseSeverity        = jsonData['vulnerabilities'][j]['cve']['metrics']['cvssMetricV40'][k]['cvssData']['baseSeverity']
                    vXXexploitabilityScore = 'N/A'
                    vXXimpactScore         = 'N/A'
                
                cveList.writerow(
                [No,cve_id,cve_published,cve_lastModified,cve_vulnStatus,cve_descriptions,cve_weaknesses,cve_url,
                 vXXtype,vXXvectorString,vXXbaseScore,vXXbaseSeverity,vXXexploitabilityScore,vXXimpactScore,
                 v20type,v20vectorString,v20baseScore,v20baseSeverity,v20exploitabilityScore,v20impactScore,
                 v30type,v30vectorString,v30baseScore,v30baseSeverity,v30exploitabilityScore,v30impactScore,
                 v31type,v31vectorString,v31baseScore,v31baseSeverity,v31exploitabilityScore,v31impactScore,
                 v40type,v40vectorString,v40baseScore,v40baseSeverity
                ])
                #print(
                #  No,cve_id,cve_published,cve_lastModified,cve_vulnStatus,cve_descriptions,cve_weaknesses,cve_url,
                #  vXXtype,vXXvectorString,vXXbaseScore,vXXbaseSeverity,vXXexploitabilityScore,vXXimpactScore,
                #  v20type,v20vectorString,v20baseScore,v20baseSeverity,v20exploitabilityScore,v20impactScore,
                #  v30type,v30vectorString,v30baseScore,v30baseSeverity,v30exploitabilityScore,v30impactScore,
                #  v31type,v31vectorString,v31baseScore,v31baseSeverity,v31exploitabilityScore,v31impactScore,
                #  v40type,v40vectorString,v40baseScore,v40baseSeverity
                #)
                #print('----------')


def main():
    while True:
        print('= myTools cve list =')
        print('a: get CVE list by cpeName')
        print('x: exit')
        
        s = input('>')
        
        if s == 'a':
            cpeName = input('Enter cpeName(ex:cpe:2.3:a:apache:http_server:2.3.14:*:*:*:*:*:*:*)>')
            get_cve_list_by_cpeName(cpeName)
            #get_cve_list_by_cpeName('cpe:2.3:a:apache:http_server:2.3.14:*:*:*:*:*:*:*')
            #get_cve_list_by_cpeName('cpe:2.3:o:linux:linux_kernel:-:*:*:*:*:*:*:*')      #2000件を超える
        
        if s == 'x':
            if __name__ == '__main__':
                print('Bye.')
            return


if __name__ == '__main__':
    main()
```

実行例を以下に示します。
```cmd
>py mytools_cve_list.py
= myTools cve list =
a: get CVE list by cpeName
x: exit
>a
Enter cpeName(ex:cpe:2.3:a:apache:http_server:2.3.14:*:*:*:*:*:*:*)>cpe:2.3:a:gnu:bash:4.3:*:*:*:*:*:*:*
cpe:2.3:a:gnu:bash:4.3:*:*:*:*:*:*:*
cpe-2R3-a-gnu-bash-4R3-X-X-X-X-X-X-X-20250202-230048.csv
https://services.nvd.nist.gov/rest/json/cves/2.0?cpeName=cpe:2.3:a:gnu:bash:4.3:*:*:*:*:*:*:*&resultsPerPage=500&startIndex=0
= myTools cve list =
a: get CVE list by cpeName
x: exit
>
```

```sh:cpe-2R3-a-gnu-bash-4R3-X-X-X-X-X-X-X-20250202-230048.csv
cpe:2.3:a:gnu:bash:4.3:*:*:*:*:*:*:*
resultsPerPage,startIndex,totalResults,format,version,timestamp
12,0,12,NVD_CVE,2.0,2025-02-02T14:00:49.173
No,cve_id,cve_published,cve_lastModified,cve_vulnStatus,cve_descriptions,cve_weaknesses,cve_url,vXXtype,vXXvectorString,vXXbaseScore,vXXbaseSeverity,vXXexploitabilityScore,vXXimpactScore,v20type,v20vectorString,v20baseScore,v20baseSeverity,v20exploitabilityScore,v20impactScore,v30type,v30vectorString,v30baseScore,v30baseSeverity,v30exploitabilityScore,v30impactScore,v31type,v31vectorString,v31baseScore,v31baseSeverity,v31exploitabilityScore,v31impactScore,v40type,v40vectorString,v40baseScore,v40baseSeverity
0,CVE-2014-6271,2014-09-24T18:48:04.477,2025-01-06T19:36:17.117,Analyzed,"GNU Bash through 4.3 processes trailing strings after function definitions in the values of environment variables, which allows remote attackers to execute arbitrary code via a crafted environment, as demonstrated by vectors involving the ForceCommand feature in OpenSSH sshd, the mod_cgi and mod_cgid modules in the Apache HTTP Server, scripts executed by unspecified DHCP clients, and other situations in which setting the environment occurs across a privilege boundary from Bash execution, aka ""ShellShock.""  NOTE: the original fix for this issue was incorrect; CVE-2014-7169 has been assigned to cover the vulnerability that is still present after the incorrect fix.",CWE-78,https://nvd.nist.gov/vuln/detail/CVE-2014-6271,Primary,CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H,9.8,CRITICAL,3.9,5.9,Primary,AV:N/AC:L/Au:N/C:C/I:C/A:C,10.0,HIGH,10.0,10.0,N/A,N/A,N/A,N/A,N/A,N/A,Primary,CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H,9.8,CRITICAL,3.9,5.9,N/A,N/A,N/A,N/A
1,CVE-2014-7169,2014-09-25T01:55:04.367,2025-01-06T19:35:05.427,Analyzed,"GNU Bash through 4.3 bash43-025 processes trailing strings after certain malformed function definitions in the values of environment variables, which allows remote attackers to write to files or possibly have unknown other impact via a crafted environment, as demonstrated by vectors involving the ForceCommand feature in OpenSSH sshd, the mod_cgi and mod_cgid modules in the Apache HTTP Server, scripts executed by unspecified DHCP clients, and other situations in which setting the environment occurs across a privilege boundary from Bash execution.  NOTE: this vulnerability exists because of an incomplete fix for CVE-2014-6271.",CWE-78,https://nvd.nist.gov/vuln/detail/CVE-2014-7169,Primary,CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H,9.8,CRITICAL,3.9,5.9,Primary,AV:N/AC:L/Au:N/C:C/I:C/A:C,10.0,HIGH,10.0,10.0,N/A,N/A,N/A,N/A,N/A,N/A,Primary,CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H,9.8,CRITICAL,3.9,5.9,N/A,N/A,N/A,N/A
2,CVE-2014-6277,2014-09-27T22:55:02.660,2024-11-21T02:14:04.890,Modified,"GNU Bash through 4.3 bash43-026 does not properly parse function definitions in the values of environment variables, which allows remote attackers to execute arbitrary code or cause a denial of service (uninitialized memory access, and untrusted-pointer read and write operations) via a crafted environment, as demonstrated by vectors involving the ForceCommand feature in OpenSSH sshd, the mod_cgi and mod_cgid modules in the Apache HTTP Server, scripts executed by unspecified DHCP clients, and other situations in which setting the environment occurs across a privilege boundary from Bash execution.  NOTE: this vulnerability exists because of an incomplete fix for CVE-2014-6271 and CVE-2014-7169.",CWE-78,https://nvd.nist.gov/vuln/detail/CVE-2014-6277,Primary,AV:N/AC:L/Au:N/C:C/I:C/A:C,10.0,HIGH,10.0,10.0,Primary,AV:N/AC:L/Au:N/C:C/I:C/A:C,10.0,HIGH,10.0,10.0,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A
3,CVE-2014-6278,2014-09-30T10:55:04.723,2024-11-21T02:14:05.110,Modified,"GNU Bash through 4.3 bash43-026 does not properly parse function definitions in the values of environment variables, which allows remote attackers to execute arbitrary commands via a crafted environment, as demonstrated by vectors involving the ForceCommand feature in OpenSSH sshd, the mod_cgi and mod_cgid modules in the Apache HTTP Server, scripts executed by unspecified DHCP clients, and other situations in which setting the environment occurs across a privilege boundary from Bash execution.  NOTE: this vulnerability exists because of an incomplete fix for CVE-2014-6271, CVE-2014-7169, and CVE-2014-6277.",CWE-78,https://nvd.nist.gov/vuln/detail/CVE-2014-6278,Primary,AV:N/AC:L/Au:N/C:C/I:C/A:C,10.0,HIGH,10.0,10.0,Primary,AV:N/AC:L/Au:N/C:C/I:C/A:C,10.0,HIGH,10.0,10.0,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A
4,CVE-2014-7187,2014-09-28T19:55:06.270,2024-11-21T02:16:29.490,Modified,"Off-by-one error in the read_token_word function in parse.y in GNU Bash through 4.3 bash43-026 allows remote attackers to cause a denial of service (out-of-bounds array access and application crash) or possibly have unspecified other impact via deeply nested for loops, aka the ""word_lineno"" issue.",CWE-119,https://nvd.nist.gov/vuln/detail/CVE-2014-7187,Primary,AV:N/AC:L/Au:N/C:C/I:C/A:C,10.0,HIGH,10.0,10.0,Primary,AV:N/AC:L/Au:N/C:C/I:C/A:C,10.0,HIGH,10.0,10.0,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A
5,CVE-2014-7186,2014-09-28T19:55:06.223,2024-11-21T02:16:29.170,Modified,"The redirection implementation in parse.y in GNU Bash through 4.3 bash43-026 allows remote attackers to cause a denial of service (out-of-bounds array access and application crash) or possibly have unspecified other impact via crafted use of here documents, aka the ""redir_stack"" issue.",CWE-119,https://nvd.nist.gov/vuln/detail/CVE-2014-7186,Primary,AV:N/AC:L/Au:N/C:C/I:C/A:C,10.0,HIGH,10.0,10.0,Primary,AV:N/AC:L/Au:N/C:C/I:C/A:C,10.0,HIGH,10.0,10.0,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A
6,CVE-2016-7543,2017-01-19T20:59:00.470,2024-11-21T02:58:11.050,Modified,Bash before 4.4 allows local users to execute arbitrary commands with root privileges via crafted SHELLOPTS and PS4 environment variables.,CWE-20,https://nvd.nist.gov/vuln/detail/CVE-2016-7543,Primary,CVSS:3.0/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H,8.4,HIGH,2.5,5.9,Primary,AV:L/AC:L/Au:N/C:C/I:C/A:C,7.2,HIGH,3.9,10.0,Primary,CVSS:3.0/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H,8.4,HIGH,2.5,5.9,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A
7,CVE-2016-9401,2017-01-23T21:59:02.987,2024-11-21T03:01:07.123,Modified,popd in bash might allow local users to bypass the restricted shell and cause a use-after-free via a crafted address.,CWE-416,https://nvd.nist.gov/vuln/detail/CVE-2016-9401,Primary,CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H,5.5,MEDIUM,1.8,3.6,Primary,AV:L/AC:L/Au:N/C:N/I:N/A:P,2.1,LOW,3.9,2.9,N/A,N/A,N/A,N/A,N/A,N/A,Primary,CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H,5.5,MEDIUM,1.8,3.6,N/A,N/A,N/A,N/A
8,CVE-2016-0634,2017-08-28T15:29:01.487,2024-11-21T02:42:03.420,Modified,The expansion of '\h' in the prompt string in bash 4.3 allows remote authenticated users to execute arbitrary code via shell metacharacters placed in 'hostname' of a machine.,CWE-78,https://nvd.nist.gov/vuln/detail/CVE-2016-0634,Primary,CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H,7.5,HIGH,1.6,5.9,Primary,AV:N/AC:M/Au:S/C:P/I:P/A:P,6.0,MEDIUM,6.8,6.4,Primary,CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H,7.5,HIGH,1.6,5.9,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A
9,CVE-2019-9924,2019-03-22T08:29:00.467,2024-11-21T04:52:35.227,Modified,"rbash in Bash before 4.4-beta2 did not prevent the shell user from modifying BASH_CMDS, thus allowing the user to execute any command with the permissions of the shell.",CWE-862,https://nvd.nist.gov/vuln/detail/CVE-2019-9924,Primary,CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H,7.8,HIGH,1.8,5.9,Primary,AV:L/AC:L/Au:N/C:C/I:C/A:C,7.2,HIGH,3.9,10.0,N/A,N/A,N/A,N/A,N/A,N/A,Primary,CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H,7.8,HIGH,1.8,5.9,N/A,N/A,N/A,N/A
10,CVE-2012-6711,2019-06-18T18:15:09.247,2024-11-21T01:46:43.983,Modified,"A heap-based buffer overflow exists in GNU Bash before 4.3 when wide characters, not supported by the current locale set in the LC_CTYPE environment variable, are printed through the echo built-in function. A local attacker, who can provide data to print through the ""echo -e"" built-in function, may use this flaw to crash a script or execute code with the privileges of the bash process. This occurs because ansicstr() in lib/sh/strtrans.c mishandles u32cconv().",CWE-119,https://nvd.nist.gov/vuln/detail/CVE-2012-6711,Primary,CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H,7.8,HIGH,1.8,5.9,Primary,AV:L/AC:L/Au:N/C:P/I:P/A:P,4.6,MEDIUM,3.9,6.4,Primary,CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H,7.8,HIGH,1.8,5.9,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A
11,CVE-2019-18276,2019-11-28T01:15:10.603,2024-11-21T04:32:57.217,Modified,"An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support ""saved UID"" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use ""enable -f"" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.",CWE-273,https://nvd.nist.gov/vuln/detail/CVE-2019-18276,Primary,CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H,7.8,HIGH,1.8,5.9,Primary,AV:L/AC:L/Au:N/C:C/I:C/A:C,7.2,HIGH,3.9,10.0,N/A,N/A,N/A,N/A,N/A,N/A,Primary,CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H,7.8,HIGH,1.8,5.9,N/A,N/A,N/A,N/A
```

## 4.おわりに
- ひとまずNVDからREST APIで脆弱性一覧を取得できました
- [Pythonで始めるテストツール製作(5)NVDからCVSSメトリクスを取得しTextileの表形式に整形する](https://qiita.com/pbjpkas/items/5bd6082a0742121d2d4a)からの改善点として、cvssMetricsにPrimaryとSecondaryの両方のリストが含まれていた場合にPrimaryを保存するようにしました
- 次のような機能追加は追々試してみたいです
  - weaknessesが二つ以上ある場合にそれらをCSVファイルへ保存する
    - 今は保存しているのは一つだけ

## A. 参考資料
- [Vulnerability APIs](https://nvd.nist.gov/developers/vulnerabilities)
- [NVD API Key](https://nvd.nist.gov/general/news/API-Key-Announcement)
- [NVD公開のREST APIを用いて脆弱性情報を取得する](https://qiita.com/riikunn_ryo/items/97e385ed0a78dc28534f)
- [PythonでWeb APIを叩いてJSONをパースする](https://qiita.com/bow_arrow/items/4dcab3389c892baba1a5)
- [Pythonで辞書のキー・値の存在を確認、取得（検索）](https://note.nkmk.me/python-dict-in-values-items/)

## B. Pythonで始めるテストツール製作
- [Pythonで始めるテストツール製作(1)](https://qiita.com/pbjpkas/items/0b8a32e2b0e126fab8bc)
- [Pythonで始めるテストツール製作(2)オシロスコープの操作](https://qiita.com/pbjpkas/items/be69d2cb34eb7715170a)
- [Pythonで始めるテストツール製作(3)シリアルポートのデータ送受信](https://qiita.com/pbjpkas/items/6c64c199ee3fd2584ae8)
- [Pythonで始めるテストツール製作(4)電源（一次側）のオンオフ（Windows編）](https://qiita.com/pbjpkas/items/8811e64301b057434b28)
- [Pythonで始めるテストツール製作(5)NVDからCVSSメトリクスを取得しTextileの表形式に整形する](https://qiita.com/pbjpkas/items/5bd6082a0742121d2d4a)
- Pythonで始めるテストツール製作(6)NVDからcpeNameで脆弱性一覧を取得する

(1)～(3)をPDF化した「[Pythonで始めるテストツール製作 Menu Based CLI編](https://techbookfest.org/product/5348598382329856)」を技術書典で頒布しています。

[^1]: 例：https://nvd.nist.gov/vuln/search/results?form_type=Advanced&results_type=overview&query=cpe%3A2.3%3Aa%3Aapache%3Ahttp_server%3A2.3.14%3A*%3A*%3A*%3A*%3A*%3A*%3A*&search_type=all&isCpeNameSearch=true