diff --git a/discovery/dashboard.py b/discovery/dashboard.py index 2a2cd5e..beb4389 100644 --- a/discovery/dashboard.py +++ b/discovery/dashboard.py @@ -3,10 +3,10 @@ import requests @handler.subscribe(OpenPortEvent, predicate=lambda x: x.port == 30000) class KubeDashboard(object): - def __init__(self, task): - self.task = task - self.host = task.host - self.port = task.port + def __init__(self, event): + self.event = event + self.host = event.host + self.port = event.port @property def secure(self): @@ -14,4 +14,4 @@ class KubeDashboard(object): return False def execute(self): - handler.publish_event(KubeDashboardEvent(host=self.host, port=self.port, secure=self.secure)) + handler.publish_event(KubeDashboardEvent()) diff --git a/discovery/hosts.py b/discovery/hosts.py index b024694..e151b76 100644 --- a/discovery/hosts.py +++ b/discovery/hosts.py @@ -1,16 +1,35 @@ -from netifaces import interfaces, ifaddresses, AF_INET +import logging +import sys +import time +from enum import Enum + from netaddr import IPNetwork -import events +from events import HostScanEvent, NewHostEvent, handler +from netifaces import AF_INET, ifaddresses, interfaces +# for comparing prefixes +class InterfaceTypes(Enum): + LOCALHOST = "127.0.0" + +@handler.subscribe(HostScanEvent) class HostDiscovery(object): - def __init__(self, task): - pass + def __init__(self, event): + self.event = event + # self.external = event.external def execute(self): + logging.info("Discovering Open Kubernetes Services...") + + handler.publish_event(NewHostEvent(host="acs954agent1.westus2.cloudapp.azure.com")) # test cluster for ifaceName in interfaces(): - addresses = [i['addr'] for i in ifaddresses(ifaceName).setdefault(AF_INET, [])] - if addresses: - subnet = IPNetwork('{0}/24'.format(addresses[0])) - for single_ip in IPNetwork(subnet): - events.handler.publish_event(events.NewHostEvent(host=single_ip)) \ No newline at end of file + for ip in self.generate_addresses(ifaceName): + handler.publish_event(NewHostEvent(host=ip)) + + def generate_addresses(self, ifaceName): + for address in [i['addr'] for i in ifaddresses(ifaceName).setdefault(AF_INET, [])]: + subnet = IPNetwork('{0}/24'.format(address)) + for ip in IPNetwork(subnet): + if not self.event.localhost and InterfaceTypes.LOCALHOST.value in ip.__str__(): + continue + yield ip diff --git a/discovery/kubelet.py b/discovery/kubelet.py new file mode 100644 index 0000000..cb4e91a --- /dev/null +++ b/discovery/kubelet.py @@ -0,0 +1,35 @@ +import json +import logging +import urllib3 +from enum import Enum + +import requests + +from events import ReadOnlyKubeletEvent, SecureKubeletEvent, OpenPortEvent, handler + +class KubeletPorts(Enum): + SECURED = 10250 + READ_ONLY = 10255 + +@handler.subscribe(OpenPortEvent, predicate= lambda x: x.port == 10255 or x.port == 10250) +class KubeletDiscovery(object): + def __init__(self, event): + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + self.event = event + + @property + def read_only_access(self): + r = requests.get("http://{host}:{port}/pods".format(host=self.event.host, port=self.event.port)) + return r.status_code == 200 + + @property + def secure_access(self): + r = requests.get("https://{host}:{port}/pods".format(host=self.event.host, port=self.event.port), verify=False) + return r.status_code == 200 + + def execute(self): + logging.debug("secure port on {}".format(self.event.port)) + if self.event.port == KubeletPorts.SECURED.value and self.secure_access: + handler.publish_event(SecureKubeletEvent()) + elif self.event.port == KubeletPorts.READ_ONLY.value and self.read_only_access: + handler.publish_event(ReadOnlyKubeletEvent()) \ No newline at end of file diff --git a/discovery/ports.py b/discovery/ports.py index 19acc09..ee50985 100644 --- a/discovery/ports.py +++ b/discovery/ports.py @@ -5,14 +5,15 @@ default_ports = [8001, 10250, 10255, 30000] @handler.subscribe(NewHostEvent) class PortDiscovery(object): - def __init__(self, task): - self.host = task.host - self.port = task.port + def __init__(self, event): + self.event = event + self.host = event.host + self.port = event.port def execute(self): for single_port in default_ports: if self.test_connection(self.host, single_port): - handler.publish_event(OpenPortEvent(host=self.host, port=single_port)) + handler.publish_event(OpenPortEvent(port=single_port)) @staticmethod def test_connection(host, port): diff --git a/discovery/proxy.py b/discovery/proxy.py index bbcc5c6..66fc0f8 100644 --- a/discovery/proxy.py +++ b/discovery/proxy.py @@ -5,10 +5,10 @@ import logging @handler.subscribe(OpenPortEvent, predicate=lambda x: x.port == 8001) class KubeProxy(object): - def __init__(self, task): - self.task = task - self.host = task.host - self.port = task.port or 8001 + def __init__(self, event): + self.event = event + self.host = event.host + self.port = event.port or 8001 @property def accesible(self): @@ -16,5 +16,5 @@ class KubeProxy(object): def execute(self): if self.accesible: - handler.publish_event(KubeProxyEvent(host=self.host, port=self.port)) + handler.publish_event(KubeProxyEvent()) \ No newline at end of file diff --git a/events/default_types.py b/events/default_types.py index e5076f4..88bdad6 100644 --- a/events/default_types.py +++ b/events/default_types.py @@ -1,41 +1,61 @@ -# class EventMeta(type): -# def __init__(cls, name, bases, dct): -# super(EventMeta, cls).__init__(name, bases, dct) +import logging -""" Parent Event Objects """ -class NetworkEvent(object): - # __metaclass__ = EventMeta - def __init__(self, host, port): - self.host = host - self.port = port +class Event(object): + def __init__(self): + self.previous = None + + # newest attribute gets selected first + def __getattr__(self, name): + if name == "previous": + return None + for event in self.history: + if name in event.__dict__: + return event.__dict__[name] + + # returns the event history ordered from newest to oldest + @property + def history(self): + previous, history = self.previous, list() + while previous: + history.append(previous) + previous = previous.previous + return history -class ServiceEvent(NetworkEvent): - # __metaclass__ = EventMeta - def __init__(self, secure, location, host, port): - super(ServiceEvent, self).__init__(host=host, port=port) - self.secure = secure - self.location = location """ Event Objects """ -class NewHostEvent(NetworkEvent): - def __init__(self, host, port=0): - super(NewHostEvent, self).__init__(port=port, host=host) +class NewHostEvent(Event): + def __init__(self, host): + self.host = host def __str__(self): return str(self.host) -class OpenPortEvent(NetworkEvent): - def __init__(self, host, port): - super(OpenPortEvent, self).__init__(port=port, host=host) - +class OpenPortEvent(Event): + def __init__(self, port): + self.port = port + def __str__(self): return str(self.port) -class KubeProxyEvent(ServiceEvent): - def __init__(self, host, port=8001, secure=True, location=""): - super(KubeProxyEvent, self).__init__(secure=secure, location=location, host=host, port=port) +class HostScanEvent(Event): + def __init__(self, interal=True, localhost=True): + self.internal = interal + self.localhost = localhost -class KubeDashboardEvent(ServiceEvent): - def __init__(self, host, secure=True, port=30000, location=""): - super(KubeDashboardEvent, self).__init__(location=location, secure=secure, host=host, port=port) - \ No newline at end of file +class KubeDashboardEvent(Event): + def __init__(self, path="/", secure=False): + self.path = path + self.secure + pass + +class ReadOnlyKubeletEvent(Event): + def __init__(self): + pass + +class SecureKubeletEvent(Event): + def __init__(self): + pass + +class KubeProxyEvent(Event): + def __init__(self): + pass diff --git a/events/handler.py b/events/handler.py index 408a3bf..d9275e7 100644 --- a/events/handler.py +++ b/events/handler.py @@ -1,9 +1,9 @@ +import inspect import logging -from Queue import Queue -from threading import Thread -from collections import defaultdict -from threading import Lock from abc import ABCMeta +from collections import defaultdict +from Queue import Queue +from threading import Lock, Thread # Inherits Queue object, handles events asynchronously class EventQueue(Queue, object): @@ -38,6 +38,13 @@ class EventQueue(Queue, object): for hook, predicate in self.hooks[event_name]: if predicate and not predicate(event): continue + + # access to stack frame, can also be implemented by changing the function call to recieve self. + # TODO: decide whether invisibility to the developer is the best approach + last_frame = inspect.stack()[1][0] + if "self" in last_frame.f_locals: + event.previous = last_frame.f_locals["self"].event + self.put(hook(event)) # executes callbacks on dedicated thread as a daemon @@ -54,4 +61,5 @@ class EventQueue(Queue, object): self.queue.clear() -handler = EventQueue(800) \ No newline at end of file +handler = EventQueue(800) + diff --git a/hunting/dashboard.py b/hunting/dashboard.py index a576f53..28cb360 100644 --- a/hunting/dashboard.py +++ b/hunting/dashboard.py @@ -4,21 +4,20 @@ import requests @handler.subscribe(KubeDashboardEvent) class KubeDashboard(object): - def __init__(self, task): - self.task = task + def __init__(self, event): + self.event = event @property def accessible(self): - protocol = "https" if self.task.secure else "http" - r = requests.get("{protocol}://{host}:{port}/{loc}".format(protocol=protocol, host=self.task.host, port=self.task.port, loc=self.task.location)) + protocol = "https" if self.event.secure else "http" + r = requests.get("{protocol}://{host}:{port}{loc}".format(protocol=protocol, host=self.event.host, port=self.event.port, loc=self.event.path)) return r.status_code == 200 def execute(self): if not self.accessible: return - if self.task.secure: - logging.info("SECURED DASHBOARD AT {}:{}/{}".format(self.task.host, self.task.port, self.task.location)) + if self.event.secure: + logging.info("[OPEN SERVICE] SECURE DASHBOARD - {}:{}{}".format(self.event.host, self.event.port, self.event.path)) else: - logging.info("INSECURED DASHBOARD AT {}:{}/{}".format(self.task.host, self.task.port, self.task.location)) - + logging.info("[OPEN SERVICE] INSECURE DASHBOARD - {}:{}{}".format(self.event.host, self.event.port, self.event.path)) diff --git a/hunting/kubelet.py b/hunting/kubelet.py new file mode 100644 index 0000000..c207c4b --- /dev/null +++ b/hunting/kubelet.py @@ -0,0 +1,20 @@ +import logging + +from events import handler, ReadOnlyKubeletEvent, SecureKubeletEvent + +""" dividing ports for seperate hunters """ +@handler.subscribe(ReadOnlyKubeletEvent) +class ReadOnlyKubeletPortHunter(object): + def __init__(self, event): + self.event = event + + def execute(self): + logging.info("[OPEN SERVICE] INSECURED KUBELET API - {}:{}".format(self.event.host, self.event.port)) + +@handler.subscribe(SecureKubeletEvent) +class SecurePortKubeletHunter(object): + def __init__(self, event): + self.event = event + + def execute(self): + logging.info("[OPEN SERVICE] SECURED KUBELET API - {}:{}".format(self.event.host, self.event.port)) \ No newline at end of file diff --git a/hunting/proxy.py b/hunting/proxy.py index 4627ecc..b67d17d 100644 --- a/hunting/proxy.py +++ b/hunting/proxy.py @@ -8,16 +8,16 @@ class Service(Enum): @handler.subscribe(KubeProxyEvent) class KubeProxy(object): - def __init__(self, task): - self.task = task - self.api_url = "http://{host}:{port}/api/v1".format(host=self.task.host, port=self.task.port) + def __init__(self, event): + self.event = event + self.api_url = "http://{host}:{port}/api/v1".format(host=self.event.host, port=self.event.port) def execute(self): for namespace, services in self.services.items(): for service in services: curr_path = "api/v1/namespaces/{ns}/services/{sv}/proxy".format(ns=namespace,sv=service) # TODO: check if /proxy is a convention on other services if service == Service.DASHBOARD.value: - handler.publish_event(KubeDashboardEvent(host=self.task.host, port=self.task.port, location=curr_path, secure=False)) + handler.publish_event(KubeDashboardEvent(path=curr_path, secure=False)) @property def namespaces(self): diff --git a/kube-hunter.py b/kube-hunter.py index 47f917b..0c5fd17 100644 --- a/kube-hunter.py +++ b/kube-hunter.py @@ -1,7 +1,7 @@ import log -from events import handler -import discovery +from events import handler, HostScanEvent +from discovery import HostDiscovery import hunting import time import sys @@ -10,7 +10,7 @@ import logging def main(): logging.info("Started") try: - discovery.HostDiscovery({}).execute() + handler.publish_event(HostScanEvent(interal=True, localhost=False)) # Blocking to see discovery output while(True): time.sleep(100) diff --git a/log/__init__.py b/log/__init__.py new file mode 100644 index 0000000..635cf56 --- /dev/null +++ b/log/__init__.py @@ -0,0 +1 @@ +from config import * \ No newline at end of file diff --git a/log.py b/log/config.py similarity index 99% rename from log.py rename to log/config.py index 829d2fa..3c74159 100644 --- a/log.py +++ b/log/config.py @@ -5,7 +5,6 @@ parser = argparse.ArgumentParser(description='Kubehunter, hunting weak kubernete parser.add_argument('--log', type=str, metavar="LOGLEVEL", default='INFO', help="set output level, options are:\nDEBUG INFO WARNING") args = parser.parse_args() - try: loglevel = getattr(logging, args.log.upper()) except: