sperated default report (tables and logging) from the the report being sent to aqua, to seperate modules. also added two core events: HuntStarted and HuntFinished. for reporters to listen to

Also changed default of tables for prinring evidence
This commit is contained in:
daniel_sagi
2018-07-17 15:44:28 +03:00
parent 4d599cda50
commit 31d7c1e754
7 changed files with 173 additions and 125 deletions

View File

@@ -14,8 +14,8 @@ parser.add_argument('--remote', nargs='+', metavar="HOST", default=list(), help=
parser.add_argument('--active', action="store_true", help="enables active hunting")
parser.add_argument('--log', type=str, metavar="LOGLEVEL", default='INFO', help="set log level, options are: debug, info, warn, none")
parser.add_argument('--token', type=str, metavar="AQUA_TOKEN", help="specify the token retrieved from Aqua, after finished executing, the report will be visible on kube-hunter's site")
config = parser.parse_args()
try:
loglevel = getattr(logging, config.log.upper())
except:
@@ -23,8 +23,11 @@ except:
if config.log.lower() != "none":
logging.basicConfig(level=loglevel, format='%(asctime)s - [%(levelname)s]: %(message)s')
from report import reporter
from report import default
from report import aqua
from src.core.events import handler
from src.core.events.types import HuntFinished, HuntStarted
from src.modules.discovery import HostDiscovery
from src.modules.discovery.hosts import HostScanEvent
@@ -51,22 +54,22 @@ def interactive_set_config():
return False
return True
hunt_started = False
def main():
global hunt_started
scan_options = [
config.pod,
config.cidr,
config.remote,
config.internal
]
hunt_started = False
try:
if not any(scan_options):
if not interactive_set_config(): return
if config.token:
reporter.print_report_url(token=config.token)
hunt_started = True
logging.info("Started")
hunt_started = True
handler.publish_event(HuntStarted())
handler.publish_event(HostScanEvent())
# Blocking to see discovery output
@@ -75,11 +78,10 @@ def main():
logging.debug("Kube-Hunter stopped by user")
finally:
if hunt_started:
handler.publish_event(HuntFinished())
handler.join()
handler.free()
logging.debug("Cleaned Queue")
reporter.print_tables()
if config.token:
reporter.send_report(token=config.token)
logging.debug("Cleaned Queue")
if config.pod:
while True: time.sleep(5)

View File

@@ -1 +0,0 @@
from reporter import *

View File

@@ -1,14 +1,15 @@
import json
import logging
from time import time
from collections import defaultdict
import time
import requests
from prettytable import ALL, PrettyTable
from __main__ import config
from src.core.events import handler
from src.core.events.types import Service, Vulnerability
from src.core.events.types import Service, Vulnerability, HuntStarted, HuntFinished
from __main__ import config
# [event, ...]
services = list()
@@ -16,58 +17,28 @@ services = list()
# [(TypeClass, event), ...]
insights = list()
vulnerabilities = list()
EVIDENCE_PREVIEW = 40
MAX_WIDTH_VULNS = 70
MAX_WIDTH_SERVICES = 60
AQUA_PUSH_URL = "https://qlyscbqwl7.execute-api.us-east-1.amazonaws.com/Prod/submit?token={token}"
AQUA_RESULTS_URL = "https://kubehunter.aquasec.com/report.html?token={token}"
@handler.subscribe(Service)
@handler.subscribe(Vulnerability)
class Reporter(object):
"""Reportes can be initiated by the event handler, and by regular decaration. for usage on end of runtime"""
def __init__(self, event=None):
class AquaReporter(object):
def __init__(self, event):
self.event = event
self.insights_by_id = defaultdict(list)
self.services_by_id = defaultdict(list)
def execute(self):
"""function is called only when collecting data"""
global services, insights
bases = self.event.__class__.__mro__
if Service in bases:
services.append(self.event)
logging.info("[OPEN SERVICE - {name}] IP:{host} PORT:{port}".format(
host=self.event.host,
port=self.event.port,
name=self.event.get_name(),
desc=self.event.explain()
))
elif Vulnerability in bases:
insights.append((Vulnerability, self.event))
vulnerabilities.append(self.event)
logging.info("[VULNERABILITY - {name}] {desc}".format(
name=self.event.get_name(),
desc=self.event.explain(),
))
if config.token:
self.send_report(token=config.token)
def print_tables(self):
"""generates report tables and outputs to stdout"""
if len(services):
print_nodes()
if not config.mapping:
print_services()
print_vulnerabilities()
else:
print "\nKube Hunter couldn't find any clusters"
# print "\nKube Hunter couldn't find any clusters. {}".format("Maybe try with --active?" if not config.active else "")
def build_sub_services(self, services_list):
# correlation functions
def get_insights_by_service(service):
@@ -110,7 +81,7 @@ class Reporter(object):
} for insight_type, insight in get_insights_by_service(service)]
return current_list
def send_report(self, token):
def send_report(self, token, finished=False):
def generate_report():
"""function generates a report corresponding to specifications of the frontend of kubehunter"""
for service in services:
@@ -132,12 +103,11 @@ class Reporter(object):
report["services"].append(service_report)
return report
finished = (not handler.unfinished_tasks)
logging.debug("generating report")
report = {
'results': generate_report(),
'metadata': {
'finished': int(time()*1000) if finished else False
'finished': int(time.time()*1000) if finished else False
}
}
logging.debug("uploading report")
@@ -150,65 +120,25 @@ class Reporter(object):
else:
logging.debug("Failed sending report with:{}, {}".format(r.status_code, r.text))
if finished:
print "\nCould not send report.\n{}".format(json.loads(r.text).get("status", ""))
def print_report_url(self, token):
url_table = PrettyTable(["{}".format(AQUA_RESULTS_URL.format(token=token))], hrules=ALL)
print "\nReport will be available at:\n{}\n".format(url_table)
reporter = Reporter()
print "\nCould not send report.\n{}".format(json.loads(r.text).get("status", ""))
reporter = AquaReporter({})
""" Tables Generation """
def print_nodes():
nodes_table = PrettyTable(["Type", "Location"], hrules=ALL)
nodes_table.align="l"
nodes_table.max_width=MAX_WIDTH_SERVICES
nodes_table.padding_width=1
nodes_table.sortby="Type"
nodes_table.reversesort=True
nodes_table.header_style="upper"
# TODO: replace with sets
id_memory = list()
for service in services:
if service.event_id not in id_memory:
nodes_table.add_row(["Node/Master", service.host])
id_memory.append(service.event_id)
print "Nodes:"
print nodes_table
print
@handler.subscribe(HuntStarted)
class PrintUrlOnStart(object):
def __init__(self, event):
self.event = event
def print_services():
services_table = PrettyTable(["Service", "Location", "Description"], hrules=ALL)
services_table.align="l"
services_table.max_width=MAX_WIDTH_SERVICES
services_table.padding_width=1
services_table.sortby="Service"
services_table.reversesort=True
services_table.header_style="upper"
for service in services:
services_table.add_row([service.get_name(), "{}:{}{}".format(service.host, service.port, service.get_path()), service.explain()])
print "Detected Services:"
print services_table
print
def execute(self):
if config.token:
url_table = PrettyTable(["{}".format(AQUA_RESULTS_URL.format(token=config.token))], hrules=ALL)
print "\nReport will be available at:\n{}\n".format(url_table)
def print_vulnerabilities():
column_names = ["Location", "Category", "Vulnerability", "Description"]
if config.active: column_names.append("Evidence")
vuln_table = PrettyTable(column_names, hrules=ALL)
vuln_table.align="l"
vuln_table.max_width=MAX_WIDTH_VULNS
vuln_table.sortby="Category"
vuln_table.reversesort=True
vuln_table.padding_width=1
vuln_table.header_style="upper"
for vuln in vulnerabilities:
row = ["{}:{}".format(vuln.host, vuln.port) if vuln.host else "", vuln.component.name, vuln.get_name(), vuln.explain()]
if config.active:
evidence = str(vuln.evidence)[:EVIDENCE_PREVIEW] + "..." if len(str(vuln.evidence)) > EVIDENCE_PREVIEW else str(vuln.evidence)
row.append(evidence)
vuln_table.add_row(row)
print "Vulnerabilities:"
print vuln_table
print
@handler.subscribe(HuntFinished)
class SendFullReport(object):
def __init__(self, event):
self.event = event
def execute(self):
if config.token:
reporter.send_report(token=config.token, finished=True)

