476 lines
29 KiB
Python
476 lines
29 KiB
Python
|
import weasyprint
|
||
|
import os
|
||
|
import json
|
||
|
import hashlib
|
||
|
import re
|
||
|
import sys
|
||
|
|
||
|
from weasyprint import HTML
|
||
|
from pathlib import Path
|
||
|
from datetime import datetime
|
||
|
from utils import get_config
|
||
|
|
||
|
|
||
|
class Report(object):
|
||
|
|
||
|
def __init__(self, capture_directory, analysis_duration):
|
||
|
self.capture_directory = capture_directory
|
||
|
self.alerts = self.read_json(os.path.join(capture_directory, "assets/alerts.json"))
|
||
|
self.records = self.read_json(os.path.join(capture_directory, "assets/records.json"))
|
||
|
self.methods = self.read_json(os.path.join(capture_directory, "assets/detection_methods.json"))
|
||
|
self.device = self.read_json(os.path.join(capture_directory, "assets/device.json"))
|
||
|
self.capinfos = self.read_json(os.path.join(capture_directory, "assets/capinfos.json"))
|
||
|
self.instance = self.read_json(os.path.join(capture_directory, "assets/instance.json"))
|
||
|
self.analysis_duration = analysis_duration
|
||
|
|
||
|
with open(os.path.join(self.capture_directory, "capture.pcap"), "rb") as f:
|
||
|
self.capture_sha1 = hashlib.sha1(f.read()).hexdigest()
|
||
|
|
||
|
self.userlang = get_config(("frontend", "user_lang"))
|
||
|
|
||
|
# Load template language
|
||
|
if not re.match("^[a-z]{2,3}$", self.userlang):
|
||
|
self.userlang = "en"
|
||
|
with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "locales/{}.json".format(self.userlang))) as f:
|
||
|
self.template = json.load(f)["report"]
|
||
|
|
||
|
def read_json(self, json_path):
|
||
|
"""Read a JSON
|
||
|
|
||
|
Args:
|
||
|
json_path (_type_): _description_
|
||
|
|
||
|
Returns:
|
||
|
_type_: _description_
|
||
|
"""
|
||
|
with open(json_path, "r") as json_file:
|
||
|
return json.load(json_file)
|
||
|
|
||
|
def generate_report(self):
|
||
|
"""Generate the full report and save it as report.pdf """
|
||
|
|
||
|
content = self.generate_page_header()
|
||
|
content += self.generate_header()
|
||
|
content += self.generate_warning()
|
||
|
content += self.generate_alerts()
|
||
|
content += self.generate_suspect_conns_block()
|
||
|
content += self.generate_uncat_conns_block()
|
||
|
content += self.generate_whitelist_block()
|
||
|
|
||
|
htmldoc = HTML(string=content, base_url="").write_pdf()
|
||
|
Path(os.path.join(self.capture_directory,
|
||
|
"report.pdf")).write_bytes(htmldoc)
|
||
|
|
||
|
def generate_warning(self):
|
||
|
"""Generate the main warning message on the report
|
||
|
|
||
|
Returns:
|
||
|
string: HTML code of the main warning message.
|
||
|
"""
|
||
|
if len(self.alerts["high"]):
|
||
|
msg = "<div class=\"warning high\">"
|
||
|
msg += self.template["high_msg"].format(
|
||
|
self.nb_translate(len(self.alerts["high"])))
|
||
|
msg += "</div>"
|
||
|
return msg
|
||
|
elif len(self.alerts["moderate"]):
|
||
|
msg = "<div class=\"warning moderate\">"
|
||
|
msg += self.template["moderate_msg"].format(
|
||
|
self.nb_translate(len(self.alerts["moderate"])))
|
||
|
msg += "</div>"
|
||
|
return msg
|
||
|
elif len(self.alerts["low"]):
|
||
|
msg = "<div class=\"warning low\">"
|
||
|
msg += self.template["low_msg"].format(
|
||
|
self.nb_translate(len(self.alerts["low"])))
|
||
|
msg += "</div>"
|
||
|
return msg
|
||
|
else:
|
||
|
msg = "<div class=\"warning low\">"
|
||
|
msg += self.template["none_msg"]
|
||
|
msg += "</div>"
|
||
|
return msg
|
||
|
|
||
|
def nb_translate(self, nb):
|
||
|
""" Translate a number in a string.
|
||
|
|
||
|
Args:
|
||
|
nb (int): integer
|
||
|
|
||
|
Returns:
|
||
|
string: A translated string related to the integer
|
||
|
"""
|
||
|
a = self.template["numbers"]
|
||
|
return a[nb-1] if nb <= 9 else str(nb)
|
||
|
|
||
|
def generate_suspect_conns_block(self):
|
||
|
"""Generate the block and the table of communications categorized as suspect.
|
||
|
|
||
|
Returns:
|
||
|
str: HTML block of the suspect communications
|
||
|
"""
|
||
|
|
||
|
tbody = ""
|
||
|
|
||
|
for record in self.records:
|
||
|
if record["suspicious"] == True:
|
||
|
tbody += "<tr>"
|
||
|
tbody += f"<td>{', '.join([p['name'] for p in record['protocols']])}</td>"
|
||
|
tbody += f"<td>{', '.join(record['domains'])}</td>"
|
||
|
tbody += f"<td>{record['ip_dst']}</td>"
|
||
|
tbody += f"<td>{', '.join([str(p['port']) if p['port'] != -1 else '--' for p in record['protocols']])}</td>"
|
||
|
tbody += "</tr>"
|
||
|
|
||
|
if len(tbody):
|
||
|
title = f"<h2>{self.template['suspect_title']}</h2>"
|
||
|
table = "<table>"
|
||
|
table += " <thead>"
|
||
|
table += " <tr>"
|
||
|
table += f" <th>{self.template['protocol']}</th>"
|
||
|
table += f" <th>{self.template['domain']}</th>"
|
||
|
table += f" <th>{self.template['dst_ip']}</th>"
|
||
|
table += f" <th>{self.template['dst_port']}</th>"
|
||
|
table += " </tr>"
|
||
|
table += " </thead>"
|
||
|
table += "<tbody>"
|
||
|
table += tbody
|
||
|
table += "</tbody></table>"
|
||
|
return title + table
|
||
|
else:
|
||
|
return ""
|
||
|
|
||
|
def generate_uncat_conns_block(self):
|
||
|
"""Generate the block and the table of the uncategorized communications
|
||
|
|
||
|
Returns:
|
||
|
str: HTML block of the uncategorized communications
|
||
|
"""
|
||
|
tbody = ""
|
||
|
|
||
|
for record in self.records:
|
||
|
if record["suspicious"] == False and record["whitelisted"] == False:
|
||
|
tbody += "<tr>"
|
||
|
tbody += f"<td>{', '.join([p['name'] for p in record['protocols']])}</td>"
|
||
|
tbody += f"<td>{', '.join(record['domains'])}</td>"
|
||
|
tbody += f"<td>{record['ip_dst']}</td>"
|
||
|
tbody += f"<td>{', '.join([str(p['port']) if p['port'] != -1 else '--' for p in record['protocols']])}</td>"
|
||
|
tbody += "</tr>"
|
||
|
|
||
|
if len(tbody):
|
||
|
title = "<h2>{}</h2>".format(self.template["uncat_title"])
|
||
|
table = "<table>"
|
||
|
table += " <thead>"
|
||
|
table += " <tr>"
|
||
|
table += f" <th>{self.template['protocol']}</th>"
|
||
|
table += f" <th>{self.template['domain']}</th>"
|
||
|
table += f" <th>{self.template['dst_ip']}</th>"
|
||
|
table += f" <th>{self.template['dst_port']}</th>"
|
||
|
table += " </tr>"
|
||
|
table += " </thead>"
|
||
|
table += "<tbody>"
|
||
|
table += tbody
|
||
|
table += "</tbody></table>"
|
||
|
return title + table
|
||
|
else:
|
||
|
return ""
|
||
|
|
||
|
def generate_whitelist_block(self):
|
||
|
"""Generate the block and the table of the whitelisted communications
|
||
|
|
||
|
Returns:
|
||
|
str: HTML block of the whitelisted communications
|
||
|
"""
|
||
|
|
||
|
tbody = ""
|
||
|
|
||
|
for record in self.records:
|
||
|
if record["whitelisted"] == True:
|
||
|
tbody += "<tr>"
|
||
|
tbody += f"<td>{', '.join([p['name'] for p in record['protocols']])}</td>"
|
||
|
tbody += f"<td>{', '.join(record['domains'])}</td>"
|
||
|
tbody += f"<td>{record['ip_dst']}</td>"
|
||
|
tbody += f"<td>{', '.join([str(p['port']) if p['port'] != -1 else '--' for p in record['protocols']])}</td>"
|
||
|
tbody += "</tr>"
|
||
|
|
||
|
if len(tbody):
|
||
|
title = "<h2>{}</h2>".format(self.template["whitelist_title"])
|
||
|
table = "<table>"
|
||
|
table += " <thead>"
|
||
|
table += " <tr>"
|
||
|
table += f" <th>{self.template['protocol']}</th>"
|
||
|
table += f" <th>{self.template['domain']}</th>"
|
||
|
table += f" <th>{self.template['dst_ip']}</th>"
|
||
|
table += f" <th>{self.template['dst_port']}</th>"
|
||
|
table += " </tr>"
|
||
|
table += " </thead>"
|
||
|
table += "<tbody>"
|
||
|
table += tbody
|
||
|
table += "</tbody></table>"
|
||
|
return title + table
|
||
|
else:
|
||
|
return ""
|
||
|
|
||
|
def generate_header(self):
|
||
|
"""Generate the report headers with the capture's metadata.
|
||
|
|
||
|
Returns:
|
||
|
str: HTML block containing the data.
|
||
|
"""
|
||
|
header = "<div class=\"header\">"
|
||
|
header += "<div class=\"logo\"></div>"
|
||
|
header += f"<p><br /><strong>{self.template['device_mac']}: {self.device['mac_address']}</strong><br />"
|
||
|
header += f"{self.template['detection_methods']}: {'☑' if self.methods['iocs'] else '☐'} IOCs {'☑' if self.methods['heuristics'] else '☐'} Heuristics {'☑' if self.methods['active'] else '☐'} Active analysis <br />"
|
||
|
header += f"{self.template['capture_sha1']}: {self.capture_sha1}<br />"
|
||
|
header += f"{self.template['instance_uuid']}: {self.instance['instance_uuid']}<br />"
|
||
|
header += f"{self.template['report_generated_on']} {datetime.now().strftime('%d/%m/%Y - %H:%M:%S')}<br />"
|
||
|
if self.capinfos is not None:
|
||
|
header += f"{self.template['capture_duration']}: {self.capinfos['Capture duration'].split(' ')[0]} {self.template['seconds']}<br />"
|
||
|
header += f"{self.template['analysis_duration']}: {self.analysis_duration} {self.template['seconds']}<br />"
|
||
|
header += f"{self.template['packets_number']}: {self.capinfos['Number of packets']}<br />"
|
||
|
header += "</p>"
|
||
|
header += "</div>"
|
||
|
return header
|
||
|
|
||
|
def generate_alerts(self):
|
||
|
"""Generate a block embedding the alerts triggered during the analysis.
|
||
|
|
||
|
Returns:
|
||
|
str: HTML block containing the data.
|
||
|
"""
|
||
|
alerts = "<ul class=\"alerts\">"
|
||
|
for alert in self.alerts["high"]:
|
||
|
alerts += "<li class =\"alert\">"
|
||
|
alerts += "<span class=\"high-label\">High</span>"
|
||
|
alerts += "<span class=\"alert-id\">{}</span>".format(alert["id"])
|
||
|
alerts += "<div class = \"alert-body\">"
|
||
|
alerts += "<span class=\"title\">{}</span>".format(alert["title"])
|
||
|
alerts += "<p class=\"description\">{}</p>".format(
|
||
|
alert["description"])
|
||
|
alerts += "</div>"
|
||
|
alerts += "</li>"
|
||
|
|
||
|
for alert in self.alerts["moderate"]:
|
||
|
alerts += "<li class =\"alert\">"
|
||
|
alerts += "<span class=\"moderate-label\">moderate</span>"
|
||
|
alerts += "<span class=\"alert-id\">{}</span>".format(alert["id"])
|
||
|
alerts += "<div class = \"alert-body\">"
|
||
|
alerts += "<span class=\"title\">{}</span>".format(alert["title"])
|
||
|
alerts += "<p class=\"description\">{}</p>".format(
|
||
|
alert["description"])
|
||
|
alerts += "</div>"
|
||
|
alerts += "</li>"
|
||
|
for alert in self.alerts["low"]:
|
||
|
alerts += "<li class =\"alert\">"
|
||
|
alerts += "<span class=\"low-label\">low</span>"
|
||
|
alerts += "<span class=\"alert-id\">{}</span>".format(alert["id"])
|
||
|
alerts += "<div class = \"alert-body\">"
|
||
|
alerts += "<span class=\"title\">{}</span>".format(alert["title"])
|
||
|
alerts += "<p class=\"description\">{}</p>".format(
|
||
|
alert["description"])
|
||
|
alerts += "</div>"
|
||
|
alerts += "</li>"
|
||
|
|
||
|
alerts += "</ul>"
|
||
|
return alerts
|
||
|
|
||
|
def generate_page_footer(self):
|
||
|
"""Generate the page footer
|
||
|
|
||
|
Returns:
|
||
|
str: HTML block closing the page
|
||
|
"""
|
||
|
return "</body></html>"
|
||
|
|
||
|
def generate_page_header(self):
|
||
|
"""Generate the page header
|
||
|
|
||
|
Returns:
|
||
|
str: HTML block containing the page header with the CSS.
|
||
|
"""
|
||
|
return """<html
|
||
|
<head>
|
||
|
<style>
|
||
|
* {
|
||
|
font-family: Arial, Helvetica, sans-serif;
|
||
|
}
|
||
|
|
||
|
h2 {
|
||
|
padding-top: 30px;
|
||
|
font-weight: 400;
|
||
|
font-size: 18px;
|
||
|
}
|
||
|
|
||
|
td {
|
||
|
width: auto;
|
||
|
padding: 10px;
|
||
|
}
|
||
|
|
||
|
table {
|
||
|
background: #FFF;
|
||
|
border: 2px solid #FAFAFA;
|
||
|
border-radius: 5px;
|
||
|
border-collapse: separate;
|
||
|
border-spacing: 0px;
|
||
|
width: 100%;
|
||
|
font-size: 12px;
|
||
|
}
|
||
|
|
||
|
p {
|
||
|
font-size: 13px;
|
||
|
}
|
||
|
|
||
|
thead tr th {
|
||
|
border-bottom: 1px solid #CCC;
|
||
|
border-collapse: separate;
|
||
|
border-spacing: 5px 5px;
|
||
|
background-color: #FFF;
|
||
|
padding: 10px;
|
||
|
text-align: left;
|
||
|
}
|
||
|
|
||
|
tbody tr#first td {
|
||
|
border-top: 3px solid #4d4d4d;
|
||
|
border-collapse: separate;
|
||
|
border-spacing: 5px 5px;
|
||
|
}
|
||
|
|
||
|
tr:nth-of-type(odd) {
|
||
|
background-color: #fafafa;
|
||
|
}
|
||
|
|
||
|
.logo {
|
||
|
background-image: url("data:image/svg+xml;base64;base64,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
|
||
|
width: 230px;
|
||
|
height: 60px;
|
||
|
background-size: cover;
|
||
|
position: absolute;
|
||
|
right: 0px;
|
||
|
}
|
||
|
|
||
|
.warning {
|
||
|
padding: 10px;
|
||
|
text-align: center;
|
||
|
border-radius: 5px;
|
||
|
color: #FFF;
|
||
|
margin-top: 40px;
|
||
|
margin-bottom: 40px;
|
||
|
font-weight:900;
|
||
|
}
|
||
|
|
||
|
.high {
|
||
|
background-color: #F44336;
|
||
|
}
|
||
|
|
||
|
.moderate {
|
||
|
background-color: #ff7e33;
|
||
|
}
|
||
|
|
||
|
.low {
|
||
|
background-color: #4fce0e;
|
||
|
}
|
||
|
|
||
|
ul {
|
||
|
list-style: none;
|
||
|
margin: 0;
|
||
|
padding: 0;
|
||
|
}
|
||
|
|
||
|
.alert {
|
||
|
margin-top: 15px;
|
||
|
}
|
||
|
|
||
|
.alert-body {
|
||
|
background-color: #FFF;
|
||
|
list-style: none;
|
||
|
padding: 10px;
|
||
|
border-radius: 5px;
|
||
|
border: 1px solid #EEE;
|
||
|
margin-top: 3px;
|
||
|
}
|
||
|
|
||
|
.alert-body>.title {
|
||
|
display: block;
|
||
|
padding: 5px 5px 5px 10px;
|
||
|
font-size: 13px;
|
||
|
}
|
||
|
|
||
|
.high-label {
|
||
|
background-color: #F44336;
|
||
|
padding: 5px;
|
||
|
text-transform: uppercase;
|
||
|
font-size: 10px;
|
||
|
font-weight: bold;
|
||
|
border-radius: 3px 0px 0px 0px;
|
||
|
margin: 0px;
|
||
|
color: #FFF;
|
||
|
margin-left: 10px;
|
||
|
}
|
||
|
|
||
|
.moderate-label {
|
||
|
background-color: #ff7e33;
|
||
|
padding: 5px;
|
||
|
text-transform: uppercase;
|
||
|
font-size: 10px;
|
||
|
font-weight: bold;
|
||
|
border-radius: 3px 0px 0px 0px;
|
||
|
margin: 0px;
|
||
|
color: #FFF;
|
||
|
margin-left: 10px;
|
||
|
}
|
||
|
|
||
|
.low-label {
|
||
|
background-color: #4fce0e;
|
||
|
padding: 5px;
|
||
|
text-transform: uppercase;
|
||
|
font-size: 10px;
|
||
|
font-weight: bold;
|
||
|
border-radius: 3px 0px 0px 0px;
|
||
|
margin: 0px;
|
||
|
color: #FFF;
|
||
|
margin-left: 10px;
|
||
|
}
|
||
|
|
||
|
.description {
|
||
|
margin: 0;
|
||
|
padding: 10px;
|
||
|
color:#333;
|
||
|
font-size:12px;
|
||
|
}
|
||
|
|
||
|
ul {
|
||
|
list-style: none;
|
||
|
margin: 0;
|
||
|
padding: 0;
|
||
|
}
|
||
|
|
||
|
.alert-id {
|
||
|
background-color: #636363;
|
||
|
padding: 5px;
|
||
|
text-transform: uppercase;
|
||
|
font-size: 10px;
|
||
|
font-weight: bold;
|
||
|
border-radius: 0px 3px 0px 0px;
|
||
|
margin: 0px;
|
||
|
color: #FFF;
|
||
|
margin-right: 10px;
|
||
|
}
|
||
|
|
||
|
.header>p {
|
||
|
font-size:12px;
|
||
|
}
|
||
|
@page {
|
||
|
@top-center {
|
||
|
content: "REPORT_HEADER - Page " counter(page) " / " counter(pages) ".";
|
||
|
font-size:12px;
|
||
|
color:#CCC;
|
||
|
}
|
||
|
@bottom-center {
|
||
|
content: "REPORT_FOOTER";
|
||
|
font-size:12px;
|
||
|
color:#CCC;
|
||
|
}
|
||
|
}
|
||
|
</style>
|
||
|
</head>
|
||
|
<body>""".replace("REPORT_HEADER", "{} {}".format(self.template["report_for_the_capture"], self.capture_sha1)).replace("REPORT_FOOTER", self.template["report_footer"])
|