Beispiel #1
0
def add_table_of_contents(document, label='Table of Contents'):
    document.add_heading(label, level=1)
    paragraph = document.add_paragraph()
    run = paragraph.add_run()
    fldChar = OxmlElement('w:fldChar')  # creates a new element
    fldChar.set(qn('w:fldCharType'), 'begin')  # sets attribute on element
    instrText = OxmlElement('w:instrText')
    instrText.set(qn('xml:space'), 'preserve')  # sets attribute on element
    instrText.text = r'TOC \o "1-3" \h \z \u'  # change 1-3 depending on heading levels you need

    fldChar2 = OxmlElement('w:fldChar')
    fldChar2.set(qn('w:fldCharType'), 'separate')
    fldChar3 = OxmlElement('w:t')
    fldChar3.text = "Right-click to update field."
    fldChar2.append(fldChar3)

    fldChar4 = OxmlElement('w:fldChar')
    fldChar4.set(qn('w:fldCharType'), 'end')

    r_element = run._r
    r_element.append(fldChar)
    r_element.append(instrText)
    r_element.append(fldChar2)
    r_element.append(fldChar4)
    p_element = paragraph._p
    return document
Beispiel #2
0
def generate_pw_toc(document):
    global gl_font_size
    add_simple_par(document, "", gl_font_size, align=docx.enum.text.WD_ALIGN_PARAGRAPH.CENTER, bold=False, breakpar=True)
    document.add_heading("Spis treści", 1)

    paragraph = document.add_paragraph()
    run = paragraph.add_run()
    fldChar = OxmlElement('w:fldChar')  # creates a new element
    fldChar.set(qn('w:fldCharType'), 'begin')  # sets attribute on element
    instrText = OxmlElement('w:instrText')
    instrText.set(qn('xml:space'), 'preserve')  # sets attribute on element
    instrText.text = 'TOC \\o "1-2" \\h \\z \\u'  # change 1-3 depending on heading levels you need

    fldChar2 = OxmlElement('w:fldChar')
    fldChar2.set(qn('w:fldCharType'), 'separate')
    fldChar3 = OxmlElement('w:t')
    fldChar3.text = "Kliknij prawym klawiszem myszki, aby zaktualizować spis treści."
    fldChar2.append(fldChar3)

    fldChar4 = OxmlElement('w:fldChar')
    fldChar4.set(qn('w:fldCharType'), 'end')

    r_element = run._r
    r_element.append(fldChar)
    r_element.append(instrText)
    r_element.append(fldChar2)
    r_element.append(fldChar4)
    p_element = paragraph._p
Beispiel #3
0
 def new(cls, text):
     """
     Return a new ``<w:t>`` element.
     """
     t = OxmlElement('w:t')
     t.text = text
     return t
Beispiel #4
0
def add_hyperlink(paragraph, url, text, color='0000FF', underline=True):
    """ places a hyperlink within a paragraph object
    """
    part = paragraph.part
    r_id = part.relate_to(url, RELATIONSHIP_TYPE.HYPERLINK, is_external=True)

    hyperlink = OxmlElement('w:hyperlink')
    hyperlink.set(
        qn('r:id'),
        r_id,
    )

    new_run = OxmlElement('w:r')
    rPr = OxmlElement('w:rPr')

    if color is not None:
        c = OxmlElement('w:color')
        c.set(qn('w:val'), color)
        rPr.append(c)

    if not underline:
        u = OxmlElement('w:u')
        u.set(qn('w:val'), 'none')
        rPr.append(u)

    # Join all the xml elements together add add the required text to the w:r element
    new_run.append(rPr)
    new_run.text = text
    hyperlink.append(new_run)

    paragraph._p.append(hyperlink)

    return hyperlink
    def insert_toc(self):
        paragraph = self.document.add_paragraph()
        run = paragraph.add_run()
        fldChar = OxmlElement('w:fldChar')  # creates a new element
        fldChar.set(qn('w:fldCharType'), 'begin')  # sets attribute on element
        instrText = OxmlElement('w:instrText')
        instrText.set(qn('xml:space'), 'preserve')  # sets attribute on element
        instrText.text = 'TOC \\o "1-3" \\h \\z \\u'  # change 1-3 depending on heading levels you need

        fldChar2 = OxmlElement('w:fldChar')
        fldChar2.set(qn('w:fldCharType'), 'separate')
        # fldChar3 = OxmlElement('w:t')
        fldChar3 = OxmlElement('w:updateFields')
        fldChar3.set(qn('w:val'), 'true')
        # fldChar3.text = "Right-click to update field."
        fldChar2.append(fldChar3)

        fldChar4 = OxmlElement('w:fldChar')
        fldChar4.set(qn('w:fldCharType'), 'end')

        r_element = run._r
        r_element.append(fldChar)
        r_element.append(instrText)
        r_element.append(fldChar2)
        r_element.append(fldChar4)
        p_element = paragraph._p
Beispiel #6
0
 def new(cls, text):
     """
     Return a new ``<w:t>`` element.
     """
     t = OxmlElement('w:t')
     t.text = text
     return t
    def add_page_number(run: Run):
        """
        Add the page number to a paragraph.

        Args:
            run: Run in which the page number will be added.
        """

        # access to XML run element <w:r>
        r = run._r

        # create new XML elements, set their attributes and add them to the run element
        # so that the page number correspond to a real page number
        fldChar1 = OxmlElement('w:fldChar')
        fldChar1.set(qn('w:fldCharType'), 'begin')
        r.append(fldChar1)

        instrText = OxmlElement('w:instrText')
        instrText.set(qn('xml:space'), 'preserve')
        instrText.text = 'PAGE'
        r.append(instrText)

        fldChar2 = OxmlElement('w:fldChar')
        fldChar2.set(qn('w:fldCharType'), 'end')
        r.append(fldChar2)
Beispiel #8
0
def add_url_hyperlink(paragraph, url: str, text: str):
    """Add URL hyperlink in docx report. Adapted from https://stackoverflow.com/a/47666747/906385

    Args:
        paragraph: a docx paragraph object
        url (str): The URL
        text (str): The text to display
    """
    # This gets access to the document.xml.rels file and gets a new relation id value
    part = paragraph.part
    r_id = part.relate_to(url,
                          constants.RELATIONSHIP_TYPE.HYPERLINK,
                          is_external=True)

    # Create the w:hyperlink tag and add needed values
    hyperlink = OxmlElement("w:hyperlink")
    hyperlink.set(qn("r:id"), r_id)

    # Create a w:r element and a new w:rPr element
    new_run = OxmlElement("w:r")
    rPr = OxmlElement("w:rPr")

    # Join all the xml elements together add add the required text to the w:r element
    new_run.append(rPr)
    new_run.text = text
    hyperlink.append(new_run)

    # Create a new Run object and add the hyperlink into it
    run = paragraph.add_run()
    run._r.append(hyperlink)

    # A workaround for the lack of a hyperlink style (doesn't go purple after using the link)
    # Delete this if using a template that has the hyperlink style in it
    run.font.color.theme_color = dml.MSO_THEME_COLOR_INDEX.HYPERLINK
    run.font.underline = True
def add_hyperlink(paragraph,
                  url,
                  text,
                  font_name=None,
                  underline_hyperlink=True,
                  indent=0,
                  color=True):
    """
    A function that places a hyperlink within a paragraph object.

    :param paragraph: The paragraph we are adding the hyperlink to.
    :param url: A string containing the required url
    :param text: The text displayed for the url
    :return: A Run object containing the hyperlink
    """

    # This gets access to the document.xml.rels file and gets a new relation id value
    part = paragraph.part
    r_id = part.relate_to(url, RT.HYPERLINK, is_external=True)

    # Create the w:hyperlink tag and add needed values
    hyperlink = OxmlElement('w:hyperlink')
    hyperlink.set(
        qn('r:id'),
        r_id,
    )
    hyperlink.set(qn('w:history'), '1')

    # Create a w:r element
    new_run = OxmlElement('w:r')

    # Create a new w:rPr element
    rPr = OxmlElement('w:rPr')

    # Create a w:rStyle element, note this currently does not add the hyperlink style as its not in
    # the default template, I have left it here in case someone uses one that has the style in it
    rStyle = OxmlElement('w:rStyle')
    rStyle.set(qn('w:val'), 'Hyperlink')

    # Join all the xml elements together add add the required text to the w:r element
    rPr.append(rStyle)
    new_run.append(rPr)
    new_run.text = text
    hyperlink.append(new_run)

    # Create a new Run object and add the hyperlink into it
    r = paragraph.add_run()
    r.font.name = font_name
    r._r.append(hyperlink)

    # A workaround for the lack of a hyperlink style (doesn't go purple after using the link)
    # Delete this if using a template that has the hyperlink style in it
    if color:
        r.font.color.theme_color = MSO_THEME_COLOR_INDEX.HYPERLINK
    r.font.underline = underline_hyperlink

    return r
