<reponame>clearbluejar/cve-markdown-charts
from urllib import response
import requests
import datetime
import json
from bs4 import BeautifulSoup
import os
from mdutils.tools.Table import Table
from html import escape
import argparse


msrc_api_url = "https://api.msrc.microsoft.com/"
cve_url = "https://cve.mitre.org/cgi-bin/"

def get_json_cve_list_from_keyword(keyword):
    cve_list = []
    url = "{}cvekey.cgi?keyword={}".format(cve_url, str(keyword))
    response = requests.get(url)
    if response.status_code == 200:
        soup = BeautifulSoup(response.content, "html.parser")
        results = soup.find(id="TableWithRules")
        #print(results.prettify())
        tds = results.find_all("td")
        tds = iter(tds)
        for td in tds:
            cve_list.append({'id': td.get_text(),'description' : escape(next(tds).get_text().replace('\n',' '))})

    return json.dumps(cve_list)

#Find the cvrf_id (in the form YYYY-Month) given the CVE of interest
def get_cvrf_id_and_date_for_cve(cve):
    # derived from https://github.com/NopSec/ThreatIntelScripts/blob/master/find_microsoft_kb_by_cve.py#L9

    cvrf_id = None
    releaseDate = None

    year = int(cve.split('-')[1])
    # MSRC CVRF is not available before 2016
    if year > 2015:
        url = "{}Updates('{}')?api-version={}".format(msrc_api_url, str(cve),   str(datetime.datetime.now().year))
        headers = {}
        response = requests.get(url, headers=headers)

        if response.status_code == 200:
            data = json.loads(response.content)
            cvrf_id = data["value"][0]["ID"]
            releaseDate = data["value"][0]["InitialReleaseDate"]
            cvrf_url = data["value"][0]["CvrfUrl"]
    else:
        pass

    return cvrf_id,releaseDate

def get_knowledge_base_cvrf_json(cvrf_id):
    if cvrf_id == None:
        return None
    cvrf_json = None

    cache_path = '.cache'
    if not os.path.exists(cache_path):
        os.mkdir(cache_path)

    # if file exists locally, load it
    cvrf_file = os.path.join(cache_path,cvrf_id+".json")
    if not os.path.exists(cvrf_file):
        url = "{}cvrf/{}?api-Version={}".format(msrc_api_url, cvrf_id, str(datetime.datetime.now().year))
        headers = {'Accept': 'application/json'}
        response = requests.get(url, headers = headers)
        with open(cvrf_file, 'w') as f:
            json.dump(json.loads(response.content), f)

    with open(cvrf_file) as f:
        cvrf_json = json.load(f)

    return cvrf_json

def build_markdown_table_from_cves(cves,keyword):
    print("Building table...")

    table_list = []
    table_list.extend(['CVE','Description', 'Release Date', 'KBs', 'Acknowledgments'])
    column_len = len(table_list)

    cves = json.loads(cves)
    for cve in cves:
        print(cve['id'])
        cvrf_id,release_date = get_cvrf_id_and_date_for_cve(cve['id'])
        cvrf_json = get_knowledge_base_cvrf_json(cvrf_id)
        
        if cvrf_json:
            release_date = '[{}](https://msrc.microsoft.com/update-guide/en-US/vulnerability/{})'.format(release_date,cve['id'])
            #description = [vuln['Title']['Value'] for vuln in cvrf_json["Vulnerability"] if vuln["CVE"] == cve['id']]
            kbs = {'[KB{}]({})-{}'.format(kb['Description']['Value'],kb['URL'],kb.get('FixedBuild')) for vuln in cvrf_json["Vulnerability"] if vuln["CVE"] == cve['id'] for kb in vuln["Remediations"] if (str(kb['Description']['Value']).isnumeric() and 'FixedBuild' in kb)  }
            kbs = sorted(['[{}]({}) - [KB{}]({})'.format(kb.get('FixedBuild'),'https://support.microsoft.com/help/{}'.format(kb['Description']['Value']),kb['Description']['Value'],kb['URL']) for vuln in cvrf_json["Vulnerability"] if vuln["CVE"] == cve['id'] for kb in vuln["Remediations"] if (str(kb['Description']['Value']).isnumeric() and 'catalog' in kb['URL'] )])
            acks = {'{}'.format(ack['Name'][0].get('Value')) for vuln in cvrf_json["Vulnerability"] if vuln["CVE"] == cve['id'] for ack in vuln["Acknowledgments"] }
        else:
            kbs = ''
            builds = ''
            acks = ''
                
        cve_id = '[{}](https://cve.mitre.org/cgi-bin/cvename.cgi?name={})'.format(cve['id'],cve['id'])

        table_list.extend([cve_id,cve['description'],release_date,'<details>'+'<br>'.join(kbs)+'</details>', '<br>'.join(acks).replace('\n',' ')])
        
    cve_table = Table().create_table(columns=column_len, rows=len(cves)+1, text=table_list, text_align='center')

    #write results to disk
    table_path = '-'.join(keyword) + '-table.md'
    with open(table_path, 'w') as f:
        f.write(cve_table)

    print(cve_table)
    print('\nCVE table available: ' + table_path + '\n')

