diff --git a/src/core/events/handler.py b/src/core/events/handler.py index 8e3b109..916aeff 100644 --- a/src/core/events/handler.py +++ b/src/core/events/handler.py @@ -41,7 +41,7 @@ class EventQueue(Queue, object): self.subscribe_event(event, hook=hook, predicate=predicate) return hook return wrapper - + # getting uninstantiated event object def subscribe_event(self, event, hook=None, predicate=None): if ActiveHunter in hook.__mro__: @@ -75,13 +75,13 @@ class EventQueue(Queue, object): hook = self.get() try: hook.execute() - except Exception as ex: + except Exception as ex: logging.debug(ex.message) self.task_done() logging.debug("closing thread...") def notifier(self): - time.sleep(2) + time.sleep(2) while self.unfinished_tasks > 0: logging.debug("{} tasks left".format(self.unfinished_tasks)) time.sleep(3) @@ -91,5 +91,5 @@ class EventQueue(Queue, object): self.running = False with self.mutex: self.queue.clear() - + handler = EventQueue(800) diff --git a/src/modules/discovery/etcd.py b/src/modules/discovery/etcd.py new file mode 100644 index 0000000..3e92540 --- /dev/null +++ b/src/modules/discovery/etcd.py @@ -0,0 +1,28 @@ +import json +import logging + +import requests + +from ...core.events import handler +from ...core.events.types import Event, OpenPortEvent, Service +from ...core.types import Hunter + +# Service: + +class EtcdAccessEvent(Service, Event): + """Etcd is a DB that stores cluster's data, it contains configuration and current state information, and might contain secrets""" + def __init__(self): + Service.__init__(self, name="Etcd") + + + +@handler.subscribe(OpenPortEvent, predicate= lambda p: p.port == 2379) +class EtcdRemoteAccess(Hunter): + """Etcd Remote Access + Checks for remote availability of etcd, version, read access, write access + """ + def __init__(self, event): + self.event = event + + def execute(self): + self.publish_event(EtcdAccessEvent()) diff --git a/src/modules/discovery/hosts.py b/src/modules/discovery/hosts.py index bf866d3..f4d5e1d 100644 --- a/src/modules/discovery/hosts.py +++ b/src/modules/discovery/hosts.py @@ -15,7 +15,6 @@ from ...core.events import handler from ...core.events.types import Event, NewHostEvent, Vulnerability from ...core.types import Hunter, InformationDisclosure, Azure - class RunningAsPodEvent(Event): def __init__(self): self.name = 'Running from within a pod' diff --git a/src/modules/discovery/ports.py b/src/modules/discovery/ports.py index 73e2804..cd1170b 100644 --- a/src/modules/discovery/ports.py +++ b/src/modules/discovery/ports.py @@ -7,7 +7,7 @@ from ...core.events import handler from ...core.events.types import NewHostEvent, OpenPortEvent -default_ports = [8001, 10250, 10255, 30000, 443, 6443] +default_ports = [8001, 10250, 10255, 30000, 443, 6443, 2379] @handler.subscribe(NewHostEvent) class PortDiscovery(Hunter): diff --git a/src/modules/hunting/aks.py b/src/modules/hunting/aks.py index 679fd79..6090295 100644 --- a/src/modules/hunting/aks.py +++ b/src/modules/hunting/aks.py @@ -7,7 +7,7 @@ from kubelet import ExposedRunHandler from ...core.events import handler from ...core.events.types import Event, Vulnerability -from ...core.types import Hunter, ActiveHunter, KubernetesCluster, IdentityTheft, Azure +from ...core.types import Hunter, ActiveHunter, IdentityTheft, Azure class AzureSpnExposure(Vulnerability, Event): diff --git a/src/modules/hunting/etcd.py b/src/modules/hunting/etcd.py new file mode 100644 index 0000000..a9d8e1a --- /dev/null +++ b/src/modules/hunting/etcd.py @@ -0,0 +1,122 @@ +import logging + +import requests + +from ...core.events import handler +from ...core.events.types import Vulnerability, Event, OpenPortEvent +from ...core.types import ActiveHunter, Hunter, KubernetesCluster, InformationDisclosure, RemoteCodeExec, \ + UnauthenticatedAccess, AccessRisk + +""" Vulnerabilities """ +class EtcdRemoteWriteAccessEvent(Vulnerability, Event): + """Remote write access might grant an attacker full control over the kubernetes cluster""" + + def __init__(self, write_res): + Vulnerability.__init__(self, KubernetesCluster, name="Etcd Remote Write Access Event", category=RemoteCodeExec) + self.evidence = write_res + +class EtcdRemoteReadAccessEvent(Vulnerability, Event): + """Remote read access might expose to an attacker cluster's possible exploits, secrets and more.""" + + def __init__(self, keys): + Vulnerability.__init__(self, KubernetesCluster, name="Etcd Remote Read Access Event", category=AccessRisk) + self.evidence = keys + +class EtcdRemoteVersionDisclosureEvent(Vulnerability, Event): + """Remote version disclosure might give an attacker a valuable data to attack a cluster""" + + def __init__(self, version): + Vulnerability.__init__(self, KubernetesCluster, name="Etcd Remote version disclosure", + category=InformationDisclosure) + self.evidence = version + +class EtcdAccessEnabledWithoutAuthEvent(Vulnerability, Event): + """Etcd is accessible using HTTP (without authorization and authentication), it would allow a potential attacker to + gain access to the etcd""" + + def __init__(self, version): + Vulnerability.__init__(self, KubernetesCluster, name="Etcd is accessible using insecure connection (HTTP)", + category=UnauthenticatedAccess) + self.evidence = version + +# Active Hunter +@handler.subscribe(OpenPortEvent, predicate=lambda p: p.port == 2379) +class EtcdRemoteAccessActive(ActiveHunter): + """Etcd Remote Access + Checks for remote write access to etcd""" + + def __init__(self, event): + self.event = event + self.write_evidence = '' + + def db_keys_write_access(self): + logging.debug("Active hunter is attempting to write keys remotely on host " + self.event.host) + data = { + 'value': 'remotely written data' + } + try: + r = requests.post("{protocol}://{host}:{port}/v2/keys/message".format(host=self.event.host, port=2379, + protocol=self.protocol), data=data) + self.write_evidence = r.content if r.status_code == 200 and r.content != '' else False + return self.write_evidence + except requests.exceptions.ConnectionError: + return False + + def execute(self): + if self.db_keys_write_access(): + self.publish_event(EtcdRemoteWriteAccessEvent(self.write_evidence)) + + +# Passive Hunter +@handler.subscribe(OpenPortEvent, predicate=lambda p: p.port == 2379) +class EtcdRemoteAccess(Hunter): + """Etcd Remote Access + Checks for remote availability of etcd, version, read access, write access + """ + + def __init__(self, event): + self.event = event + self.version_evidence = '' + self.keys_evidence = '' + self.protocol = 'https' + + def db_keys_disclosure(self): + logging.debug(self.event.host + " Passive hunter is attempting to read etcd keys remotely") + try: + r = requests.get( + "{protocol}://{host}:{port}/v2/keys".format(protocol=self.protocol, host=self.event.host, port=2379), + verify=False) + self.keys_evidence = r.content if r.status_code == 200 and r.content != '' else False + return self.keys_evidence + except requests.exceptions.ConnectionError: + return False + + def version_disclosure(self): + logging.debug(self.event.host + " Passive hunter is attempting to check etcd version remotely") + try: + r = requests.get( + "{protocol}://{host}:{port}/version".format(protocol=self.protocol, host=self.event.host, port=2379), + verify=False) + self.version_evidence = r.content if r.status_code == 200 and r.content != '' else False + return self.version_evidence + except requests.exceptions.ConnectionError: + return False + + def insecure_access(self): + logging.debug(self.event.host + " Passive hunter is attempting to access etcd insecurely") + try: + r = requests.get("http://{host}:{port}/version".format(host=self.event.host, port=2379), verify=False) + return r.content if r.status_code == 200 and r.content != '' else False + except requests.exceptions.ConnectionError: + return False + + def execute(self): + if self.insecure_access(): # make a decision between http and https protocol + self.protocol = 'http' + if self.version_disclosure(): + self.publish_event(EtcdRemoteVersionDisclosureEvent(self.version_evidence)) + if self.protocol == 'http': + self.publish_event(EtcdAccessEnabledWithoutAuthEvent(self.version_evidence)) + if self.db_keys_disclosure(): + self.publish_event(EtcdRemoteReadAccessEvent(self.keys_evidence)) +