Beispiel #10
0
 def add_fig_caption(self, paragraph):
     """Add figure caption to image with auto updating numbers
     - User must select all (cmd/ctrl + A), then F9 to update fig captions"""
     run = paragraph.add_run()
     r = run._r
     fldChar = OxmlElement('w:fldChar')
     fldChar.set(qn('w:fldCharType'), 'begin')
     r.append(fldChar)
     instrText = OxmlElement('w:instrText')
     instrText.text = ' SEQ Figure \* ARABIC'  # noqa
     r.append(instrText)
     fldChar = OxmlElement('w:fldChar')
     fldChar.set(qn('w:fldCharType'), 'end')
     r.append(fldChar)
Beispiel #11
0
def add_hyperlink(paragraph, text, url):
	part = paragraph.part
	r_id = part.relate_to(url, RELATIONSHIP_TYPE.HYPERLINK, is_external=True)
	hyperlink = OxmlElement("w:hyperlink")
	hyperlink.set(qn("r:id"), r_id, )
	new_run = OxmlElement("w:r")
	rPr = OxmlElement("w:rPr")
	new_run.append(rPr)
	new_run.text = text
	hyperlink.append(new_run)
	r = paragraph.add_run()
	r._r.append(hyperlink)
	r.font.color.theme_color = MSO_THEME_COLOR_INDEX.HYPERLINK
	r.font.underline = True
	return hyperlink
Beispiel #12
0
def add_hyperlink(paragraph, url, text):
    """
    A function that places a hyperlink within a paragraph object.

    Parameters
    ----------
    paragraph: The paragraph we are adding the hyperlink to.
    url: A string containing the required url
    text: The text displayed for the url

    Returns
    -------
    hyperlink: hyperlink object

    """

    # This gets access to the document.xml.rels file and gets a new relation id value
    part = paragraph.part
    r_id = part.relate_to(url,
                          docx.opc.constants.RELATIONSHIP_TYPE.HYPERLINK,
                          is_external=True)

    # Create the w:hyperlink tag and add needed values
    hyperlink = OxmlElement("w:hyperlink")
    hyperlink.set(qn("r:id"), r_id)

    # Create a w:r element
    new_run = OxmlElement("w:r")

    # Create a new w:rPr element
    rPr = OxmlElement("w:rPr")

    # Style it
    c = OxmlElement("w:color")
    c.set(qn("w:val"), "4F81BD")
    rPr.append(c)
    u = OxmlElement("w:u")
    u.set(qn("w:val"), "none")
    rPr.append(u)

    # Join all the xml elements together add add the required text to the w:r element
    new_run.append(rPr)
    new_run.text = text
    hyperlink.append(new_run)

    paragraph._p.append(hyperlink)

    return hyperlink
Beispiel #13
0
def add_hyperlink(paragraph, url, text, color, underline):
    """
    A function that places a hyperlink within a paragraph object.

    :param paragraph: The paragraph we are adding the hyperlink to.
    :param url: A string containing the required url
    :param text: The text displayed for the url
    :return: The hyperlink object
    """

    # This gets access to the document.xml.rels file and gets a new relation id value
    part = paragraph.part
    r_id = part.relate_to(url, RELATIONSHIP_TYPE.HYPERLINK, is_external=True)

    # Create the w:hyperlink tag and add needed values
    hyperlink = OxmlElement('w:hyperlink')
    hyperlink.set(
        sharedqn('r:id'),
        r_id,
    )

    # Create a w:r element
    new_run = OxmlElement('w:r')

    # Create a new w:rPr element
    rPr = OxmlElement('w:rPr')

    # Add color if it is given
    if not color is None:
        c = OxmlElement('w:color')
        c.set(sharedqn('w:val'), color)
        rPr.append(c)

    # Remove underlining if it is requested
    if not underline:
        u = OxmlElement('w:u')
        u.set(sharedqn('w:val'), 'none')
        rPr.append(u)

    # Join all the xml elements together add add the required text to the w:r element
    new_run.append(rPr)
    new_run.text = text
    hyperlink.append(new_run)

    paragraph._p.append(hyperlink)

    return hyperlink
def add_hyperlink(paragraph,
                  url,
                  text,
                  font="Times New Roman",
                  size=14,
                  color='0000FF',
                  underline=True):
    # https://github.com/python-openxml/python-docx/issues/74#issuecomment-261169410
    # https://github.com/python-openxml/python-docx/issues/383#issue-220027501

    part = paragraph.part
    r_id = part.relate_to(url, RELATIONSHIP_TYPE.HYPERLINK, is_external=True)
    hyperlink = OxmlElement('w:hyperlink')
    hyperlink.set(
        qn('r:id'),
        r_id,
    )
    new_run = OxmlElement('w:r')
    rPr = OxmlElement('w:rPr')

    c = OxmlElement('w:color')
    c.set(qn('w:val'), color)
    rPr.append(c)

    c = OxmlElement('w:sz')
    c.set(qn('w:val'), str(size * 2))  # size x2 and convert to str (20 == 10)
    rPr.append(c)

    c = OxmlElement('w:rFonts')
    c.set(qn('w:ascii'), font)
    c.set(qn('w:eastAsia'), font)
    c.set(qn('w:hAnsi'), font)
    c.set(qn('w:cs'), font)
    rPr.append(c)

    if not underline:
        u = OxmlElement('w:u')
        u.set(qn('w:val'), 'none')
        rPr.append(u)

    new_run.append(rPr)
    new_run.text = text
    hyperlink.append(new_run)
    paragraph._p.append(hyperlink)
    return hyperlink
Beispiel #15
0
def add_field(paragraph, field_code):
    # https://github.com/python-openxml/python-docx/issues/359#issuecomment-369271235
    run = paragraph.add_run()
    r = run._r
    fldChar = OxmlElement('w:fldChar')
    fldChar.set(qn('w:fldCharType'), 'begin')
    r.append(fldChar)

    run = paragraph.add_run()
    r = run._r
    instrText = OxmlElement('w:instrText')
    instrText.set(qn('xml:space'), 'preserve')
    instrText.text = field_code
    r.append(instrText)

    run = paragraph.add_run()
    r = run._r
    fldChar = OxmlElement('w:fldChar')
    fldChar.set(qn('w:fldCharType'), 'end')
    r.append(fldChar)
