pentext/chatops/python/pentext_id.py
Peter Mosmans 230283aa31 Format finding codes using three digits
Minor docstring change.
2017-10-31 17:10:10 +10:00

169 lines
5.3 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""pentext_id - Identify findings from report.
This script is part of the PenText framework
https://pentext.org
Copyright (C) 2017 Radically Open Security
https://www.radicallyopensecurity.com
Author: Peter Mosmans
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
"""
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals
import argparse
import logging
import os
import sys
import textwrap
try:
from lxml import etree
except ImportError as exception:
print('[-] This script needs lxml',
file=sys.stderr)
print("Install lxml with: sudo pip install lxml", file=sys.stderr)
sys.exit(-1)
VERSION = '0.2.1'
class LogFormatter(logging.Formatter):
"""
Class to format log messages based on their type.
"""
FORMATS = {logging.DEBUG: "[d] %(message)s",
logging.INFO: "[*] %(message)s",
logging.ERROR: "[-] %(message)s",
logging.CRITICAL: "[-] FATAL: %(message)s",
'DEFAULT': "%(message)s"}
def format(self, record):
self._fmt = self.FORMATS.get(record.levelno, self.FORMATS['DEFAULT'])
return logging.Formatter.format(self, record)
def parse_arguments(banner):
"""
Parse and return command line arguments.
"""
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=textwrap.dedent(banner + '''\
pentext_id - Identify findings based on their unique id, filename, or location
in the report
Copyright (C) 2017 Peter Mosmans [Radically Open Security]
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.'''))
parser.add_argument('target', nargs='?', type=str,
help="""[PARAMETER] can be TODO""")
parser.add_argument('--debug', action='store_true',
help='Show debug information')
parser.add_argument('-v', '--verbose', action='store_true',
help='Be more verbose')
parser.add_argument('--finding', action='store', type=int,
help='Convert finding filename ID to finding ID')
parser.add_argument('--id', action='store', type=int,
help='Show filename of a finding ID')
parser.add_argument('--source', action='store',
default='source/report.xml',
help='Set source file (default: source/report.xml)')
args = parser.parse_args()
return args
def setup_logging(args):
"""
Set up loghandlers according to options.
"""
logger = logging.getLogger()
logger.setLevel(0)
console = logging.StreamHandler(stream=sys.stdout)
console.setFormatter(LogFormatter())
if args.debug:
console.setLevel(logging.DEBUG)
elif args.verbose:
console.setLevel(logging.INFO)
else:
console.setLevel(logging.ERROR)
logger.addHandler(console)
def locate_finding(findings, args):
"""
Show id corresponding to a finding.
"""
try:
for i, href in enumerate(findings, start=1):
if 'findings/f{0}-'.format(args.finding) in href or \
'findings/{0}-'.format(args.finding) in href:
print("{0:2d} {1}".format(i, href))
except IndexError:
logging.error('Finding %s could not be located', args.id)
def locate_id(findings, args):
"""
Show finding corresponding to an identifier
"""
try:
print("{0:2d} {1}".format(args.id, findings[args.id-1]))
except IndexError:
logging.error('Finding %s could not be located', args.id)
def main():
"""
Main program loop.
"""
banner = 'pentext_id version {0}'.format(VERSION)
args = parse_arguments(banner)
setup_logging(args)
logging.info('%s starting', banner)
# read document
filename = args.source
if not os.path.isfile(filename):
logging.error('Could not open %s', filename)
sys.exit(-1)
findings = []
try:
# Parse document into an ElementTree
tree = etree.parse(filename, etree.XMLParser(strip_cdata=False))
# Read finding code
code = tree.getroot().attrib['findingCode']
# Grab all elements from the findings section
for elements in tree.xpath('//section[@id="findings"]'):
# Iterate through all elements, looking for the findings
for element in elements:
if 'href' in element.attrib:
findings.append(element.attrib['href'])
except (etree.ParseError, IOError) as exception:
logging.warning('Validating %s failed: %s', filename, exception)
if args.id:
locate_id(findings, args)
elif args.finding:
locate_finding(findings, args)
else:
for i, href in enumerate(findings, start=1):
print("{0}-{1:03d} {2}".format(code, i, href))
if __name__ == "__main__":
main()