def build_markdown_gantt_from_cves(cves,keyword='CVE Markdown Gantt'):
    gantt_template = '''
```mermaid
gantt

title {keyword}
dateFormat YYYY-MM-DD
axisFormat %Y-%m

section CVE Release Dates
{rows}

```
'''
    
    print("Building gantt chart...")

    cves = json.loads(cves)
    rows = []

    sections = {}
    for num,cve in enumerate(cves):
        cvrf_id,release_date = get_cvrf_id_and_date_for_cve(cve['id'])
        
        #handle release dates
        if release_date is None:
            year = cve['id'].split('-')[1]
            release_date = '{}-01-01'.format(cve['id'].split('-')[1])
            cvrf_id = year+'-Jan'
        else:
            from datetime import datetime
            release_date = datetime.strptime(release_date,'%Y-%m-%dT%H:%M:%SZ').strftime("%Y-%m-%d")

        row = '{} :cve{}, {}, 30d'.format(cve['id'],num,release_date)
        sections.setdefault(cvrf_id,[]).append(row)

    sorted_sections = sorted(sections.items(), key = lambda x:datetime.strptime(x[0], '%Y-%b'), reverse=True)    

    for section in sorted_sections:
        rows.append('section {}'.format(section[0]))
        rows.append('\n'.join(section[1]))

    gantt_path = '-'.join(keyword) + '-gantt.md'
    gantt = gantt_template.format(keyword=' '.join(keyword),rows='\n'.join(rows))
    with open(gantt_path, 'w') as f:
        f.write(gantt)
    
    print(gantt)
    print('Gantt chart available: ' + gantt_path)
    



parser = argparse.ArgumentParser(description='Generate CVE Markdown Charts')
parser.add_argument('keyword',  nargs='+', help='The CVE keyword to chart')                 
args = parser.parse_args()

# Get List of CVEs
cve_json = get_json_cve_list_from_keyword(args.keyword)

#print(json.dumps(cve_json, indent=4, sort_keys=True))

build_markdown_table_from_cves(cve_json,args.keyword)
build_markdown_gantt_from_cves(cve_json,args.keyword)

from urllib import response
import requests
import datetime
import json
from bs4 import BeautifulSoup
import os
from mdutils.tools.Table import Table
from html import escape
import argparse


msrc_api_url = "https://api.msrc.microsoft.com/"
cve_url = "https://cve.mitre.org/cgi-bin/"

def get_json_cve_list_from_keyword(keyword):
    cve_list = []
    url = "{}cvekey.cgi?keyword={}".format(cve_url, str(keyword))
    response = requests.get(url)
    if response.status_code == 200:
        soup = BeautifulSoup(response.content, "html.parser")
        results = soup.find(id="TableWithRules")
        #print(results.prettify())
        tds = results.find_all("td")
        tds = iter(tds)
        for td in tds:
            cve_list.append({'id': td.get_text(),'description' : escape(next(tds).get_text().replace('\n',' '))})

    return json.dumps(cve_list)

#Find the cvrf_id (in the form YYYY-Month) given the CVE of interest
def get_cvrf_id_and_date_for_cve(cve):
    # derived from https://github.com/NopSec/ThreatIntelScripts/blob/master/find_microsoft_kb_by_cve.py#L9

    cvrf_id = None
    releaseDate = None

    year = int(cve.split('-')[1])
    # MSRC CVRF is not available before 2016
    if year > 2015:
        url = "{}Updates('{}')?api-version={}".format(msrc_api_url, str(cve),   str(datetime.datetime.now().year))
        headers = {}
        response = requests.get(url, headers=headers)

        if response.status_code == 200:
            data = json.loads(response.content)
            cvrf_id = data["value"][0]["ID"]
            releaseDate = data["value"][0]["InitialReleaseDate"]
            cvrf_url = data["value"][0]["CvrfUrl"]
    else:
        pass

    return cvrf_id,releaseDate

def get_knowledge_base_cvrf_json(cvrf_id):
    if cvrf_id == None:
        return None
    cvrf_json = None

    cache_path = '.cache'
    if not os.path.exists(cache_path):
        os.mkdir(cache_path)

    # if file exists locally, load it
    cvrf_file = os.path.join(cache_path,cvrf_id+".json")
    if not os.path.exists(cvrf_file):
        url = "{}cvrf/{}?api-Version={}".format(msrc_api_url, cvrf_id, str(datetime.datetime.now().year))
        headers = {'Accept': 'application/json'}
        response = requests.get(url, headers = headers)
        with open(cvrf_file, 'w') as f:
            json.dump(json.loads(response.content), f)

    with open(cvrf_file) as f:
        cvrf_json = json.load(f)

    return cvrf_json