Beispiel #16
0
def export_to_word(vuln_info, template, output_file='openvas_report.docx'):
    """
    Export vulnerabilities info in a Word file.

    :param vuln_info: Vulnerability list info
    :type vuln_info: list(Vulnerability)

    :param output_file: Filename of the Excel file
    :type output_file: str
    
    :param template: Path to Docx template
    :type template: str

    :raises: TypeError
    """

    import matplotlib.pyplot as plt
    import numpy as np
    import tempfile
    import os

    from docx import Document
    from docx.oxml.shared import qn, OxmlElement
    from docx.oxml.ns import nsdecls
    from docx.oxml import parse_xml
    from docx.shared import Cm

    if not isinstance(vuln_info, list):
        raise TypeError("Expected list, got '{}' instead".format(
            type(vuln_info)))
    else:
        for x in vuln_info:
            if not isinstance(x, Vulnerability):
                raise TypeError(
                    "Expected Vulnerability, got '{}' instead".format(type(x)))
    if not isinstance(output_file, str):
        raise TypeError("Expected str, got '{}' instead".format(
            type(output_file)))
    else:
        if not output_file:
            raise ValueError("output_file must have a valid name.")
    if template is not None:
        if not isinstance(template, str):
            raise TypeError("Expected str, got '{}' instead".format(
                type(template)))
    else:
        template = 'openvasreporting/src/openvas-template.docx'

    vuln_info, vuln_levels, vuln_host_by_level, vuln_by_family = _get_collections(
        vuln_info)

    # ====================
    # DOCUMENT PROPERTIES
    # ====================
    document = Document(template)

    doc_prop = document.core_properties
    doc_prop.title = "OpenVAS Report"
    doc_prop.category = "Report"

    document.add_paragraph('OpenVAS Report', style='Title')

    # ====================
    # TABLE OF CONTENTS
    # ====================
    document.add_paragraph('Table of Contents', style='Heading 1')

    par = document.add_paragraph()
    run = par.add_run()
    fld_char = OxmlElement('w:fldChar')  # creates a new element
    fld_char.set(qn('w:fldCharType'), 'begin')  # sets attribute on element
    instr_text = OxmlElement('w:instrText')
    instr_text.set(qn('xml:space'), 'preserve')  # sets attribute on element
    instr_text.text = r'TOC \h \z \t "OV-H1toc;1;OV-H2toc;2;OV-H3toc;3;OV-Finding;3"'

    fld_char2 = OxmlElement('w:fldChar')
    fld_char2.set(qn('w:fldCharType'), 'separate')
    fld_char3 = OxmlElement('w:t')
    fld_char3.text = "# Right-click to update field. #"
    fld_char2.append(fld_char3)

    fld_char4 = OxmlElement('w:fldChar')
    fld_char4.set(qn('w:fldCharType'), 'end')

    r_element = run._r
    r_element.append(fld_char)
    r_element.append(instr_text)
    r_element.append(fld_char2)
    r_element.append(fld_char4)

    document.add_page_break()

    # ====================
    # MANAGEMENT SUMMARY
    # ====================
    document.add_paragraph('Management Summary', style='OV-H1toc')
    document.add_paragraph('< TYPE YOUR MANAGEMENT SUMMARY HERE >')
    document.add_page_break()

    # ====================
    # TECHNICAL FINDINGS
    # ====================
    document.add_paragraph('Technical Findings', style='OV-H1toc')
    document.add_paragraph(
        'The section below discusses the technical findings.')

    # --------------------
    # SUMMARY TABLE
    # --------------------
    document.add_paragraph('Summary', style='OV-H2toc')

    colors_sum = []
    labels_sum = []
    vuln_sum = []
    aff_sum = []

    table_summary = document.add_table(rows=1, cols=3)
    hdr_cells = table_summary.rows[0].cells
    hdr_cells[0].paragraphs[0].add_run('Risk level').bold = True
    hdr_cells[1].paragraphs[0].add_run('Vulns number').bold = True
    hdr_cells[2].paragraphs[0].add_run('Affected hosts').bold = True

    # Provide data to table and charts
    for level in Config.levels().values():
        row_cells = table_summary.add_row().cells
        row_cells[0].text = level.capitalize()
        row_cells[1].text = str(vuln_levels[level])
        row_cells[2].text = str(vuln_host_by_level[level])
        colors_sum.append(Config.colors()[level])
        labels_sum.append(level)
        vuln_sum.append(vuln_levels[level])
        aff_sum.append(vuln_host_by_level[level])

    # --------------------
    # CHART
    # --------------------
    fd, path = tempfile.mkstemp(suffix='.png')

    par_chart = document.add_paragraph()
    run_chart = par_chart.add_run()

    plt.figure()

    pos = np.arange(len(labels_sum))
    width = 0.35

    bars_vuln = plt.bar(pos - width / 2,
                        vuln_sum,
                        width,
                        align='center',
                        label='Vulnerabilities',
                        color=colors_sum,
                        edgecolor='black')
    bars_aff = plt.bar(pos + width / 2,
                       aff_sum,
                       width,
                       align='center',
                       label='Affected hosts',
                       color=colors_sum,
                       edgecolor='black',
                       hatch='//')
    plt.title('Vulnerability summary by risk level')
    plt.subplot().set_xticks(pos)
    plt.subplot().set_xticklabels(labels_sum)
    plt.gca().spines['left'].set_visible(False)
    plt.gca().spines['right'].set_visible(False)
    plt.gca().spines['top'].set_visible(False)
    plt.gca().spines['bottom'].set_position('zero')
    plt.tick_params(top=False,
                    bottom=True,
                    left=False,
                    right=False,
                    labelleft=False,
                    labelbottom=True)
    plt.subplots_adjust(left=0.0, right=1.0)

    def __label_bars(barcontainer):
        for bar in barcontainer:
            height = bar.get_height()
            plt.gca().text(bar.get_x() + bar.get_width() / 2,
                           bar.get_height() + 0.3,
                           str(int(height)),
                           ha='center',
                           color='black',
                           fontsize=9)

    __label_bars(bars_vuln)
    __label_bars(bars_aff)

    plt.legend()

    plt.savefig(path)

    # plt.show()  # DEBUG

    run_chart.add_picture(path, width=Cm(8.0))

    plt.figure()

    values = list(vuln_by_family.values())
    pie, tx, autotexts = plt.pie(values,
                                 labels=vuln_by_family.keys(),
                                 autopct='')
    plt.title('Vulnerability by family')
    for i, txt in enumerate(autotexts):
        txt.set_text('{}'.format(values[i]))
    plt.axis('equal')

    plt.savefig(
        path, bbox_inches='tight'
    )  # bbox_inches fixes labels being cut, however only on save not on show

    # plt.show()  # DEBUG

    run_chart.add_picture(path, width=Cm(8.0))
    os.close(fd)
    os.remove(path)

    # ====================
    # VULN PAGES
    # ====================
    cur_level = ""

    for i, vuln in enumerate(vuln_info, 1):
        # --------------------
        # GENERAL
        # --------------------
        level = vuln.level.lower()

        if level != cur_level:
            document.add_paragraph(
                level.capitalize(),
                style='OV-H2toc').paragraph_format.page_break_before = True
            cur_level = level
        else:
            document.add_page_break()

        title = "[{}] {}".format(level.upper(), vuln.name)
        document.add_paragraph(title, style='OV-Finding')

        table_vuln = document.add_table(rows=8, cols=3)
        table_vuln.autofit = False

        # COLOR
        # --------------------
        col_cells = table_vuln.columns[0].cells
        col_cells[0].merge(col_cells[7])
        color_fill = parse_xml(r'<w:shd {} w:fill="{}"/>'.format(
            nsdecls('w'),
            Config.colors()[vuln.level][1:]))
        col_cells[0]._tc.get_or_add_tcPr().append(color_fill)

        for col_cell in col_cells:
            col_cell.width = Cm(0.42)

        # TABLE HEADERS
        # --------------------
        hdr_cells = table_vuln.columns[1].cells
        hdr_cells[0].paragraphs[0].add_run('Description').bold = True
        hdr_cells[1].paragraphs[0].add_run('Impact').bold = True
        hdr_cells[2].paragraphs[0].add_run('Recommendation').bold = True
        hdr_cells[3].paragraphs[0].add_run('Details').bold = True
        hdr_cells[4].paragraphs[0].add_run('CVSS').bold = True
        hdr_cells[5].paragraphs[0].add_run('CVEs').bold = True
        hdr_cells[6].paragraphs[0].add_run('Family').bold = True
        hdr_cells[7].paragraphs[0].add_run('References').bold = True

        for hdr_cell in hdr_cells:
            hdr_cell.width = Cm(3.58)

        # FIELDS
        # --------------------
        cves = ", ".join(vuln.cves)
        cves = cves.upper() if cves != "" else "No CVE"

        cvss = str(vuln.cvss) if vuln.cvss != -1.0 else "No CVSS"

        txt_cells = table_vuln.columns[2].cells
        txt_cells[0].text = vuln.description
        txt_cells[1].text = vuln.impact
        txt_cells[2].text = vuln.solution
        txt_cells[3].text = vuln.insight
        txt_cells[4].text = cvss
        txt_cells[5].text = cves
        txt_cells[6].text = vuln.family
        txt_cells[7].text = vuln.references

        for txt_cell in txt_cells:
            txt_cell.width = Cm(12.50)

        # VULN HOSTS
        # --------------------
        document.add_paragraph('Vulnerable hosts', style='Heading 4')

        # add coloumn for result per port and resize columns
        table_hosts = document.add_table(cols=5, rows=(len(vuln.hosts) + 1))

        col_cells = table_hosts.columns[1].cells
        for col_cell in col_cells:
            col_cell.width = Cm(3.2)

        col_cells = table_hosts.columns[2].cells
        for col_cell in col_cells:
            col_cell.width = Cm(3.2)

        col_cells = table_hosts.columns[2].cells
        for col_cell in col_cells:
            col_cell.width = Cm(1.6)

        col_cells = table_hosts.columns[3].cells
        for col_cell in col_cells:
            col_cell.width = Cm(1.6)

        col_cells = table_hosts.columns[4].cells
        for col_cell in col_cells:
            col_cell.width = Cm(6.4)

        hdr_cells = table_hosts.rows[0].cells
        hdr_cells[0].paragraphs[0].add_run('IP').bold = True
        hdr_cells[1].paragraphs[0].add_run('Host name').bold = True
        hdr_cells[2].paragraphs[0].add_run('Port number').bold = True
        hdr_cells[3].paragraphs[0].add_run('Port protocol').bold = True
        hdr_cells[4].paragraphs[0].add_run('Port result').bold = True

        for j, (host, port) in enumerate(vuln.hosts, 1):
            cells = table_hosts.rows[j].cells
            cells[0].text = host.ip
            cells[1].text = host.host_name if host.host_name else "-"
            if port and port is not None:
                cells[2].text = "-" if port.number == 0 else str(port.number)
                cells[3].text = port.protocol
                cells[4].text = port.result
            else:
                cells[2].text = "No port info"

    document.save(output_file)
