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
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
def new(cls, text): """ Return a new ``<w:t>`` element. """ t = OxmlElement('w:t') t.text = text return t
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
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)
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
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)
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
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
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
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)
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)
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))
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)
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))
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)