123
report/default.py Normal file
View File

@@ -0,0 +1,123 @@
import json
import logging
from time import time
from collections import defaultdict
import requests
from prettytable import ALL, PrettyTable
from __main__ import config
from src.core.events import handler
from src.core.events.types import Service, Vulnerability, HuntFinished
# [event, ...]
services = list()
# [(TypeClass, event), ...]
insights = list()
vulnerabilities = list()
EVIDENCE_PREVIEW = 40
MAX_WIDTH_VULNS = 70
MAX_WIDTH_SERVICES = 60
@handler.subscribe(Service)
@handler.subscribe(Vulnerability)
class DefaultReporter(object):
"""Reportes can be initiated by the event handler, and by regular decaration. for usage on end of runtime"""
def __init__(self, event=None):
self.event = event
def execute(self):
"""function is called only when collecting data"""
global services, insights
bases = self.event.__class__.__mro__
if Service in bases:
services.append(self.event)
logging.info("[OPEN SERVICE - {name}] IP:{host} PORT:{port}".format(
host=self.event.host,
port=self.event.port,
name=self.event.get_name(),
desc=self.event.explain()
))
elif Vulnerability in bases:
insights.append((Vulnerability, self.event))
vulnerabilities.append(self.event)
logging.info("[VULNERABILITY - {name}] {desc}".format(
name=self.event.get_name(),
desc=self.event.explain(),
))
def print_tables(self):
"""generates report tables and outputs to stdout"""
if len(services):
print_nodes()
if not config.mapping:
print_services()
print_vulnerabilities()
else:
print "\nKube Hunter couldn't find any clusters"
# print "\nKube Hunter couldn't find any clusters. {}".format("Maybe try with --active?" if not config.active else "")
reporter = DefaultReporter()
@handler.subscribe(HuntFinished)
class SendFullReport(object):
def __init__(self, event):
self.event = event
def execute(self):
reporter.print_tables()
""" Tables Generation """
def print_nodes():
nodes_table = PrettyTable(["Type", "Location"], hrules=ALL)
nodes_table.align="l"
nodes_table.max_width=MAX_WIDTH_SERVICES
nodes_table.padding_width=1
nodes_table.sortby="Type"
nodes_table.reversesort=True
nodes_table.header_style="upper"
# TODO: replace with sets
id_memory = list()
for service in services:
if service.event_id not in id_memory:
nodes_table.add_row(["Node/Master", service.host])
id_memory.append(service.event_id)
print "Nodes:"
print nodes_table
print
def print_services():
services_table = PrettyTable(["Service", "Location", "Description"], hrules=ALL)
services_table.align="l"
services_table.max_width=MAX_WIDTH_SERVICES
services_table.padding_width=1
services_table.sortby="Service"
services_table.reversesort=True
services_table.header_style="upper"
for service in services:
services_table.add_row([service.get_name(), "{}:{}{}".format(service.host, service.port, service.get_path()), service.explain()])
print "Detected Services:"
print services_table
print
def print_vulnerabilities():
column_names = ["Location", "Category", "Vulnerability", "Description", "Evidence"]
vuln_table = PrettyTable(column_names, hrules=ALL)
vuln_table.align="l"
vuln_table.max_width=MAX_WIDTH_VULNS
vuln_table.sortby="Category"
vuln_table.reversesort=True
vuln_table.padding_width=1
vuln_table.header_style="upper"
for vuln in vulnerabilities:
row = ["{}:{}".format(vuln.host, vuln.port) if vuln.host else "", vuln.category.name, vuln.get_name(), vuln.explain()]
evidence = str(vuln.evidence)[:EVIDENCE_PREVIEW] + "..." if len(str(vuln.evidence)) > EVIDENCE_PREVIEW else str(vuln.evidence)
row.append(evidence)
vuln_table.add_row(row)
print "Vulnerabilities:"
print vuln_table
print

View File

@@ -1,15 +0,0 @@
from threading import Lock
class Database(object):
def __init__(self):
self.lock = Lock()
# def __getattribute__(self, value):
# with self.lock:
# return self.__dict__[value]
# def __setattr__(self, name, value):
# with self.lock:
# self.__dict__[name] = value
db = Database()

View File

@@ -1,13 +1,16 @@
import logging
import time
from abc import ABCMeta
from collections import defaultdict
from Queue import Queue
from threading import Lock, Thread
import time
from __main__ import config
from ..types import ActiveHunter
from ...core.events.types import HuntFinished
working_count = 0
lock = Lock()
@@ -80,4 +83,4 @@ class EventQueue(Queue, object):
with self.mutex:
self.queue.clear()
handler = EventQueue(800)
handler = EventQueue(800)

View File

@@ -79,3 +79,9 @@ class OpenPortEvent(Event):
def __str__(self):
return str(self.port)
class HuntFinished(Event):
pass
class HuntStarted(Event):
pass