Beispiel #17
0
    def run(self, \
        args, \
        report_directory, \
        lookup, whois_result, \
        dns_result, \
        google_result, \
        shodan_result, \
        paste_scrape_result, \
        theharvester_result, \
        webscrape_result, \
        cred_result, \
        pyfoca_result):

        for l in lookup:

            print('[+] Starting OSINT report for '.format(l))

            self.document = docx.Document()
            #add logo
            self.document.add_picture('./resources/logo.png', height=Inches(1.25))

            #add domain cover info
            paragraph = self.document.add_paragraph()
            run_paragraph = paragraph.add_run('%s' % l)
            font = run_paragraph.font
            font.name = 'Arial'
            font.size = Pt(28)
            font.color.rgb = RGBColor(0x00, 0x00, 0x00)

            #add cover info
            paragraph = self.document.add_paragraph()
            run_paragraph = paragraph.add_run('Open Source Intelligence Report\n\n\n\n\n\n\n\n\n\n\n')
            font = run_paragraph.font
            font.name = 'Arial'
            font.size = Pt(26)
            font.color.rgb = RGBColor(0xe9, 0x58, 0x23)

            paragraph = self.document.add_paragraph()
            run_paragraph = paragraph.add_run('Generated on: %s' % self.today)
            font = run_paragraph.font
            font.name = 'Arial'
            font.size = Pt(16)
            font.color.rgb = RGBColor(0x00, 0x00, 0x00)

            #page break for cover page
            self.document.add_page_break()

            #add intro text on intropage
            heading = self.document.add_heading()
            run_heading = heading.add_run('Executive Summary')
            font = run_heading.font
            font.name = 'Arial'
            font.size = Pt(20)
            font.color.rgb = RGBColor(0xe9, 0x58, 0x23)

            paragraph = self.document.add_paragraph()
            run_paragraph = paragraph.add_run('\nThis document contains information about network, technology, and people associated with the assessment targets. The information was obtained by programatically querying various free or low cost Internet data sources.\n')
            font = run_paragraph.font
            font.name = 'Arial'
            font.size = Pt(11)
            run_paragraph = paragraph.add_run('\nThese data include information about the network, technology, and people associated with the targets.\n')
            font = run_paragraph.font
            font.name = 'Arial'
            font.size = Pt(11)
            run_paragraph = paragraph.add_run('\nSpecific data sources include: whois, domain name system (DNS) records, Google dork results, and data from recent compromises such as LinkedIn. Other sources include results from Shodan, document metadata from theHarvester and pyFoca, as well as queries to Pastebin, Github, job boards, etc. \n')
            font = run_paragraph.font
            font.name = 'Arial'
            font.size = Pt(11)

            #page break for cover page
            self.document.add_page_break()

            heading = self.document.add_heading()
            run_heading = heading.add_run('Table of Contents')
            font = run_heading.font
            font.bold = True
            font.name = 'Arial'
            font.size = Pt(20)
            font.color.rgb = RGBColor(0x0, 0x0, 0x0)

            #TOC https://github.com/python-openxml/python-docx/issues/36
            paragraph = self.document.add_paragraph()
            run = paragraph.add_run()
            font.name = 'Arial'
            font.size = Pt(11)
            fldChar = OxmlElement('w:fldChar')  # creates a new element
            fldChar.set(qn('w:fldCharType'), 'begin')  # sets attribute on element

            instrText = OxmlElement('w:instrText')
            instrText.set(qn('xml:space'), 'preserve')  # sets attribute on element
            instrText.text = 'TOC \o "1-3" \h \z \u'   # change 1-3 depending on heading levels you need

            fldChar2 = OxmlElement('w:fldChar')
            fldChar2.set(qn('w:fldCharType'), 'separate')
            fldChar3 = OxmlElement('w:t')
            fldChar3.text = "Right-click to update field."
            fldChar2.append(fldChar3)

            fldChar4 = OxmlElement('w:fldChar')
            fldChar4.set(qn('w:fldCharType'), 'end')

            r_element = run._r
            r_element.append(fldChar)
            r_element.append(instrText)
            r_element.append(fldChar2)
            r_element.append(fldChar4)
            p_element = paragraph._p

            #page break for toc
            self.document.add_page_break()

            if cred_result is not None:
                print('[+] Adding credential dump results to report')
                #header
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('Credentials found from recent compromises (LinkedIn, Adobe, etc.) related to: %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
                paragraph = self.document.add_paragraph()
                for c in cred_result:
                    run_paragraph = paragraph.add_run(''.join(c))
                    font = run_paragraph.font
                    font.name = 'Arial'
                    font.size = Pt(11)
                self.document.add_page_break()

            #add whois data with header and break after end
            if whois_result is not None:
                print('[+] Adding whois results to report')
                #header
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('Whois Data for: %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
                #content
                paragraph = self.document.add_paragraph()
                for line in whois_result:
                    if ':' in line:
                        run_paragraph = paragraph.add_run(''.join(line)+'\n')
                        font = run_paragraph.font
                        font.name = 'Arial'
                        font.size = Pt(10)
                self.document.add_page_break()

            #add dns data with header and break after end
            if dns_result is not None:
                print('[+] Adding DNS results to report')
                #header
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('Domain Name System Data for: %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
                #content
                paragraph = self.document.add_paragraph()
                for d in dns_result:
                    run_paragraph = paragraph.add_run('\n'.join(d))
                    font = run_paragraph.font
                    font.name = 'Arial'
                    font.size = Pt(10)
                self.document.add_page_break()

            #google dork output
            if google_result is not None:
                print('[+] Adding google dork results to report')
                #header
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('Google Dork Results for: %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
                #content
                paragraph = self.document.add_paragraph()
                for r in google_result:
                    run_paragraph = paragraph.add_run(''.join(r+'\n'))
                    font = run_paragraph.font
                    font.name = 'Arial'
                    font.size = Pt(10)
                self.document.add_page_break()

            #harvester output
            if theharvester_result is not None:
                print('[+] Adding theHarvester results to report')
                #header
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('theHarvester Results for: %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
                #content
                paragraph = self.document.add_paragraph()
                for h in theharvester_result:
                    run_paragraph = paragraph.add_run(''.join(h))
                    #set font stuff
                    font = run_paragraph.font
                    font.name = 'Arial'
                    font.size = Pt(10)
                self.document.add_page_break()

            #pastebin scrape output
            if paste_scrape_result is not None:
                print('[+] Adding pastebin scrape results to report')
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('Pastebin URLs for %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)

                paragraph = self.document.add_paragraph()
                self.document.add_paragraph(paste_scrape_result)
                font = run_paragraph.font
                font.name = 'Arial'
                font.size = Pt(10)
                self.document.add_page_break()

            #general scrape output
            if webscrape_result is not None:
                print('[+] Adding website scraping results to report')
                #header
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('Website Scraping Results for %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
                #content
                paragraph = self.document.add_paragraph()
                for sr in webscrape_result:
                    for line in sr:
                        run_paragraph = paragraph.add_run(line)
                        font = run_paragraph.font
                        font.name = 'Arial'
                        font.size = Pt(10)

                self.document.add_page_break()

            #pyfoca results
            if pyfoca_result is not None:
                print('[+] Adding pyfoca results to report')
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('pyFoca Results for: %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)

                paragraph = self.document.add_paragraph()
                for fr in pyfoca_result:
                    run_paragraph = paragraph.add_run(''.join(str(fr).strip(("\\ba\x00b\n\rc\fd\xc3"))))
                    font = run_paragraph.font
                    font.name = 'Arial'
                    font.size = Pt(10)

                self.document.add_page_break()

            #shodan output
            if shodan_result is not None:
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('Shodan Results for: %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)

                paragraph = self.document.add_paragraph()
                for shr in shodan_result:
                    try:
                        run_paragraph = paragraph.add_run(str(shr).strip(("\\ba\x00b\n\rc\fd\xc3")))
                        #set font stuff
                        font = run_paragraph.font
                        font.name = 'Arial'
                        font.size = Pt(10)
                    except:
                        print ('probably an encoding error...')
                        continue

            print('[+] Writing file: ./reports/{}/OSINT_{}_.docx'.format(l, l))

            #saves to ./reports/domain.com/OSINT_domain.com_.docx
            self.document.save(report_directory+'{}/OSINT_{}_.docx'.format(l, l))
Beispiel #18
0
def write_report(args, reportDir, lookup, whoisResult, dnsResult, googleResult,
                 shodanResult, pasteScrapeResult, harvesterResult,
                 scrapeResult, credResult, pyfocaResult):

    today = time.strftime("%m/%d/%Y")
    for l in lookup:
        print '[+] Starting OSINT report for ' + l

        #dump to a word doc
        #refs
        #https://python-docx.readthedocs.io/en/latest/user/text.html
        #https://python-docx.readthedocs.io/en/latest/user/quickstart.html

        #create a document
        document = docx.Document()

        #add logo
        document.add_picture('./resources/logo.png', height=Inches(1.25))

        #add domain cover info

        paragraph = document.add_paragraph()
        runParagraph = paragraph.add_run('%s' % l)
        font = runParagraph.font
        font.name = 'Arial'
        font.size = Pt(28)
        font.color.rgb = RGBColor(0x00, 0x00, 0x00)

        #add cover info
        paragraph = document.add_paragraph()
        runParagraph = paragraph.add_run(
            'Open Source Intelligence Report\n\n\n\n\n\n\n\n\n\n\n')
        font = runParagraph.font
        font.name = 'Arial'
        font.size = Pt(26)
        font.color.rgb = RGBColor(0xe9, 0x58, 0x23)

        paragraph = document.add_paragraph()
        runParagraph = paragraph.add_run('Generated on: %s' % today)
        font = runParagraph.font
        font.name = 'Arial'
        font.size = Pt(16)
        font.color.rgb = RGBColor(0x00, 0x00, 0x00)

        #page break for cover page
        document.add_page_break()

        #add intro text on intropage

        heading = document.add_heading()
        runHeading = heading.add_run('Executive Summary')
        font = runHeading.font
        font.name = 'Arial'
        font.size = Pt(20)
        font.color.rgb = RGBColor(0xe9, 0x58, 0x23)

        paragraph = document.add_paragraph()
        runParagraph = paragraph.add_run(
            '\nThis document contains information about network, technology, and people associated with the assessment targets. The information was obtained by programatically querying various free or low cost Internet data sources.\n'
        )
        font = runParagraph.font
        font.name = 'Arial'
        font.size = Pt(11)
        runParagraph = paragraph.add_run(
            '\nThese data include information about the network, technology, and people associated with the targets.\n'
        )
        font = runParagraph.font
        font.name = 'Arial'
        font.size = Pt(11)
        runParagraph = paragraph.add_run(
            '\nSpecific data sources include: whois, domain name system (DNS) records, Google dork results, and data from recent compromises such as LinkedIn. Other sources include results from Shodan, document metadata from theHarvester and pyFoca, as well as queries to Pastebin, Github, job boards, etc. \n'
        )
        font = runParagraph.font
        font.name = 'Arial'
        font.size = Pt(11)

        #page break for cover page
        document.add_page_break()

        heading = document.add_heading()
        runHeading = heading.add_run('Table of Contents')
        font = runHeading.font
        font.bold = True
        font.name = 'Arial'
        font.size = Pt(20)
        font.color.rgb = RGBColor(0x0, 0x0, 0x0)

        #TOC https://github.com/python-openxml/python-docx/issues/36
        paragraph = document.add_paragraph()
        run = paragraph.add_run()
        font.name = 'Arial'
        font.size = Pt(11)
        fldChar = OxmlElement('w:fldChar')  # creates a new element
        fldChar.set(qn('w:fldCharType'), 'begin')  # sets attribute on element

        instrText = OxmlElement('w:instrText')
        instrText.set(qn('xml:space'), 'preserve')  # sets attribute on element
        instrText.text = 'TOC \o "1-3" \h \z \u'  # change 1-3 depending on heading levels you need

        fldChar2 = OxmlElement('w:fldChar')
        fldChar2.set(qn('w:fldCharType'), 'separate')
        fldChar3 = OxmlElement('w:t')
        fldChar3.text = "Right-click to update field."
        fldChar2.append(fldChar3)

        fldChar4 = OxmlElement('w:fldChar')
        fldChar4.set(qn('w:fldCharType'), 'end')

        r_element = run._r
        r_element.append(fldChar)
        r_element.append(instrText)
        r_element.append(fldChar2)
        r_element.append(fldChar4)
        p_element = paragraph._p

        #page break for toc
        document.add_page_break()

        if credResult:
            print '[+] Adding credential dump results to report'
            #header
            heading = document.add_heading(level=3)
            runHeading = heading.add_run(
                'Credentials found from recent compromises (LinkedIn, Adobe, etc.) related to: %s'
                % l)
            font = runHeading.font
            font.name = 'Arial'
            font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
            paragraph = document.add_paragraph()
            for c in credResult:
                runParagraph = paragraph.add_run(''.join(c))
                font = runParagraph.font
                font.name = 'Arial'
                font.size = Pt(11)
            document.add_page_break()

        #add whois data with header and break after end
        if whoisResult:
            print '[+] Adding whois results to report'
            #header
            heading = document.add_heading(level=3)
            runHeading = heading.add_run('Whois Data for: %s' % l)
            font = runHeading.font
            font.name = 'Arial'
            font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
            #content
            paragraph = document.add_paragraph()
            for w in whoisResult:
                runParagraph = paragraph.add_run('\n'.join(w))
                font = runParagraph.font
                font.name = 'Arial'
                font.size = Pt(10)
            document.add_page_break()

        #add dns data with header and break after end
        if dnsResult:
            print '[+] Adding DNS results to report'
            #header
            heading = document.add_heading(level=3)
            runHeading = heading.add_run('Domain Name System Data for: %s' % l)
            font = runHeading.font
            font.name = 'Arial'
            font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
            #content
            paragraph = document.add_paragraph()
            for d in dnsResult:
                runParagraph = paragraph.add_run('\n'.join(d))
                font = runParagraph.font
                font.name = 'Arial'
                font.size = Pt(10)
            document.add_page_break()

        #google dork output
        if googleResult:
            print '[+] Adding google dork results to report'
            #header
            heading = document.add_heading(level=3)
            runHeading = heading.add_run('Google Dork Results for: %s' % l)
            font = runHeading.font
            font.name = 'Arial'
            font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
            #content
            paragraph = document.add_paragraph()
            for r in googleResult:
                runParagraph = paragraph.add_run(''.join(r + '\n'))
                font = runParagraph.font
                font.name = 'Arial'
                font.size = Pt(10)
            document.add_page_break()

        #harvester output
        if harvesterResult:
            print '[+] Adding theHarvester results to report'
            #header
            heading = document.add_heading(level=3)
            runHeading = heading.add_run('theHarvester Results for: %s' % l)
            font = runHeading.font
            font.name = 'Arial'
            font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
            #content
            paragraph = document.add_paragraph()
            for h in harvesterResult:
                runParagraph = paragraph.add_run(''.join(h))
                #set font stuff
                font = runParagraph.font
                font.name = 'Arial'
                font.size = Pt(10)
            document.add_page_break()

        #pastebin scrape output
        if pasteScrapeResult:
            print '[+] Adding pastebin scrape results to report'
            document.add_heading('Pastebin URLs for %s' % l, level=3)
            document.add_paragraph(pasteScrapeResult)
            document.add_page_break()
            #document.add_paragraph(pasteScrapeContent)
            #document.add_page_break()

        #general scrape output
        if scrapeResult:
            print '[+] Adding website scraping results to report'
            #header
            heading = document.add_heading(level=3)
            runHeading = heading.add_run('Website Scraping Results for %s' % l)
            font = runHeading.font
            font.name = 'Arial'
            font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
            #content
            paragraph = document.add_paragraph()
            for sr in scrapeResult:
                runParagraph = paragraph.add_run(sr)
                font = runParagraph.font
                font.name = 'Arial'
                font.size = Pt(10)

            document.add_page_break()

        #pyfoca results
        if pyfocaResult:
            print '[+] Adding pyfoca results to report'
            heading = document.add_heading(level=3)
            runHeading = heading.add_run('pyFoca Results for: %s' % l)
            font = runHeading.font
            font.name = 'Arial'
            font.color.rgb = RGBColor(0xe9, 0x58, 0x23)

            paragraph = document.add_paragraph()
            for fr in pyfocaResult:
                #lolwut
                runParagraph = paragraph.add_run(''.join(
                    str(fr).strip(("\\ba\x00b\n\rc\fd\xc3"))))
                font = runParagraph.font
                font.name = 'Arial'
                font.size = Pt(10)

            document.add_page_break()

        #shodan output
        if shodanResult:
            heading = document.add_heading(level=3)
            runHeading = heading.add_run('Shodan Results for: %s' % l)
            font = runHeading.font
            font.name = 'Arial'
            font.color.rgb = RGBColor(0xe9, 0x58, 0x23)

            paragraph = document.add_paragraph()
            for shr in shodanResult:
                try:
                    runParagraph = paragraph.add_run(
                        str(shr).strip(("\\ba\x00b\n\rc\fd\xc3")))
                    #set font stuff
                    font = runParagraph.font
                    font.name = 'Arial'
                    font.size = Pt(10)
                except:
                    print 'probably an encoding error...'
                    continue

        print '[+] Writing file: ./reports/%s/OSINT_%s_.docx' % (l, l)
        document.save(reportDir + l + '/' + l + 'OSINT_%s_.docx' % l)
Beispiel #19
0
    def run(self, \
        args, \
        report_directory, \
        lookup, whois_result, \
        dns_result, \
        google_result, \
        shodan_result, \
        paste_scrape_result, \
        theharvester_result, \
        webscrape_result, \
        cred_result, \
        pyfoca_result):

        for l in lookup:

            print('[+] Starting OSINT report for '.format(l))

            self.document = docx.Document()
            #add logo
            self.document.add_picture('./resources/logo.png',
                                      height=Inches(1.25))

            #add domain cover info
            paragraph = self.document.add_paragraph()
            run_paragraph = paragraph.add_run('%s' % l)
            font = run_paragraph.font
            font.name = 'Arial'
            font.size = Pt(28)
            font.color.rgb = RGBColor(0x00, 0x00, 0x00)

            #add cover info
            paragraph = self.document.add_paragraph()
            run_paragraph = paragraph.add_run(
                'Open Source Intelligence Report\n\n\n\n\n\n\n\n\n\n\n')
            font = run_paragraph.font
            font.name = 'Arial'
            font.size = Pt(26)
            font.color.rgb = RGBColor(0xe9, 0x58, 0x23)

            paragraph = self.document.add_paragraph()
            run_paragraph = paragraph.add_run('Generated on: %s' % self.today)
            font = run_paragraph.font
            font.name = 'Arial'
            font.size = Pt(16)
            font.color.rgb = RGBColor(0x00, 0x00, 0x00)

            #page break for cover page
            self.document.add_page_break()

            #add intro text on intropage
            heading = self.document.add_heading()
            run_heading = heading.add_run('Executive Summary')
            font = run_heading.font
            font.name = 'Arial'
            font.size = Pt(20)
            font.color.rgb = RGBColor(0xe9, 0x58, 0x23)

            paragraph = self.document.add_paragraph()
            run_paragraph = paragraph.add_run(
                '\nThis document contains information about network, technology, and people associated with the assessment targets. The information was obtained by programatically querying various free or low cost Internet data sources.\n'
            )
            font = run_paragraph.font
            font.name = 'Arial'
            font.size = Pt(11)
            run_paragraph = paragraph.add_run(
                '\nThese data include information about the network, technology, and people associated with the targets.\n'
            )
            font = run_paragraph.font
            font.name = 'Arial'
            font.size = Pt(11)
            run_paragraph = paragraph.add_run(
                '\nSpecific data sources include: whois, domain name system (DNS) records, Google dork results, and data from recent compromises such as LinkedIn. Other sources include results from Shodan, document metadata from theHarvester and pyFoca, as well as queries to Pastebin, Github, job boards, etc. \n'
            )
            font = run_paragraph.font
            font.name = 'Arial'
            font.size = Pt(11)

            #page break for cover page
            self.document.add_page_break()

            heading = self.document.add_heading()
            run_heading = heading.add_run('Table of Contents')
            font = run_heading.font
            font.bold = True
            font.name = 'Arial'
            font.size = Pt(20)
            font.color.rgb = RGBColor(0x0, 0x0, 0x0)

            #TOC https://github.com/python-openxml/python-docx/issues/36
            paragraph = self.document.add_paragraph()
            run = paragraph.add_run()
            font.name = 'Arial'
            font.size = Pt(11)
            fldChar = OxmlElement('w:fldChar')  # creates a new element
            fldChar.set(qn('w:fldCharType'),
                        'begin')  # sets attribute on element

            instrText = OxmlElement('w:instrText')
            instrText.set(qn('xml:space'),
                          'preserve')  # sets attribute on element
            instrText.text = 'TOC \o "1-3" \h \z \u'  # change 1-3 depending on heading levels you need

            fldChar2 = OxmlElement('w:fldChar')
            fldChar2.set(qn('w:fldCharType'), 'separate')
            fldChar3 = OxmlElement('w:t')
            fldChar3.text = "Right-click to update field."
            fldChar2.append(fldChar3)

            fldChar4 = OxmlElement('w:fldChar')
            fldChar4.set(qn('w:fldCharType'), 'end')

            r_element = run._r
            r_element.append(fldChar)
            r_element.append(instrText)
            r_element.append(fldChar2)
            r_element.append(fldChar4)
            p_element = paragraph._p

            #page break for toc
            self.document.add_page_break()

            if cred_result is not None:
                print('[+] Adding credential dump results to report')
                #header
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run(
                    'Credentials found from recent compromises (LinkedIn, Adobe, etc.) related to: %s'
                    % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
                paragraph = self.document.add_paragraph()
                for c in cred_result:
                    run_paragraph = paragraph.add_run(''.join(c))
                    font = run_paragraph.font
                    font.name = 'Arial'
                    font.size = Pt(11)
                self.document.add_page_break()

            #add whois data with header and break after end
            if whois_result is not None:
                print('[+] Adding whois results to report')
                #header
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('Whois Data for: %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
                #content
                paragraph = self.document.add_paragraph()
                for line in whois_result:
                    if ':' in line:
                        run_paragraph = paragraph.add_run(''.join(line) + '\n')
                        font = run_paragraph.font
                        font.name = 'Arial'
                        font.size = Pt(10)
                self.document.add_page_break()

            #add dns data with header and break after end
            if dns_result is not None:
                print('[+] Adding DNS results to report')
                #header
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run(
                    'Domain Name System Data for: %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
                #content
                paragraph = self.document.add_paragraph()
                for d in dns_result:
                    run_paragraph = paragraph.add_run('\n'.join(d))
                    font = run_paragraph.font
                    font.name = 'Arial'
                    font.size = Pt(10)
                self.document.add_page_break()

            #google dork output
            if google_result is not None:
                print('[+] Adding google dork results to report')
                #header
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('Google Dork Results for: %s' %
                                              l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
                #content
                paragraph = self.document.add_paragraph()
                for r in google_result:
                    run_paragraph = paragraph.add_run(''.join(r + '\n'))
                    font = run_paragraph.font
                    font.name = 'Arial'
                    font.size = Pt(10)
                self.document.add_page_break()

            #harvester output
            if theharvester_result is not None:
                print('[+] Adding theHarvester results to report')
                #header
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('theHarvester Results for: %s' %
                                              l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
                #content
                paragraph = self.document.add_paragraph()
                for h in theharvester_result:
                    run_paragraph = paragraph.add_run(''.join(h))
                    #set font stuff
                    font = run_paragraph.font
                    font.name = 'Arial'
                    font.size = Pt(10)
                self.document.add_page_break()

            #pastebin scrape output
            if paste_scrape_result is not None:
                print('[+] Adding pastebin scrape results to report')
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('Pastebin URLs for %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)

                paragraph = self.document.add_paragraph()
                self.document.add_paragraph(paste_scrape_result)
                font = run_paragraph.font
                font.name = 'Arial'
                font.size = Pt(10)
                self.document.add_page_break()

            #general scrape output
            if webscrape_result is not None:
                print('[+] Adding website scraping results to report')
                #header
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run(
                    'Website Scraping Results for %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)
                #content
                paragraph = self.document.add_paragraph()
                for sr in webscrape_result:
                    for line in sr:
                        run_paragraph = paragraph.add_run(line)
                        font = run_paragraph.font
                        font.name = 'Arial'
                        font.size = Pt(10)

                self.document.add_page_break()

            #pyfoca results
            if pyfoca_result is not None:
                print('[+] Adding pyfoca results to report')
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('pyFoca Results for: %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)

                paragraph = self.document.add_paragraph()
                for fr in pyfoca_result:
                    run_paragraph = paragraph.add_run(''.join(
                        str(fr).strip(("\\ba\x00b\n\rc\fd\xc3"))))
                    font = run_paragraph.font
                    font.name = 'Arial'
                    font.size = Pt(10)

                self.document.add_page_break()

            #shodan output
            if shodan_result is not None:
                heading = self.document.add_heading(level=3)
                run_heading = heading.add_run('Shodan Results for: %s' % l)
                font = run_heading.font
                font.name = 'Arial'
                font.color.rgb = RGBColor(0xe9, 0x58, 0x23)

                paragraph = self.document.add_paragraph()
                for shr in shodan_result:
                    try:
                        run_paragraph = paragraph.add_run(
                            str(shr).strip(("\\ba\x00b\n\rc\fd\xc3")))
                        #set font stuff
                        font = run_paragraph.font
                        font.name = 'Arial'
                        font.size = Pt(10)
                    except:
                        print('probably an encoding error...')
                        continue

            print('[+] Writing file: ./reports/{}/OSINT_{}_.docx'.format(l, l))

            #saves to ./reports/domain.com/OSINT_domain.com_.docx
            self.document.save(report_directory +
                               '{}/OSINT_{}_.docx'.format(l, l))
Beispiel #20
0
def export_to_word(vuln_info, template, output_file='openvas_report.docx'):
    """
    Export vulnerabilities info in a Word file.

    :param vuln_info: Vulnerability list info
    :type vuln_info: list(Vulnerability)

    :param output_file: Filename of the Excel file
    :type output_file: str
    
    :param template: Path to Docx template
    :type template: str

    :raises: TypeError
    """

    import matplotlib.pyplot as plt
    import numpy as np
    import tempfile
    import os
    import math

    from docx import Document
    from docx.oxml.shared import qn, OxmlElement
    from docx.oxml.ns import nsdecls
    from docx.oxml import parse_xml
    from docx.shared import Cm, Pt, Twips
    from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
    from docx.enum.section import WD_ORIENT
    from docx.enum.style import WD_STYLE_TYPE
    from docx.enum.table import WD_ALIGN_VERTICAL
    from docx.shared import RGBColor

    if not isinstance(vuln_info, list):
        raise TypeError("Expected list, got '{}' instead".format(
            type(vuln_info)))
    else:
        for x in vuln_info:
            if not isinstance(x, Vulnerability):
                raise TypeError(
                    "Expected Vulnerability, got '{}' instead".format(type(x)))
    if not isinstance(output_file, str):
        raise TypeError("Expected str, got '{}' instead".format(
            type(output_file)))
    else:
        if not output_file:
            raise ValueError("output_file must have a valid name.")
    if template is not None:
        if not isinstance(template, str):
            raise TypeError("Expected str, got '{}' instead".format(
                type(template)))

    vuln_info, vuln_levels, vuln_host_by_level, vuln_by_family = _get_collections(
        vuln_info)

    # ====================
    # DOCUMENT PROPERTIES
    # ====================
    # Create new doc
    if template is None:
        document = Document()
        doc_section = document.sections[0]
        # Set A4 Format
        doc_section.page_width = Cm(21.0)
        doc_section.page_height = Cm(29.7)
        # Shrink margins almost to 0
        doc_section.left_margin = Cm(1.5)
        doc_section.right_margin = doc_section.left_margin
        doc_section.top_margin = Cm(1.0)
        doc_section.bottom_margin = Cm(1.0)
        # Force portrait
        doc_section.orientation = WD_ORIENT.PORTRAIT
    # use template
    else:
        document = Document(template)
        doc_section = document.sections[0]

    # Defining styles (if not defined already)
    # All used style will be custom, and with 'OR-' prefix.
    # In this way, the template can still define styles.
    doc_styles = document.styles

    # Base paragraph
    if 'OR-base' not in doc_styles:
        style_pr_base = doc_styles.add_style('OR-base',
                                             WD_STYLE_TYPE.PARAGRAPH)
        style_pr_base.font.name = 'Ubuntu'
        style_pr_base.font.size = Pt(8)
        style_pr_base.font.color.rgb = RGBColor.from_string('080808')
        style_pr_base.paragraph_format.left_indent = Cm(0)
        style_pr_base.paragraph_format.right_indent = Cm(0)
        style_pr_base.paragraph_format.first_line_indent = Cm(0)
        style_pr_base.paragraph_format.space_before = Cm(0)
        style_pr_base.paragraph_format.space_after = Cm(0)
        style_pr_base.paragraph_format.line_spacing_rule = WD_LINE_SPACING.SINGLE
        style_pr_base.paragraph_format.widow_control = True
        style_pr_base.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY_LOW
    # Base Styles modification
    if 'OR-base_bold' not in doc_styles:
        style_pr_body = doc_styles.add_style('OR-base_bold',
                                             WD_STYLE_TYPE.PARAGRAPH)
        style_pr_body.base_style = doc_styles['OR-base']
        style_pr_body.font.bold = True
    # Section headers
    if 'OR-Heading_base' not in doc_styles:
        style_pr_or_head_base = doc_styles.add_style('OR-Heading_base',
                                                     WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_head_base.base_style = doc_styles['OR-base_bold']
        style_pr_or_head_base.font.color.rgb = RGBColor.from_string('183868')
        style_pr_or_head_base.paragraph_format.space_after = Pt(4)
    # - Titles
    if 'OR-Title' not in doc_styles:
        style_pr_or_title = doc_styles.add_style('OR-Title',
                                                 WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_title.base_style = doc_styles['OR-Heading_base']
        style_pr_or_title.font.size = Pt(36)
        style_pr_or_title.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
        style_pr_or_head_base.paragraph_format.space_after = Pt(8)
    # - Headers
    if 'OR-Heading_1' not in doc_styles:
        style_pr_or_header = doc_styles.add_style('OR-Heading_1',
                                                  WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_header.base_style = doc_styles['OR-Heading_base']
        style_pr_or_header.font.size = Pt(20)
    if 'OR-Heading_2' not in doc_styles:
        style_pr_or_header = doc_styles.add_style('OR-Heading_2',
                                                  WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_header.base_style = doc_styles['OR-Heading_base']
        style_pr_or_header.font.size = Pt(16)
    if 'OR-Heading_3' not in doc_styles:
        style_pr_or_header = doc_styles.add_style('OR-Vuln_title',
                                                  WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_header.base_style = doc_styles['OR-Heading_base']
        style_pr_or_header.font.size = Pt(12)
    # - Vulnerabilities Titles
    for name, rgb in Config.colors().items():
        if 'OR-Vuln_title_' + name not in doc_styles:
            style_pr_or_header = doc_styles.add_style('OR-Vuln_title_' + name,
                                                      WD_STYLE_TYPE.PARAGRAPH)
            style_pr_or_header.base_style = doc_styles['OR-Vuln_title']
            style_pr_or_header.font.color.rgb = RGBColor.from_string(rgb[1:])
    # - Host with vulnerabilities title
    if 'OR-Vuln_hosts' not in doc_styles:
        style_pr_or_header = doc_styles.add_style('OR-Vuln_hosts',
                                                  WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_header.base_style = doc_styles['OR-Heading_base']
        style_pr_or_header.font.size = Pt(10)
    # TOC specific
    if 'OR-TOC_base' not in doc_styles:
        style_pr_or_toc_base = doc_styles.add_style('OR-TOC_base',
                                                    WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_toc_base.base_style = doc_styles['OR-base']
        style_pr_or_toc_base.font.color.rgb = RGBColor.from_string('183868')
    if 'OR-TOC_1' not in doc_styles:
        style_pr_or_toc = doc_styles.add_style('OR-TOC_1',
                                               WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_toc.base_style = doc_styles['OR-TOC_base']
        style_pr_or_toc.font.bold = True
    if 'OR-TOC_2' not in doc_styles:
        style_pr_or_toc = doc_styles.add_style('OR-TOC_2',
                                               WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_toc.base_style = doc_styles['OR-TOC_base']
    if 'OR-TOC_3' not in doc_styles:
        style_pr_or_toc = doc_styles.add_style('OR-Toc_3',
                                               WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_toc.base_style = doc_styles['OR-TOC_base']
    if 'OR-TOC_4' not in doc_styles:
        style_pr_or_toc = doc_styles.add_style('OR-TOC_4',
                                               WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_toc.base_style = doc_styles['OR-TOC_base']
        style_pr_or_toc.font.italic = True
    # Tables style
    # - Specific paragraph style to allow space before and after
    if 'OR-cell' not in doc_styles:
        style_pr_cell = doc_styles.add_style('OR-cell',
                                             WD_STYLE_TYPE.PARAGRAPH)
        style_pr_cell.base_style = doc_styles['OR-base']
        style_pr_cell.paragraph_format.space_before = Pt(1.5)
        style_pr_cell.paragraph_format.space_after = Pt(1.5)
    if 'OR-cell_bold' not in doc_styles:
        style_pr_cell = doc_styles.add_style('OR-cell_bold',
                                             WD_STYLE_TYPE.PARAGRAPH)
        style_pr_cell.base_style = doc_styles['OR-cell']
        style_pr_cell.font.bold = True

    # Clear all contents
    document._body.clear_content()

    # Set doc title
    doc_prop = document.core_properties
    doc_title = 'OpenVAS/GreenBone Report'
    doc_prop.title = doc_title
    doc_prop.category = "Report"

    # Effective writeable width
    # If margins set are float, try to fix (issue in python-docx: expected an int)
    # In this case, they _should be_ in twentieths of a point, so
    # multiply Twips helper
    try:
        doc_section.left_margin
    except ValueError as e:
        fixed_margin = float(re.search(": '(.+?)'", str(e)).group(1))
        doc_section.left_margin = Twips(fixed_margin)
    try:
        doc_section.right_margin
    except ValueError as e:
        fixed_margin = float(re.search(": '(.+?)'", str(e)).group(1))
        doc_section.right_margin = Twips(fixed_margin)

    page_width = doc_section.page_width - (doc_section.left_margin +
                                           doc_section.right_margin)

    ## Start actual document writing ##
    document.add_paragraph(doc_title, style='OR-Title')

    # ====================
    # TABLE OF CONTENTS
    # ====================
    # WARNING -Not working with LibreOffice
    document.add_paragraph('Table of Contents', style='OR-Heading_1')
    # keep the title as cover of the report
    document.add_page_break()

    par = document.add_paragraph(style='OR-base')
    run = par.add_run()
    fld_char = OxmlElement('w:fldChar')  # creates a new element
    fld_char.set(qn('w:fldCharType'), 'begin')  # sets attribute on element
    instr_text = OxmlElement('w:instrText')
    instr_text.set(qn('xml:space'), 'preserve')  # sets attribute on element
    instr_text.text = r'TOC \h \z \t "OR-TOC_1;1;OR-OR-TOC_1;2;OR-TOC_3;3;OR-TOC_4;3"'

    fld_char2 = OxmlElement('w:fldChar')
    fld_char2.set(qn('w:fldCharType'), 'separate')
    fld_char3 = OxmlElement('w:t')
    fld_char3.text = "# Right-click to update field. #"
    fld_char2.append(fld_char3)

    fld_char4 = OxmlElement('w:fldChar')
    fld_char4.set(qn('w:fldCharType'), 'end')

    r_element = run._r
    r_element.append(fld_char)
    r_element.append(instr_text)
    r_element.append(fld_char2)
    r_element.append(fld_char4)

    document.add_page_break()

    # ====================
    # MANAGEMENT SUMMARY
    # ====================
    document.add_paragraph('Management Summary', style='OR-Heading_1')
    document.add_paragraph('< TYPE YOUR MANAGEMENT SUMMARY HERE >',
                           style='OR-base')
    document.add_page_break()

    # ====================
    # TECHNICAL FINDINGS
    # ====================
    document.add_paragraph('Technical Findings', style='OR-Heading_1')
    document.add_paragraph(
        'The section below discusses the technical findings.', style='OR-base')

    # --------------------
    # SUMMARY TABLE
    # --------------------
    document.add_paragraph('Summary', style='OR-Heading_2')

    colors_sum = []
    labels_sum = []
    vuln_sum = []
    aff_sum = []

    table_summary = document.add_table(rows=1, cols=3)

    # TABLE HEADERS
    # --------------------
    hdr_cells = table_summary.rows[0].cells
    hdr_cells[0].paragraphs[0].add_run('Risk level')
    hdr_cells[1].paragraphs[0].add_run('Vulns number')
    hdr_cells[2].paragraphs[0].add_run('Affected hosts')
    # FIELDS
    # --------------------
    # Provide data to table and charts
    for level in Config.levels().values():
        row_cells = table_summary.add_row().cells
        row_cells[0].text = level.capitalize()
        row_cells[1].text = str(vuln_levels[level])
        row_cells[2].text = str(vuln_host_by_level[level])
        colors_sum.append(Config.colors()[level])
        labels_sum.append(level)
        vuln_sum.append(vuln_levels[level])
        aff_sum.append(vuln_host_by_level[level])

    # Apply styles
    # --------------------
    for h in hdr_cells:
        h.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
        for p in h.paragraphs:
            p.style = doc_styles['OR-cell_bold']

    for r in table_summary.rows[1:]:
        for c in r.cells:
            c.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
            for p in c.paragraphs:
                p.style = doc_styles['OR-cell']

    # --------------------
    # CHART
    # --------------------
    fd, path = tempfile.mkstemp(suffix='.png')
    chart_dpi = 144
    chart_height = Cm(8)
    par_chart = document.add_paragraph(style='OR-base')
    par_chart.alignment = WD_ALIGN_PARAGRAPH.CENTER
    run_chart = par_chart.add_run()

    bar_chart, bar_axis = plt.subplots(dpi=chart_dpi)
    bar_axis.set_title('Vulnerability summary by risk level', fontsize=10)

    pos = np.arange(len(labels_sum))
    width = 0.35

    bar_axis.set_xticks(pos)
    bar_axis.set_xticklabels(labels_sum)
    bar_chart.gca().spines['left'].set_visible(False)
    bar_chart.gca().spines['right'].set_visible(False)
    bar_chart.gca().spines['top'].set_visible(False)
    bar_chart.gca().spines['bottom'].set_position('zero')
    bar_axis.tick_params(top=False,
                         bottom=True,
                         left=False,
                         right=False,
                         labelleft=False,
                         labelbottom=True)
    bars_vuln = plt.bar(pos - width / 2,
                        vuln_sum,
                        width,
                        align='center',
                        label='Vulnerabilities',
                        color=colors_sum,
                        edgecolor='black')
    bars_aff = plt.bar(pos + width / 2,
                       aff_sum,
                       width,
                       align='center',
                       label='Affected hosts',
                       color=colors_sum,
                       edgecolor='black',
                       hatch='//')
    for barcontainer in (bars_vuln, bars_aff):
        for bar in barcontainer:
            height = bar.get_height()
            bar_chart.gca().text(bar.get_x() + bar.get_width() / 2,
                                 bar.get_height() + 0.3,
                                 str(int(height)),
                                 ha='center',
                                 color='black',
                                 fontsize=8)
    bar_chart.legend()

    bar_chart.savefig(path)

    # plt.show()  # DEBUG

    bar_height = chart_height
    run_chart.add_picture(path, height=bar_height)
    os.remove(path)

    pie_chart, pie_axis = plt.subplots(dpi=chart_dpi,
                                       subplot_kw=dict(aspect="equal"))
    pie_axis.set_title('Vulnerability by family', fontsize=10)

    values = list(vuln_by_family.values())
    pie, tx, autotexts = pie_axis.pie(values,
                                      labels=vuln_by_family.keys(),
                                      autopct='',
                                      textprops=dict(fontsize=8))
    for i, txt in enumerate(autotexts):
        txt.set_text('{}'.format(values[i]))
    pie_chart.savefig(path)

    # plt.show()  # DEBUG
    pie_height = chart_height
    run_chart.add_picture(path, height=pie_height)
    os.remove(path)

    # ====================
    # VULN PAGES
    # ====================
    cur_level = ""

    for i, vuln in enumerate(vuln_info, 1):
        # --------------------
        # GENERAL
        # --------------------
        level = vuln.level.lower()

        if level != cur_level:
            document.add_paragraph(
                level.capitalize(),
                style='OR-Heading_2').paragraph_format.page_break_before = True
            cur_level = level
        else:
            document.add_page_break()

        title = "[{}] {}".format(level.upper(), vuln.name)
        par = document.add_paragraph(title,
                                     style='OR-Vuln_title_' + vuln.level)

        table_vuln = document.add_table(rows=8, cols=3)
        table_vuln.autofit = False
        table_vuln.columns[0].width = Cm(0.35)
        table_vuln.columns[1].width = Cm(2.85)
        table_vuln.columns[-1].width = page_width
        for c in range(len(table_vuln.columns) - 1):
            table_vuln.columns[-1].width -= table_vuln.columns[c].width

        # COLOR
        # --------------------
        col_cells = table_vuln.columns[0].cells
        col_cells[0].merge(col_cells[7])
        color_fill = parse_xml(r'<w:shd {} w:fill="{}"/>'.format(
            nsdecls('w'),
            Config.colors()[vuln.level][1:]))
        col_cells[0]._tc.get_or_add_tcPr().append(color_fill)

        # TABLE HEADERS
        # --------------------
        hdr_cells = table_vuln.columns[1].cells
        hdr_cells[0].paragraphs[0].add_run('Description')
        hdr_cells[1].paragraphs[0].add_run('Impact')
        hdr_cells[2].paragraphs[0].add_run('Recommendation')
        hdr_cells[3].paragraphs[0].add_run('Details')
        hdr_cells[4].paragraphs[0].add_run('CVSS')
        hdr_cells[5].paragraphs[0].add_run('CVEs')
        hdr_cells[6].paragraphs[0].add_run('Family')
        hdr_cells[7].paragraphs[0].add_run('References')

        # FIELDS
        # --------------------
        cves = ", ".join(vuln.cves)
        cves = cves.upper() if cves != "" else "No CVE"

        cvss = str(vuln.cvss) if vuln.cvss != -1.0 else "No CVSS"

        txt_cells = table_vuln.columns[2].cells
        txt_cells[0].text = vuln.description
        txt_cells[1].text = vuln.impact
        txt_cells[2].text = vuln.solution
        txt_cells[3].text = vuln.insight
        txt_cells[4].text = cvss
        txt_cells[5].text = cves
        txt_cells[6].text = vuln.family
        txt_cells[7].text = vuln.references
        for c in txt_cells:
            for p in c.paragraphs:
                p.style = doc_styles['OR-cell']

        # Apply styles
        # --------------------
        for h in hdr_cells:
            for p in h.paragraphs:
                p.style = doc_styles['OR-cell_bold']

        for c in txt_cells:
            for p in c.paragraphs:
                p.style = doc_styles['OR-cell']

        # VULN HOSTS
        # --------------------
        document.add_paragraph('Vulnerable hosts', style='OR-Vuln_hosts')

        # add coloumn for result per port and resize columns
        table_hosts = document.add_table(cols=5, rows=(len(vuln.hosts) + 1))

        table_hosts.columns[0].width = Cm(2.8)
        table_hosts.columns[1].width = Cm(3.0)
        table_hosts.columns[2].width = Cm(2.0)
        table_hosts.columns[3].width = Cm(2.0)
        table_hosts.columns[-1].width = page_width
        for c in range(len(table_hosts.columns) - 1):
            table_hosts.columns[-1].width -= table_hosts.columns[c].width

        # TABLE HEADERS
        # --------------------
        hdr_cells = table_hosts.rows[0].cells
        hdr_cells[0].paragraphs[0].add_run('IP')
        hdr_cells[1].paragraphs[0].add_run('Host name')
        hdr_cells[2].paragraphs[0].add_run('Port number')
        hdr_cells[3].paragraphs[0].add_run('Port protocol')
        hdr_cells[4].paragraphs[0].add_run('Port result')
        # FIELDS
        # --------------------
        for j, (host, port) in enumerate(vuln.hosts, 1):
            cells = table_hosts.rows[j].cells
            cells[0].text = host.ip
            cells[1].text = host.host_name if host.host_name else "-"
            if port and port is not None:
                cells[2].text = "-" if port.number == 0 else str(port.number)
                cells[3].text = port.protocol
                cells[4].text = port.result
            else:
                cells[2].text = "No port info"

        # Apply styles
        # --------------------
        for h in hdr_cells:
            for p in h.paragraphs:
                p.style = doc_styles['OR-cell_bold']
        for r in table_hosts.rows[1:]:
            for c in r.cells:
                for p in c.paragraphs:
                    p.style = doc_styles['OR-cell']

    document.save(output_file)