Projects STRLCPY flan Commits ed03000e
🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■ ■
    Dockerfile
    1 1  FROM python:3.5-alpine
    2 2   
    3 3  RUN apk add --no-cache nmap nmap-scripts git
    4  - 
    5  -RUN pip install --no-cache-dir xmltodict google-cloud-storage boto3
     4 +COPY requirements.txt /
     5 +RUN pip install --no-cache-dir -r requirements.txt
    6 6   
    7 7  RUN git clone https://github.com/vulnersCom/nmap-vulners /usr/share/nmap/scripts/vulners && nmap --script-updatedb
    8 8  RUN mkdir /shared
    9 9   
    10  -COPY run.sh output_report.py latex_header.tex gcp_push.py aws_push.py /
     10 +COPY run.sh output_report.py gcp_push.py aws_push.py /
     11 +COPY contrib /contrib
    11 12  COPY shared /shared
    12 13   
    13 14  RUN chmod +x /run.sh
    skipped 3 lines
  • ■ ■ ■ ■ ■
    contrib/__init__.py
     1 + 
  • ■ ■ ■ ■ ■ ■
    contrib/descriptions/__init__.py
     1 +from .description_provider import VulnDescription, VulnDescriptionProvider
     2 +from .cveproject import CveProjectProvider
     3 + 
  • ■ ■ ■ ■ ■ ■
    contrib/descriptions/cveproject.py
     1 +from requests import Session, HTTPError
     2 + 
     3 +from contrib.descriptions import VulnDescriptionProvider, VulnDescription
     4 + 
     5 +__all__ = ['CveProjectProvider']
     6 + 
     7 + 
     8 +class CveProjectProvider(VulnDescriptionProvider):
     9 + """
     10 + Provides vulnerability descriptions using requests to CVEProject
     11 + """
     12 + uri_template = 'https://raw.githubusercontent.com/CVEProject/cvelist/master/{}/{}/{}.json'
     13 + nist_uri_template = 'https://nvd.nist.gov/vuln/detail/{}'
     14 + 
     15 + def __init__(self, session: Session):
     16 + self.sess = session
     17 + self.cache = {}
     18 + 
     19 + def get_description(self, vuln: str, vuln_type: str) -> VulnDescription:
     20 + if vuln in self.cache:
     21 + return self.cache[vuln]
     22 + 
     23 + try:
     24 + if vuln_type == 'cve':
     25 + year = vuln[4:8]
     26 + section = vuln[9:-3] + 'xxx'
     27 + url = self.uri_template.format(year, section, vuln)
     28 + response = self.sess.get(url)
     29 + response.raise_for_status()
     30 + cve_json = response.json()
     31 + description = cve_json['description']['description_data'][0]['value']
     32 + self.cache[vuln] = description
     33 + return VulnDescription(description, self.nist_uri_template.format(vuln))
     34 + except HTTPError as he:
     35 + return VulnDescription('', 'Description fetching error: ' + str(he))
     36 + 
     37 + return VulnDescription('', '')
     38 + 
  • ■ ■ ■ ■ ■ ■
    contrib/descriptions/description_provider.py
     1 +import abc
     2 +from typing import Optional
     3 + 
     4 +__all__ = ['VulnDescriptionProvider', 'VulnDescription']
     5 + 
     6 + 
     7 +class VulnDescription:
     8 + def __init__(self, text: str, url: Optional[str] = None):
     9 + self.text = text
     10 + self.url = url
     11 + 
     12 + 
     13 +class VulnDescriptionProvider(metaclass=abc.ABCMeta):
     14 + """
     15 + Provides extended vulnerability description by vulnerablity identifier and type
     16 + """
     17 + @abc.abstractmethod
     18 + def get_description(self, vuln: str, vuln_type: str) -> VulnDescription:
     19 + pass
     20 + 
  • ■ ■ ■ ■ ■ ■
    contrib/internal_types/__init__.py
     1 +from .flan_types import *
     2 + 
  • ■ ■ ■ ■ ■ ■
    contrib/internal_types/flan_types.py
     1 +from collections import defaultdict
     2 +from typing import List, Dict
     3 + 
     4 +__all__ = ['SeverityLevels', 'Vuln', 'ScanResult']
     5 + 
     6 + 
     7 +class SeverityLevels:
     8 + """
     9 + Just constants
     10 + """
     11 + Low = 'Low'
     12 + Medium = 'Medium'
     13 + High = 'High'
     14 + 
     15 + 
     16 +class Vuln:
     17 + """
     18 + Descriptor for vulnerability
     19 + """
     20 + def __init__(self, name: str, vuln_type: str, severity: float):
     21 + self.name = name
     22 + self.vuln_type = vuln_type
     23 + self.severity = severity
     24 + 
     25 + @staticmethod
     26 + def convert_severity(severity: float) -> str:
     27 + """
     28 + :return: Float severity value to text
     29 + """
     30 + if severity < 4:
     31 + return 'Low'
     32 + if severity < 7:
     33 + return 'Medium'
     34 + return 'High'
     35 + 
     36 + @property
     37 + def severity_str(self) -> str:
     38 + """
     39 + :return: Text severity representation
     40 + """
     41 + return self.convert_severity(self.severity)
     42 + 
     43 + 
     44 +class ScanResult:
     45 + """
     46 + Scan result representation
     47 + """
     48 + def __init__(self):
     49 + self.locations = defaultdict(list) # type: Dict[str, List[str]]
     50 + self.vulns = [] # type: List[Vuln]
     51 + 
  • ■ ■ ■ ■ ■ ■
    contrib/parsers/__init__.py
     1 +from .flan_xml_parser import FlanXmlParser
     2 + 
  • ■ ■ ■ ■ ■ ■
    contrib/parsers/flan_xml_parser.py
     1 +from collections import defaultdict
     2 +from typing import Dict, Any, List, Set
     3 + 
     4 +import xmltodict
     5 + 
     6 +from contrib.internal_types import ScanResult, Vuln
     7 + 
     8 + 
     9 +__all__ = ['FlanXmlParser']
     10 + 
     11 + 
     12 +class FlanXmlParser:
     13 + """
     14 + NMAP XML file reader and contents parser
     15 + """
     16 + def __init__(self):
     17 + self.results = defaultdict(ScanResult)
     18 + self.vulnerable_services = [] # type: List[str]
     19 + 
     20 + @property
     21 + def vulnerable_dict(self) -> Dict[str, ScanResult]:
     22 + """
     23 + :return: Map {app_name -> scan result} for vulnerable services
     24 + """
     25 + return {service: self.results[service] for service in self.vulnerable_services}
     26 + 
     27 + @property
     28 + def non_vulnerable_dict(self) -> Dict[str, ScanResult]:
     29 + """
     30 + :return: Map {app_name -> scan result} for services without detected vulnerabilities
     31 + """
     32 + return {service: self.results[service] for service in self.non_vuln_services}
     33 + 
     34 + @property
     35 + def non_vuln_services(self) -> Set[str]:
     36 + """
     37 + :return: App names for services without detected vulnerabilities
     38 + """
     39 + return set(self.results) - set(self.vulnerable_services)
     40 + 
     41 + def parse(self, data: Dict[str, Any]):
     42 + """
     43 + Parse xmltodict output and fill internal collections
     44 + :param data: xmltodict output
     45 + """
     46 + if 'host' not in data['nmaprun']:
     47 + return
     48 + 
     49 + hosts = data['nmaprun']['host']
     50 + 
     51 + if isinstance(hosts, list):
     52 + for h in hosts:
     53 + self.parse_host(h)
     54 + else:
     55 + self.parse_host(hosts)
     56 + 
     57 + def parse_vuln(self, app_name: str, vuln: List[Dict[str, Any]]):
     58 + vuln_name = ''
     59 + severity = ''
     60 + vuln_type = ''
     61 + for field in vuln:
     62 + if field['@key'] == 'cvss':
     63 + severity = float(field['#text'])
     64 + elif field['@key'] == 'id':
     65 + vuln_name = field['#text']
     66 + elif field['@key'] == 'type':
     67 + vuln_type = field['#text']
     68 + 
     69 + self.results[app_name].vulns.append(Vuln(vuln_name, vuln_type, severity))
     70 + 
     71 + def parse_script(self, ip_addr: str, port: str, app_name: str, script: Dict[str, Any]):
     72 + if 'table' not in script:
     73 + print('ERROR in script: ' + script['@output'] + " at location: " + ip_addr + " port: " + port + " app: " +
     74 + app_name)
     75 + return
     76 + self.vulnerable_services.append(app_name)
     77 + script_table = script['table']['table']
     78 + if isinstance(script_table, list):
     79 + for vuln in script_table:
     80 + self.parse_vuln(app_name, vuln['elem'])
     81 + else:
     82 + self.parse_vuln(app_name, script_table['elem'])
     83 + 
     84 + def parse_port(self, ip_addr: str, port: Dict[str, Any]):
     85 + if port['state']['@state'] == 'closed':
     86 + return
     87 + 
     88 + app_name = self.get_app_name(port['service'])
     89 + port_num = port['@portid']
     90 + new_app = app_name not in self.results
     91 + self.results[app_name].locations[ip_addr].append(port_num)
     92 + 
     93 + if new_app and 'script' in port: # vulnerabilities parsed only if this app didn't appear before
     94 + scripts = port['script']
     95 + if isinstance(scripts, list):
     96 + for s in scripts:
     97 + if s['@id'] == 'vulners':
     98 + self.parse_script(ip_addr, port_num, app_name, s)
     99 + else:
     100 + if scripts['@id'] == 'vulners':
     101 + self.parse_script(ip_addr, port_num, app_name, scripts)
     102 + 
     103 + def parse_host(self, host: Dict[str, Any]):
     104 + addresses = host['address']
     105 + ip_addr = ''
     106 + if isinstance(addresses, list):
     107 + for addr in addresses:
     108 + if "ip" in addr['@addrtype']:
     109 + ip_addr = addr['@addr']
     110 + else:
     111 + ip_addr = addresses['@addr']
     112 + 
     113 + if not ip_addr:
     114 + return
     115 + 
     116 + if host['status']['@state'] == 'up' and 'port' in host['ports']:
     117 + ports = host['ports']['port']
     118 + if isinstance(ports, list):
     119 + for p in ports:
     120 + self.parse_port(ip_addr, p)
     121 + else:
     122 + self.parse_port(ip_addr, ports)
     123 + 
     124 + def read_xml_file(self, path: str) -> Dict[str, Any]:
     125 + """
     126 + Read file and convert to dictionary. To read raw contents use `read_xml_contents`
     127 + 
     128 + :param path: path to .xml file
     129 + :return: parsed contents
     130 + """
     131 + with open(path) as f:
     132 + contents = f.read()
     133 + return self.read_xml_contents(contents)
     134 + 
     135 + @staticmethod
     136 + def get_app_name(service: Dict[str, Any]) -> str:
     137 + app_name = ''
     138 + if '@product' in service:
     139 + app_name += service['@product'] + ' '
     140 + if '@version' in service:
     141 + app_name += service['@version'] + ' '
     142 + elif '@name' in service:
     143 + app_name += service['@name'] + ' '
     144 + 
     145 + if 'cpe' in service:
     146 + if isinstance(service['cpe'], list):
     147 + for cpe in service['cpe']:
     148 + app_name += '(' + cpe + ') '
     149 + else:
     150 + app_name += '(' + service['cpe'] + ') '
     151 + return app_name
     152 + 
     153 + @staticmethod
     154 + def read_xml_contents(contents: str) -> Dict[str, Any]:
     155 + return xmltodict.parse(contents)
     156 + 
  • ■ ■ ■ ■ ■ ■
    contrib/report_builders/__init__.py
     1 +from .report_builder import ReportBuilder
     2 +from .latex_report_builder import LatexReportBuilder
     3 +from .markdown_report_builder import MarkdownReportBuilder
     4 + 
  • ■ ■ ■ ■ ■ ■
    contrib/report_builders/latex_report_builder.py
     1 +from typing import Any, Dict
     2 + 
     3 +from contrib.descriptions import VulnDescriptionProvider
     4 +from contrib.report_builders import ReportBuilder
     5 +from contrib.internal_types import ScanResult, SeverityLevels
     6 + 
     7 +__all__ = ['LatexReportBuilder']
     8 + 
     9 + 
     10 +class LatexReportBuilder(ReportBuilder):
     11 + """
     12 + Report builer for LaTeX format. Returns raw contents as string
     13 + """
     14 + def __init__(self, description_provider: VulnDescriptionProvider):
     15 + """
     16 + :param description_provider: A provider of vulnerability full description
     17 + """
     18 + self.description_provider = description_provider
     19 + self.buffer = self.header
     20 + self.colors = {SeverityLevels.High: 'FD6864',
     21 + SeverityLevels.Medium: 'F8A102',
     22 + SeverityLevels.Low: '34CDF9'}
     23 + 
     24 + def init_report(self, start_date: str, nmap_command: str):
     25 + self._append('Flan Scan ran a network vulnerability scan with the following Nmap command on '
     26 + + start_date
     27 + + 'UTC.\n\\begin{lstlisting}\n'
     28 + + nmap_command
     29 + + '\n\\end{lstlisting}\nTo find out what IPs were scanned see the end of this report.\n')
     30 + 
     31 + def build(self) -> Any:
     32 + return self.buffer
     33 + 
     34 + @property
     35 + def header(self) -> str:
     36 + return self.report_header
     37 + 
     38 + def add_vulnerable_services(self, scan_results: Dict[str, ScanResult]):
     39 + for s, report in scan_results.items():
     40 + self._append('\\item \\textbf{\\large ' + s + ' \\large}')
     41 + vulns = report.vulns
     42 + locations = report.locations
     43 + num_vulns = len(vulns)
     44 + 
     45 + for v in vulns:
     46 + description = self.description_provider.get_description(v.name, v.vuln_type)
     47 + severity_name = v.severity_str
     48 + self._append('\\begin{figure}[h!]\n')
     49 + self._append('\\begin{tabular}{|p{16cm}|}\\rowcolor[HTML]{'
     50 + + self.colors[severity_name]
     51 + + '} \\begin{tabular}{@{}p{15cm}>{\\raggedleft\\arraybackslash} p{0.5cm}@{}}\\textbf{'
     52 + + v.name + ' ' + severity_name + ' ('
     53 + + str(v.severity)
     54 + + ')} & \\href{' + description.url
     55 + + '}{\\large \\faicon{link}}'
     56 + + '\\end{tabular}\\\\\n Summary:'
     57 + + description.text
     58 + + '\\\\ \\hline \\end{tabular} ')
     59 + self._append('\\end{figure}\n')
     60 + 
     61 + self._append('\\FloatBarrier\n\\textbf{The above '
     62 + + str(num_vulns)
     63 + + ' vulnerabilities apply to these network locations:}\n\\begin{itemize}\n')
     64 + for addr in locations:
     65 + self._append('\\item ' + addr + ' Ports: ' + str(locations[addr]) + '\n')
     66 + self._append('\\\\ \\\\ \n \\end{itemize}\n')
     67 + self._append('\\end{enumerate}\n')
     68 + 
     69 + def add_non_vulnerable_services(self, scan_results: Dict[str, ScanResult]):
     70 + for app_name, result in scan_results.items():
     71 + self._append('\\item \\textbf{\\large ' + app_name + ' \\large}\n\\begin{itemize}\n')
     72 + locations = result.locations
     73 + for addr in locations:
     74 + self._append('\\item ' + addr + ' Ports: ' + str(locations[addr]) + '\n')
     75 + self._append('\\end{itemize}\n')
     76 + self._append('\\end{enumerate}\n')
     77 + 
     78 + def initialize_section(self):
     79 + self._append('\\begin{enumerate}[wide, labelwidth=!, labelindent=0pt, label=\\textbf{\\large \\arabic{enumi} '
     80 + '\\large}]\n')
     81 + 
     82 + def add_vulnerable_section(self):
     83 + self._append('\\section*{Services with Vulnerabilities}')
     84 + 
     85 + def add_non_vulnerable_section(self):
     86 + self._append('\\section*{Services With No Known Vulnerabilities}')
     87 + 
     88 + def add_ips_section(self):
     89 + self._append('\\section*{List of IPs Scanned}')
     90 + self._append('\\begin{itemize}\n')
     91 + 
     92 + def add_ip_address(self, ip: str):
     93 + self._append('\\item ' + ip + '\n')
     94 + 
     95 + def finalize(self):
     96 + self._append('\\end{itemize}\n')
     97 + self._append('\\end{document}')
     98 + 
     99 + def _append(self, text: str):
     100 + self.buffer += text
     101 + 
     102 + # Don't want to depend on external file since this header is not so big.
     103 + report_header = r"""\documentclass{article}
     104 +\usepackage{enumitem}
     105 +\usepackage[margin=1in]{geometry}
     106 +\usepackage[utf8]{inputenc}
     107 +\usepackage[table,xcdraw]{xcolor}
     108 +\usepackage{placeins}
     109 +\usepackage{hyperref}
     110 +\usepackage{fontawesome}
     111 +\usepackage{listings}
     112 +\lstset{
     113 +basicstyle=\small\ttfamily,
     114 +columns=flexible,
     115 +breaklines=true
     116 +}
     117 +\title{Flan Scan Report\\}
     118 +\date{\today}
     119 + 
     120 +\begin{document}
     121 + 
     122 +\maketitle
     123 + 
     124 +\section*{Summary}
     125 + 
     126 +"""
     127 + 
  • ■ ■ ■ ■ ■ ■
    contrib/report_builders/markdown_report_builder.py
     1 +from datetime import datetime
     2 +from typing import Any, Dict, List
     3 + 
     4 +from contrib.descriptions import VulnDescriptionProvider
     5 +from contrib.internal_types import ScanResult
     6 +from contrib.report_builders import ReportBuilder
     7 + 
     8 +__all__ = ['MarkdownReportBuilder']
     9 + 
     10 + 
     11 +class MarkdownReportBuilder(ReportBuilder):
     12 + def __init__(self, description_provider: VulnDescriptionProvider):
     13 + self.description_provider = description_provider
     14 + self._buffer = ''
     15 + 
     16 + def init_report(self, start_date: str, nmap_command: str):
     17 + self._append_line(self.header)
     18 + self._append_line('## {date:%B %d, %Y}'.format(date=datetime.utcnow()))
     19 + self._append_line('### **Summary**')
     20 + self._append_line('Flan Scan ran a network vulnerability scan with the following Nmap command on {date}'
     21 + .format(date=start_date))
     22 + self._append_line('`{command}`'.format(command=nmap_command))
     23 + 
     24 + def build(self) -> Any:
     25 + return self._buffer
     26 + 
     27 + def add_vulnerable_section(self):
     28 + self._append_line('### Services with vulnerabilities')
     29 + 
     30 + def add_non_vulnerable_section(self):
     31 + self._append_line('### Services with no *known* vulnerabilities')
     32 + 
     33 + def add_vulnerable_services(self, scan_results: Dict[str, ScanResult]):
     34 + for i, pair in enumerate(scan_results.items(), start=1):
     35 + app_name, report = pair # type: str, ScanResult
     36 + self._append_service(i, app_name)
     37 + num_vulns = len(report.vulns)
     38 + 
     39 + for v in report.vulns:
     40 + description = self.description_provider.get_description(v.name, v.vuln_type)
     41 + self._append_line('- [**{name}** {severity} ({severity_num})]({link} "{title}")'
     42 + .format(name=v.name, severity=v.severity_str, severity_num=v.severity,
     43 + link=description.url, title=v.name), spaces=4)
     44 + self._append_line('```text', separators=1, spaces=6)
     45 + self._append_line(description.text, separators=1, spaces=6)
     46 + self._append_line('```', spaces=6)
     47 + 
     48 + self._append_line('The above {num} vulnerabilities apply to these network locations'.format(num=num_vulns),
     49 + spaces=4)
     50 + self._append_line('```text', separators=1, spaces=4)
     51 + for addr, ports in report.locations.items():
     52 + self._append_location(addr, ports, spaces=4)
     53 + self._append_line('```', spaces=4)
     54 + 
     55 + def add_non_vulnerable_services(self, scan_results: Dict[str, ScanResult]):
     56 + for i, pair in enumerate(scan_results.items(), start=1):
     57 + app_name, report = pair # type: str, ScanResult
     58 + self._append_service(i, app_name)
     59 + 
     60 + for addr, ports in report.locations.items():
     61 + self._append_location(addr, ports, spaces=4)
     62 + self._append('\n')
     63 + 
     64 + def initialize_section(self):
     65 + pass
     66 + 
     67 + def add_ips_section(self):
     68 + self._append_line('### List of IPs Scanned')
     69 + 
     70 + def add_ip_address(self, ip: str):
     71 + self._append_line('- {ip}'.format(ip=ip), separators=1)
     72 + 
     73 + def finalize(self):
     74 + pass
     75 + 
     76 + @property
     77 + def header(self) -> Any:
     78 + return '# Flan scan report'
     79 + 
     80 + def _append(self, text: str, spaces: int = 0):
     81 + if spaces:
     82 + self._buffer += ' ' * spaces
     83 + self._buffer += text
     84 + 
     85 + def _append_line(self, text: str, separators: int = 2, spaces: int = 0):
     86 + self._append(text, spaces)
     87 + self._append('\n' * separators)
     88 + 
     89 + def _append_service(self, index: int, name: str, spaces: int = 0):
     90 + self._append_line('{index}. **{service}**'.format(index=index, service=name.strip()), spaces=spaces,
     91 + separators=1)
     92 + 
     93 + def _append_location(self, address: str, ports: List[str], spaces: int):
     94 + self._append_line('- {address} Ports: {ports}'.format(address=address, ports=', '.join(ports)), spaces=spaces,
     95 + separators=1)
     96 + 
  • ■ ■ ■ ■ ■ ■
    contrib/report_builders/report_builder.py
     1 +import abc
     2 +from typing import Any, Dict
     3 + 
     4 +from contrib.internal_types import ScanResult
     5 + 
     6 + 
     7 +__all__ = ['ReportBuilder']
     8 + 
     9 + 
     10 +class ReportBuilder(metaclass=abc.ABCMeta):
     11 + @abc.abstractmethod
     12 + def init_report(self, start_date: str, nmap_command: str):
     13 + """
     14 + Creates document section with report overview
     15 + """
     16 + pass
     17 + 
     18 + @abc.abstractmethod
     19 + def build(self) -> Any:
     20 + """
     21 + :return: Ready report in specific format
     22 + """
     23 + pass
     24 + 
     25 + @abc.abstractmethod
     26 + def add_vulnerable_section(self):
     27 + """
     28 + Adds header for section with vulnerable services
     29 + """
     30 + pass
     31 + 
     32 + @abc.abstractmethod
     33 + def add_non_vulnerable_section(self):
     34 + """
     35 + Adds header for section with services without detected vulnerabilities
     36 + """
     37 + pass
     38 + 
     39 + @abc.abstractmethod
     40 + def add_vulnerable_services(self, scan_results: Dict[str, ScanResult]):
     41 + """
     42 + Adds descriptions of vulnerable services
     43 + """
     44 + pass
     45 + 
     46 + @abc.abstractmethod
     47 + def add_non_vulnerable_services(self, scan_results: Dict[str, ScanResult]):
     48 + """
     49 + Adds descriptions of services without detected vulnerabilities
     50 + """
     51 + pass
     52 + 
     53 + @abc.abstractmethod
     54 + def initialize_section(self):
     55 + """
     56 + Adds begin of report section
     57 + """
     58 + pass
     59 + 
     60 + @abc.abstractmethod
     61 + def add_ips_section(self):
     62 + """
     63 + Adds section with list of scanned ip addresses
     64 + """
     65 + pass
     66 + 
     67 + @abc.abstractmethod
     68 + def add_ip_address(self, ip: str):
     69 + """
     70 + Adds IP-address to scanned addresses section
     71 + """
     72 + pass
     73 + 
     74 + @abc.abstractmethod
     75 + def finalize(self):
     76 + """
     77 + Adds report footer
     78 + """
     79 + pass
     80 + 
     81 + @property
     82 + @abc.abstractmethod
     83 + def header(self) -> Any:
     84 + """
     85 + :return: Common document header for format type (e.g. for latex report)
     86 + """
     87 + pass
     88 + 
  • ■ ■ ■ ■ ■ ■
    latex_header.tex
    1  -\documentclass{article}
    2  -\usepackage{enumitem}
    3  -\usepackage[margin=1in]{geometry}
    4  -\usepackage[utf8]{inputenc}
    5  -\usepackage[table,xcdraw]{xcolor}
    6  -\usepackage{placeins}
    7  -\usepackage{hyperref}
    8  -\usepackage{fontawesome}
    9  -\usepackage{listings}
    10  -\lstset{
    11  -basicstyle=\small\ttfamily,
    12  -columns=flexible,
    13  -breaklines=true
    14  -}
    15  -\title{Flan Scan Report\\}
    16  -\date{\today}
    17  - 
    18  -\begin{document}
    19  - 
    20  -\maketitle
    21  - 
    22  -\section*{Summary}
    23  - 
  • ■ ■ ■ ■ ■
    output_report.py
     1 +import os
    1 2  import sys
    2  -import json
    3  -import urllib.request as urllib
    4  -import os
    5  -import xmltodict
     3 +from typing import IO
    6 4   
    7  -results = {}
    8  -vulnerable_services = []
    9  -colors = {'High': 'FD6864', 'Medium': 'F8A102', 'Low': '34CDF9'}
     5 +from requests import Session
    10 6   
     7 +from contrib.descriptions import CveProjectProvider
     8 +from contrib.parsers import FlanXmlParser
     9 +from contrib.report_builders import ReportBuilder, LatexReportBuilder, MarkdownReportBuilder
    11 10   
    12  -def parse_vuln(ip_addr, port, app_name, vuln):
    13  - vuln_name = ''
    14  - severity = ''
    15  - type = ''
    16  - for field in vuln:
    17  - if field['@key'] == 'cvss':
    18  - severity = float(field['#text'])
    19  - elif field['@key'] == 'id':
    20  - vuln_name = field['#text']
    21  - elif field['@key'] == 'type':
    22  - type = field['#text']
    23  - if 'vulns'in results[app_name].keys():
    24  - results[app_name]['vulns'].append({'name': vuln_name,
    25  - 'type': type,
    26  - 'severity': severity})
    27  - else:
    28  - results[app_name]['vulns'] = [{'name': vuln_name,
    29  - 'type': type,
    30  - 'severity': severity}]
    31 11   
     12 +def create_report(parser: FlanXmlParser, builder: ReportBuilder, nmap_command: str, start_date: str, output_writer: IO,
     13 + ip_reader: IO):
    32 14   
    33  -def parse_script(ip_addr, port, app_name, script):
    34  - if 'table' in script.keys():
    35  - vulnerable_services.append(app_name)
    36  - script_table = script['table']['table']
    37  - if isinstance(script_table, list):
    38  - for vuln in script_table:
    39  - parse_vuln(ip_addr, port, app_name, vuln['elem'])
    40  - else:
    41  - parse_vuln(ip_addr, port, app_name, script_table['elem'])
    42  - else:
    43  - print('ERROR in script: ' + script['@output'] + " at location: " + ip_addr + " port: " + port + " app: " + app_name)
    44  - 
    45  - 
    46  -def get_app_name(service):
    47  - app_name = ''
    48  - if '@product' in service.keys():
    49  - app_name += service['@product'] + " "
    50  - if '@version' in service.keys():
    51  - app_name += service['@version'] + " "
    52  - elif '@name' in service.keys():
    53  - app_name += service['@name'] + " "
    54  - 
    55  - if('cpe' in service.keys()):
    56  - if isinstance(service['cpe'], list):
    57  - for cpe in service['cpe']:
    58  - app_name += '(' + cpe + ") "
    59  - else:
    60  - app_name += '(' + service['cpe'] + ") "
    61  - return app_name
    62  - 
    63  - 
    64  -def parse_port(ip_addr, port):
    65  - if port['state']['@state'] == 'closed':
    66  - return
    67  - app_name = get_app_name(port['service'])
    68  - 
    69  - port_num = port['@portid']
    70  - 
    71  - if app_name in results.keys():
    72  - if ip_addr in results[app_name]['locations'].keys():
    73  - results[app_name]['locations'][ip_addr].append(port_num)
    74  - else:
    75  - results[app_name]['locations'][ip_addr] = [port_num]
    76  - else:
    77  - results[app_name] = {'locations': {ip_addr: [port_num]}}
    78  - if 'script' in port.keys():
    79  - scripts = port['script']
    80  - if isinstance(scripts, list):
    81  - for s in scripts:
    82  - if s['@id'] == 'vulners':
    83  - parse_script(ip_addr, port_num, app_name, s)
    84  - else:
    85  - if scripts['@id'] == 'vulners':
    86  - parse_script(ip_addr, port_num, app_name, scripts)
    87  - 
    88  - 
    89  -def parse_host(host):
    90  - addresses = host['address']
    91  - if isinstance(addresses, list):
    92  - for addr in addresses:
    93  - if "ip" in addr['@addrtype']:
    94  - ip_addr = addr['@addr']
    95  - else:
    96  - ip_addr = addresses['@addr']
    97  - 
    98  - if host['status']['@state'] == 'up' and 'port' in host['ports'].keys():
    99  - ports = host['ports']['port']
    100  - if isinstance(ports, list):
    101  - for p in ports:
    102  - parse_port(ip_addr, p)
    103  - else:
    104  - parse_port(ip_addr, ports)
    105  - 
    106  - 
    107  -def parse_results(data):
    108  - if 'host' in data['nmaprun'].keys():
    109  - hosts = data['nmaprun']['host']
    110  - 
    111  - if isinstance(hosts, list):
    112  - for h in hosts:
    113  - parse_host(h)
    114  - else:
    115  - parse_host(hosts)
    116  - 
    117  - 
    118  -def convert_severity(sev):
    119  - if sev < 4:
    120  - return 'Low'
    121  - elif sev < 7:
    122  - return 'Medium'
    123  - else:
    124  - return 'High'
    125  - 
    126  - 
    127  -def get_description(vuln, type):
    128  - if type == 'cve':
    129  - year = vuln[4:8]
    130  - section = vuln[9:-3] + 'xxx'
    131  - url = """https://raw.githubusercontent.com/CVEProject/cvelist/master/{}/{}/{}.json""".format(year, section, vuln)
    132  - cve_json = json.loads(urllib.urlopen(url).read().decode("utf-8"))
    133  - return cve_json["description"]["description_data"][0]["value"]
    134  - else:
    135  - return ''
     15 + builder.init_report(start_date, nmap_command)
    136 16   
     17 + if parser.vulnerable_services:
     18 + builder.add_vulnerable_section()
     19 + builder.initialize_section()
     20 + builder.add_vulnerable_services(parser.vulnerable_dict)
    137 21   
    138  -def create_latex(nmap_command, start_date):
    139  - f = open('./latex_header.tex')
    140  - write_buffer = f.read()
    141  - f.close()
     22 + if parser.non_vuln_services:
     23 + builder.add_non_vulnerable_section()
     24 + builder.initialize_section()
     25 + builder.add_non_vulnerable_services(parser.non_vulnerable_dict)
    142 26   
    143  - output_file = sys.argv[2]
    144  - ip_file = sys.argv[3]
     27 + builder.add_ips_section()
     28 + for ip in ip_reader:
     29 + builder.add_ip_address(ip)
    145 30   
    146  - write_buffer += "Flan Scan ran a network vulnerability scan with the following Nmap command on " \
    147  - + start_date \
    148  - + "UTC.\n\\begin{lstlisting}\n" \
    149  - + nmap_command \
    150  - + "\n\end{lstlisting}\nTo find out what IPs were scanned see the end of this report.\n"
    151  - write_buffer += "\section*{Services with Vulnerabilities}"
    152  - if vulnerable_services:
    153  - write_buffer += """\\begin{enumerate}[wide, labelwidth=!, labelindent=0pt,
    154  - label=\\textbf{\large \\arabic{enumi} \large}]\n"""
    155  - for s in vulnerable_services:
    156  - write_buffer += '\item \\textbf{\large ' + s + ' \large}'
    157  - vulns = results[s]['vulns']
    158  - locations = results[s]['locations']
    159  - num_vulns = len(vulns)
     31 + builder.finalize()
     32 + output_writer.write(builder.build())
    160 33   
    161  - for i, v in enumerate(vulns):
    162  - write_buffer += '\\begin{figure}[h!]\n'
    163  - severity_name = convert_severity(v['severity'])
    164  - write_buffer += '\\begin{tabular}{|p{16cm}|}\\rowcolor[HTML]{' \
    165  - + colors[severity_name] \
    166  - + """} \\begin{tabular}{@{}p{15cm}>{\\raggedleft\\arraybackslash}
    167  - p{0.5cm}@{}}\\textbf{""" \
    168  - + v['name'] + ' ' + severity_name + ' (' \
    169  - + str(v['severity']) \
    170  - + ')} & \href{https://nvd.nist.gov/vuln/detail/' \
    171  - + v['name'] + '}{\large \\faicon{link}}' \
    172  - + '\end{tabular}\\\\\n Summary:' \
    173  - + get_description(v['name'], v['type']) \
    174  - + '\\\\ \hline \end{tabular} '
    175  - write_buffer += '\end{figure}\n'
    176 34   
    177  - write_buffer += '\FloatBarrier\n\\textbf{The above ' \
    178  - + str(num_vulns) \
    179  - + """ vulnerabilities apply to these network locations:}\n
    180  - \\begin{itemize}\n"""
    181  - for addr in locations.keys():
    182  - write_buffer += '\item ' + addr + ' Ports: ' + str(locations[addr])+ '\n'
    183  - write_buffer += '\\\\ \\\\ \n \end{itemize}\n'
    184  - write_buffer += '\end{enumerate}\n'
     35 +def parse_nmap_command(raw_command: str) -> str:
     36 + nmap_split = raw_command.split()[:-1] # remove last element, ip address
     37 + nmap_split[3] = '<output-file>'
     38 + return ' '.join(nmap_split)
    185 39   
    186  - non_vuln_services = list(set(results.keys()) - set(vulnerable_services))
    187  - write_buffer += '\section*{Services With No Known Vulnerabilities}'
    188 40   
    189  - if non_vuln_services:
    190  - write_buffer += """\\begin{enumerate}[wide, labelwidth=!, labelindent=0pt,
    191  - label=\\textbf{\large \\arabic{enumi} \large}]\n"""
    192  - for ns in non_vuln_services:
    193  - write_buffer += '\item \\textbf{\large ' + ns \
    194  - + ' \large}\n\\begin{itemize}\n'
    195  - locations = results[ns]['locations']
    196  - for addr in locations.keys():
    197  - write_buffer += '\item ' + addr + ' Ports: ' + str(locations[addr])+ '\n'
    198  - write_buffer += '\end{itemize}\n'
    199  - write_buffer += '\end{enumerate}\n'
     41 +def create_default_provider():
     42 + return CveProjectProvider(Session())
    200 43   
    201  - write_buffer += '\section*{List of IPs Scanned}'
    202  - write_buffer += '\\begin{itemize}\n'
    203  - f = open(ip_file)
    204  - for line in f:
    205  - write_buffer += '\item ' + line + '\n'
    206  - f.close()
    207  - write_buffer += '\end{itemize}\n'
    208 44   
    209  - write_buffer += '\end{document}'
    210  - latex_file = open(output_file, "w+")
    211  - latex_file.write(write_buffer)
    212  - latex_file.close()
     45 +def create_report_builder(report_type: str) -> ReportBuilder:
     46 + if report_type == 'latex':
     47 + return LatexReportBuilder(create_default_provider())
     48 + if report_type == 'md':
     49 + return MarkdownReportBuilder(create_default_provider())
     50 + raise NotImplementedError(report_type)
    213 51   
    214  -def parse_nmap_command(raw_command):
    215  - nmap_split = raw_command.split()[:-1] #remove last element, ip address
    216  - nmap_split[3] = "<output-file>"
    217  - return " ".join(nmap_split)
    218 52   
    219  -def main():
    220  - dirname = sys.argv[1]
    221  - nmap_command = ""
    222  - start_date = ""
     53 +def main(dirname: str, output_file: str, ip_file: str, report_type: str = 'latex'):
     54 + nmap_command = ''
     55 + start_date = ''
     56 + builder = create_report_builder(report_type)
     57 + parser = FlanXmlParser()
    223 58   
    224  - for i, filename in enumerate(os.listdir(dirname)):
    225  - f = open(dirname + "/" + filename)
    226  - xml_content = f.read()
    227  - f.close()
    228  - data = xmltodict.parse(xml_content)
    229  - parse_results(data)
    230  - if i == 0:
    231  - nmap_command = parse_nmap_command(data['nmaprun']['@args'])
    232  - start_date = data['nmaprun']['@startstr']
     59 + for entry in os.scandir(dirname): # type: os.DirEntry
     60 + if not (entry.is_file() and entry.name.endswith('.xml')):
     61 + continue
     62 + data = parser.read_xml_file(entry.path)
     63 + parser.parse(data)
     64 + nmap_command = parse_nmap_command(data['nmaprun']['@args'])
     65 + start_date = data['nmaprun']['@startstr']
    233 66   
    234  - create_latex(nmap_command, start_date)
     67 + with open(output_file, 'w+') as output, open(ip_file) as ip_source:
     68 + create_report(parser, builder, nmap_command, start_date, output, ip_source)
    235 69   
    236 70   
    237  -if __name__ == "__main__":
    238  - main()
     71 +if __name__ == '__main__':
     72 + main(*sys.argv[1:4], report_type='latex')
    239 73   
  • ■ ■ ■ ■ ■ ■
    requirements.txt
     1 +xmltodict==0.12.0
     2 +google-cloud-storage==1.23.0
     3 +boto3==1.12.15
     4 + 
Please wait...
Page is in error, reload to recover