def build_markdown_table_from_cves(cves,keyword):
    print("Building table...")

    table_list = []
    table_list.extend(['CVE','Description', 'Release Date', 'KBs', 'Acknowledgments'])
    column_len = len(table_list)

    cves = json.loads(cves)
    for cve in cves:
        print(cve['id'])
        cvrf_id,release_date = get_cvrf_id_and_date_for_cve(cve['id'])
        cvrf_json = get_knowledge_base_cvrf_json(cvrf_id)
        
        if cvrf_json:
            release_date = '[{}](https://msrc.microsoft.com/update-guide/en-US/vulnerability/{})'.format(release_date,cve['id'])
            #description = [vuln['Title']['Value'] for vuln in cvrf_json["Vulnerability"] if vuln["CVE"] == cve['id']]
            kbs = {'[KB{}]({})-{}'.format(kb['Description']['Value'],kb['URL'],kb.get('FixedBuild')) for vuln in cvrf_json["Vulnerability"] if vuln["CVE"] == cve['id'] for kb in vuln["Remediations"] if (str(kb['Description']['Value']).isnumeric() and 'FixedBuild' in kb)  }
            kbs = sorted(['[{}]({}) - [KB{}]({})'.format(kb.get('FixedBuild'),'https://support.microsoft.com/help/{}'.format(kb['Description']['Value']),kb['Description']['Value'],kb['URL']) for vuln in cvrf_json["Vulnerability"] if vuln["CVE"] == cve['id'] for kb in vuln["Remediations"] if (str(kb['Description']['Value']).isnumeric() and 'catalog' in kb['URL'] )])
            acks = {'{}'.format(ack['Name'][0].get('Value')) for vuln in cvrf_json["Vulnerability"] if vuln["CVE"] == cve['id'] for ack in vuln["Acknowledgments"] }
        else:
            kbs = ''
            builds = ''
            acks = ''
                
        cve_id = '[{}](https://cve.mitre.org/cgi-bin/cvename.cgi?name={})'.format(cve['id'],cve['id'])

        table_list.extend([cve_id,cve['description'],release_date,'<details>'+'<br>'.join(kbs)+'</details>', '<br>'.join(acks).replace('\n',' ')])
        
    cve_table = Table().create_table(columns=column_len, rows=len(cves)+1, text=table_list, text_align='center')

    #write results to disk
    table_path = '-'.join(keyword) + '-table.md'
    with open(table_path, 'w') as f:
        f.write(cve_table)

    print(cve_table)
    print('\nCVE table available: ' + table_path + '\n')

def build_markdown_gantt_from_cves(cves,keyword='CVE Markdown Gantt'):
    gantt_template = '''
```mermaid
gantt

title {keyword}
dateFormat YYYY-MM-DD
axisFormat %Y-%m

section CVE Release Dates
{rows}

```
'''
    
    print("Building gantt chart...")

    cves = json.loads(cves)
    rows = []

    sections = {}
    for num,cve in enumerate(cves):
        cvrf_id,release_date = get_cvrf_id_and_date_for_cve(cve['id'])
        
        #handle release dates
        if release_date is None:
            year = cve['id'].split('-')[1]
            release_date = '{}-01-01'.format(cve['id'].split('-')[1])
            cvrf_id = year+'-Jan'
        else:
            from datetime import datetime
            release_date = datetime.strptime(release_date,'%Y-%m-%dT%H:%M:%SZ').strftime("%Y-%m-%d")

        row = '{} :cve{}, {}, 30d'.format(cve['id'],num,release_date)
        sections.setdefault(cvrf_id,[]).append(row)

    sorted_sections = sorted(sections.items(), key = lambda x:datetime.strptime(x[0], '%Y-%b'), reverse=True)    

    for section in sorted_sections:
        rows.append('section {}'.format(section[0]))
        rows.append('\n'.join(section[1]))

    gantt_path = '-'.join(keyword) + '-gantt.md'
    gantt = gantt_template.format(keyword=' '.join(keyword),rows='\n'.join(rows))
    with open(gantt_path, 'w') as f:
        f.write(gantt)
    
    print(gantt)
    print('Gantt chart available: ' + gantt_path)
    



parser = argparse.ArgumentParser(description='Generate CVE Markdown Charts')
parser.add_argument('keyword',  nargs='+', help='The CVE keyword to chart')                 
args = parser.parse_args()

# Get List of CVEs
cve_json = get_json_cve_list_from_keyword(args.keyword)

#print(json.dumps(cve_json, indent=4, sort_keys=True))

build_markdown_table_from_cves(cve_json,args.keyword)
build_markdown_gantt_from_cves(cve_json,args.keyword)
#print(results.prettify()) #Find the cvrf_id (in the form YYYY-Month) given the CVE of interest # derived from https://github.com/NopSec/ThreatIntelScripts/blob/master/find_microsoft_kb_by_cve.py#L9 # MSRC CVRF is not available before 2016 # if file exists locally, load it #description = [vuln['Title']['Value'] for vuln in cvrf_json["Vulnerability"] if vuln["CVE"] == cve['id']] #write results to disk ```mermaid gantt  title {keyword} dateFormat YYYY-MM-DD axisFormat %Y-%m  section CVE Release Dates {rows}  ``` #handle release dates # Get List of CVEs #print(json.dumps(cve_json, indent=4, sort_keys=True))