-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcacus.py
70 lines (52 loc) · 3.32 KB
/
cacus.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import argparse
import logging
import sys
from datetime import datetime
from xml.etree import ElementTree as ET
from modules import output, parser
PARSER = argparse.ArgumentParser()
PARSER.add_argument('--inputfile', '-if', type=str, required=True, help='Path to input .nessus file')
PARSER.add_argument('--outputfile', '-of', type=str, default=f"./compliance_results_{datetime.today().strftime('%Y_%m_%d_%H%M')}.csv", help='Path to output CSV file (default: "%(default)s")')
PARSER.add_argument('--outputdelim', '-od', type=str, default=',', help='Output file delimiter (default: "%(default)s")')
PARSER.add_argument('--aggregate', '-ag', action='store_true', help='Aggregate issues')
PARSER.add_argument('--nopadding', '-np', action='store_true', help='Disable padding between aggregated hosts')
ARGS = PARSER.parse_args()
logging.basicConfig(format='%(message)s', level=logging.INFO, stream=sys.stderr)
def main():
# define namespaces for non-default elements to stop search breaking
xml_namespaces = {'cm': 'http://www.nessus.org/cm'}
logging.info(f"[i] Reading file from: {ARGS.inputfile}")
try:
# get document root
xml_doc = ET.parse(ARGS.inputfile).getroot()
# iterate through all Report elements within the provided .nessus file
for report in xml_doc.findall('Report'):
report_hosts = parser.parse_hosts(report)
report_issues = list()
for host in report_hosts:
logging.info(f"[i] Parsing compliance issues for host: {host.get('name')}")
compliance_issues = parser.parse_compliance(host, xml_namespaces)
# get lists for pass/fail items
passed = list(filter(lambda x: x.result == 'PASSED', compliance_issues))
failed = list(filter(lambda x: x.result == 'FAILED', compliance_issues))
# get pass/fail percentages
passed_percent = round(len(passed) / len(compliance_issues) * 100, 2) if len(passed) > 0 else 0
failed_percent = round(len(failed) / len(compliance_issues) * 100, 2) if len(failed) > 0 else 0
# strip out anything with a status of WARNING or ERROR
compliance_issues = list(filter(lambda x: x.result in ['PASSED', 'FAILED'], compliance_issues))
# Remove the last element from the list that just contains info about what compliance check was run
compliance_issues.pop()
logging.info(f"[i] Found {len(compliance_issues)} compliance issues\n\tPassed: {len(passed)} ({passed_percent}%)\n\tFailed: {len(failed)} ({failed_percent}%)")
# append list of issues from this host to overall list
report_issues = [*report_issues, *compliance_issues]
if ARGS.aggregate:
report_issues = parser.aggregate_issues(report_issues, ARGS.nopadding)
# sort issues by name
report_issues = sorted(report_issues, key=lambda x: x.name)
headers = ['Check Name', 'Host', 'Configured Value', 'Expected Value', 'Info', 'Solution', 'Result']
output.write_output(ARGS.outputfile, headers, report_issues, ARGS.outputdelim)
logging.info(f"[i] Output file written to: {ARGS.outputfile}")
except Exception as err:
logging.error(f"[!] {err}")
if __name__ == '__main__':
main()