diff --git a/kube-hunter.py b/kube-hunter.py index 4245c50..95b250d 100644 --- a/kube-hunter.py +++ b/kube-hunter.py @@ -20,12 +20,12 @@ import log import modules from modules.discovery import HostDiscovery from modules.events import handler -from modules.events.types import HostScanEvent +from modules.discovery.hosts import HostScanEvent def main(): logging.info("Started") try: - handler.publish_event(HostScanEvent(pod=args.pod)) + handler.publish_event(HostScanEvent(pod=args.pod, active=True)) # Blocking to see discovery output while(True): time.sleep(100) @@ -34,6 +34,7 @@ def main(): finally: handler.free() logging.debug("Cleaned Queue") + log.print_results() if __name__ == '__main__': main() diff --git a/log/reporter.py b/log/reporter.py index 201e54b..7040df4 100644 --- a/log/reporter.py +++ b/log/reporter.py @@ -1,6 +1,12 @@ import logging +from prettytable import PrettyTable from modules.events import handler -from modules.events.types import Vulnerability, ServiceEvent +from modules.events.types import Vulnerability, Information, Service +from modules.discovery.kubelet import KubeletExposedHandler + +services = list() +vulnerabilities = list() +informations = list() @handler.subscribe(Vulnerability) class VulnerabilityReport(object): @@ -9,11 +15,24 @@ class VulnerabilityReport(object): def execute(self): logging.info("[VULNERABILITY - {name}] {desc}".format( - name=self.vulnerability.name, - desc=self.vulnerability.explain(), + name=self.vulnerability.name, + desc=self.vulnerability.explain(), )) + vulnerabilities.append(self.vulnerability) -@handler.subscribe(ServiceEvent) +@handler.subscribe(Information) +class ClusterInformation(object): + def __init__(self, event): + self.information = event + + def execute(self): + logging.info("[INFORMATION - {name}] {desc}".format( + name=self.information.get_name(), + desc=self.information.explain(), + )) + informations.append(self.information) + +@handler.subscribe(Service) class OpenServiceReport(object): def __init__(self, event): self.service = event @@ -24,4 +43,22 @@ class OpenServiceReport(object): desc=self.service.desc, host=self.service.host, port=self.service.port - )) \ No newline at end of file + )) + services.append(self.service) + + + +def print_results(): + services_table = PrettyTable(["Service", "Location", "Description"]) + for service in services: + services_table.add_row([service.get_name(), "{}:{}".format(service.host, service.port), service.explain()]) + + vuln_table = PrettyTable(["Location", "From Component", "Vulnerability", "Description"]) + for vuln in vulnerabilities: + vuln_table.add_row(["{}:{}".format(vuln.host, vuln.port), vuln.component.name, vuln.get_name(), vuln.explain()]) + + print "\nOpen Services:" + print services_table + print "\nVulnerabilities:" + print vuln_table + \ No newline at end of file diff --git a/modules/discovery/dashboard.py b/modules/discovery/dashboard.py index cd4b9cf..f2c3321 100644 --- a/modules/discovery/dashboard.py +++ b/modules/discovery/dashboard.py @@ -1,10 +1,17 @@ -from ..types import Hunter +import json import requests from ..events import handler -from ..events.types import KubeDashboardEvent, OpenPortEvent +from ..events.types import Event, Service, OpenPortEvent +from ..types import Hunter +class KubeDashboardEvent(Service, Event): + """Allows multiple arbitrary operations on the cluster from all connections""" + def __init__(self, path="/", secure=False): + self.path = path + self.secure + Service.__init__(self, name="Kubernetes Dashboard") @handler.subscribe(OpenPortEvent, predicate=lambda x: x.port == 30000) class KubeDashboard(Hunter): @@ -15,8 +22,11 @@ class KubeDashboard(Hunter): @property def secure(self): - # TODO: insert logic for detremining a secure/insecure dashboard is there + default = json.loads(requests.get("http://{}:{}/api/v1/service/default".format(self.host, self.port)).text) + if "errors" in default and len(default["errors"]) == 0: + return False return False def execute(self): - self.publish_event(KubeDashboardEvent()) + if not self.secure: + self.publish_event(KubeDashboardEvent()) diff --git a/modules/discovery/hosts.py b/modules/discovery/hosts.py index 709209d..bc81a49 100644 --- a/modules/discovery/hosts.py +++ b/modules/discovery/hosts.py @@ -1,22 +1,34 @@ import logging import sys import time +import json from enum import Enum import requests from netaddr import IPNetwork -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) # disables scapy's warnings -from scapy.all import ICMP, IP, Ether, srp1 - from netifaces import AF_INET, ifaddresses, interfaces from ..events import handler -from ..events.types import HostScanEvent, NewHostEvent +from ..events.types import Event, NewHostEvent from ..types import Hunter -# for comparing prefixes -class InterfaceTypes(Enum): - LOCALHOST = "127" +class HostScanEvent(Event): + def __init__(self, pod=False, active=False): + self.pod = pod + self.active = active # flag to specify whether to get actual data from vulnerabilities + self.auth_token = self.get_auth_token() + self.client_cert = self.get_client_cert() + + def get_auth_token(self): + if self.pod: + with open("/run/secrets/kubernetes.io/serviceaccount/token") as token_file: + return token_file.read() + return None + + def get_client_cert(self): + if self.pod: + return "/run/secrets/kubernetes.io/serviceaccount/ca.crt" + return None @handler.subscribe(HostScanEvent) class HostDiscovery(Hunter): @@ -26,22 +38,50 @@ class HostDiscovery(Hunter): def execute(self): logging.info("Discovering Open Kubernetes Services...") if self.event.pod: - self.scan_nodes() + if self.is_azure_cluster(): + self.azure_metadata_discovery() + else: + self.traceroute_discovery() else: # self.publish_event(NewHostEvent(host="acs954agent1.westus2.cloudapp.azure.com")) # test cluster self.scan_interfaces() + def is_azure_cluster(self): + try: + if requests.get("http://169.254.169.254/metadata/instance?api-version=2017-08-01", headers={"Metadata":"true"}).status_code == 200: + return True + except Exception as ex: + logging.debug("Not azure cluster " + ex.message) + return False + # for pod scanning - def scan_nodes(self): + def traceroute_discovery(self): + logging.getLogger("scapy.runtime").setLevel(logging.ERROR) # disables scapy's warnings + from scapy.all import ICMP, IP, Ether, srp1 + node_internal_ip = srp1(Ether() / IP(dst="google.com" , ttl=1) / ICMP(), verbose=0)[IP].src for ip in self.generate_subnet(ip=node_internal_ip, sn="24"): self.publish_event(NewHostEvent(host=ip)) + # quering azure's interface metadata api | works only from a pod + def azure_metadata_discovery(self): + machine_metadata = json.loads(requests.get("http://169.254.169.254/metadata/instance?api-version=2017-08-01", headers={"Metadata":"true"}).text) + for interface in machine_metadata["network"]["interface"]: + address, subnet = interface["ipv4"]["subnet"][0]["address"], interface["ipv4"]["subnet"][0]["prefix"] + for ip in self.generate_subnet(address, sn=subnet): + self.publish_event(NewHostEvent(host=ip)) + # for normal scanning def scan_interfaces(self): for ip in self.generate_interfaces_subnet(): handler.publish_event(NewHostEvent(host=ip)) + # generator, generating a subnet by given a cidr + def generate_subnet(self, ip, sn="24"): + subnet = IPNetwork('{ip}/{sn}'.format(ip=ip, sn=sn)) + for ip in IPNetwork(subnet): + yield ip + # generate all subnets from all internal network interfaces def generate_interfaces_subnet(self, sn='24'): for ifaceName in interfaces(): @@ -50,9 +90,7 @@ class HostDiscovery(Hunter): continue for ip in self.generate_subnet(ip, sn): yield ip - - # generator, generating a subnet by given a cidr - def generate_subnet(self, ip, sn="24"): - subnet = IPNetwork('{ip}/{sn}'.format(ip=ip, sn=sn)) - for ip in IPNetwork(subnet): - yield ip \ No newline at end of file + +# for comparing prefixes +class InterfaceTypes(Enum): + LOCALHOST = "127" \ No newline at end of file diff --git a/modules/discovery/kubelet.py b/modules/discovery/kubelet.py index 7b33a11..10aec1e 100644 --- a/modules/discovery/kubelet.py +++ b/modules/discovery/kubelet.py @@ -7,10 +7,45 @@ import requests import urllib3 from ..events import handler -from ..events.types import (OpenPortEvent, ReadOnlyKubeletEvent, - SecureKubeletEvent, Vulnerability, Event) +from ..events.types import OpenPortEvent, Kubelet, Vulnerability, Event, Service urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +""" Services """ +class ReadOnlyKubeletEvent(Service, Event): + """Exposes specific handlers which disclose sensitive information about the cluster""" + def __init__(self): + Service.__init__(self, name="Kubelet API (readonly)") + +class SecureKubeletEvent(Service, Event): + """Exposes handlers that can perform unwanted operations on pods/containers""" + def __init__(self, cert=False, token=False): + self.cert = cert + self.token = token + Service.__init__(self, name="Kubelet API") + +""" Vulnerabilities """ +class PodsHandler: + """Exposes sensitive information about pods that are bound to the node""" + name="/pods" + +class KubeletExposedHandler(Vulnerability, Event): + def __init__(self, handler): + self.handler = handler + Vulnerability.__init__(self, Kubelet, "Handler Exposure") + + def get_name(self): + return "{} - {}".format(self.name, self.handler.name) + + def explain(self): + return self.handler.__doc__ + +class AnonymousAuthEnabled(Vulnerability, Event): + """Anonymous Auth to the kubelet, exposes secure access to all requests on the kubelet""" + def __init__(self): + Vulnerability.__init__(self, Kubelet, "Anonymous Authentication") + + def proof(self): + pass # TODO: decide on an appropriate proof @handler.subscribe(OpenPortEvent, predicate= lambda x: x.port == 10255 or x.port == 10250) class KubeletDiscovery(Hunter): @@ -21,13 +56,13 @@ class KubeletDiscovery(Hunter): logging.debug(self.event.host) r = requests.get("http://{host}:{port}/pods".format(host=self.event.host, port=self.event.port)) if r.status_code == 200: - self.publish_event(KubeletOpenHandler(handler="pods")) + self.publish_event(KubeletExposedHandler(handler=PodsHandler)) self.publish_event(ReadOnlyKubeletEvent()) def get_secure_access(self): event = SecureKubeletEvent() if self.ping_kubelet(authenticate=False) == 200: - self.publish_event(KubeletOpenHandler(handler="pods")) + self.publish_event(KubeletExposedHandler(handler=PodsHandler)) self.publish_event(AnonymousAuthEnabled()) event.anonymous_auth = True # anonymous authentication is disabled @@ -45,8 +80,11 @@ class KubeletDiscovery(Hunter): if self.event.client_cert: r.cert = self.event.client_cert r.verify = False - return r.get("https://{host}:{port}/pods".format(host=self.event.host, port=self.event.port)).status_code - + try: + return r.get("https://{host}:{port}/pods".format(host=self.event.host, port=self.event.port)).status_code + except Exception as ex: + logging.debug("Failed pinging secured kubelet {} : {}".format(self.event.host, ex.message)) + def execute(self): if self.event.port == KubeletPorts.SECURED.value: self.get_secure_access() @@ -54,23 +92,6 @@ class KubeletDiscovery(Hunter): self.get_read_only_access() -""" Types """ -class KubeletOpenHandler(Vulnerability, Event): - def __init__(self, handler, **kargs): - self.handler = handler - Vulnerability.__init__(self, name="Kubelet Exposure", **kargs) - - def explain(self): - return "Handler - {}/ Kubelet Api - {}:{}".format(self.handler, self.host, self.port) - - -class AnonymousAuthEnabled(Vulnerability, Event): - def __init__(self, **kargs): - Vulnerability.__init__(self, name="Anonymous Authentication", **kargs) - - def explain(self): - return "Kubelet - {}:{}".format(self.host, self.port) - class KubeletPorts(Enum): SECURED = 10250 READ_ONLY = 10255 diff --git a/modules/discovery/proxy.py b/modules/discovery/proxy.py index db40b1b..d5d3f48 100644 --- a/modules/discovery/proxy.py +++ b/modules/discovery/proxy.py @@ -5,8 +5,11 @@ from ..types import Hunter from requests import get from ..events import handler -from ..events.types import KubeProxyEvent, OpenPortEvent +from ..events.types import Service, Event, OpenPortEvent +class KubeProxyEvent(Event, Service): + def __init__(self): + Service.__init__(self, name="Kubernetes Proxy") @handler.subscribe(OpenPortEvent, predicate=lambda x: x.port == 8001) class KubeProxy(Hunter): diff --git a/modules/events/handler.py b/modules/events/handler.py index 26a0111..c422923 100644 --- a/modules/events/handler.py +++ b/modules/events/handler.py @@ -5,6 +5,9 @@ from collections import defaultdict from Queue import Queue from threading import Lock, Thread +working_count = 0 +lock = Lock() + # Inherits Queue object, handles events asynchronously class EventQueue(Queue, object): def __init__(self, num_worker=10): diff --git a/modules/events/types/__init__.py b/modules/events/types/__init__.py index a01c21a..7be8215 100644 --- a/modules/events/types/__init__.py +++ b/modules/events/types/__init__.py @@ -2,7 +2,6 @@ from os.path import dirname, basename, isfile import glob from common import * -from information import * # dynamically importing all modules in folder files = glob.glob(dirname(__file__)+"/*.py") diff --git a/modules/events/types/common.py b/modules/events/types/common.py index 4af1f27..6b67d93 100644 --- a/modules/events/types/common.py +++ b/modules/events/types/common.py @@ -22,31 +22,68 @@ class Event(object): return history -""" Information Fathers """ -# TODO: make explain an abstract method. -class ServiceEvent(object): - def __init__(self, name, data=""): +"""Kubernetes Components""" +class KubernetesCluster(): + """Kubernetes Cluster""" + name = "Kubernetes Cluster" + +class Kubelet(KubernetesCluster): + """The kubelet is the primary "node agent" that runs on each node""" + name = "Kubelet" + + +""" Event Types """ +# TODO: make proof an abstract method. +class Service(object): + def __init__(self, name): self.name = name - self.data = data + + def get_name(self): + return self.name def explain(self): - return self.data + return self.__doc__ + + def proof(self): + return self.name class Vulnerability(object): - def __init__(self, name, data=""): + def __init__(self, component, name): + self.component = component self.name = name - self.data = data + + def get_name(self): + return self.name def explain(self): - return self.data - + return self.__doc__ + + def proof(self): + return self.name + +class Information(object): + def __init__(self, name): + self.name = name + + def get_name(self): + return self.name + + def explain(self): + return self.__doc__ + + def proof(self): + return self.name +event_id_count = 0 """ Discovery/Hunting Events """ class NewHostEvent(Event): def __init__(self, host): + global event_id_count self.host = host - + self.id = event_id_count + event_id_count += 1 + def __str__(self): return str(self.host) @@ -56,40 +93,3 @@ class OpenPortEvent(Event): def __str__(self): return str(self.port) - -class HostScanEvent(Event): - def __init__(self, pod=False): - self.pod = pod - self.auth_token = self.get_auth_token() - self.client_cert = self.get_client_cert() - - def get_auth_token(self): - if self.pod: - with open("/run/secrets/kubernetes.io/serviceaccount/token") as token_file: - return token_file.read() - return None - - def get_client_cert(self): - if self.pod: - return "/run/secrets/kubernetes.io/serviceaccount/ca.crt" - return None - - -class KubeDashboardEvent(Event, ServiceEvent): - def __init__(self, path="/", secure=False): - self.path = path - self.secure - -class ReadOnlyKubeletEvent(Event, ServiceEvent): - def __init__(self): - ServiceEvent.__init__(self, name="Kubelet API (readonly)") - -class SecureKubeletEvent(Event, ServiceEvent): - def __init__(self, cert=False, token=False): - self.cert = cert - self.token = token - ServiceEvent.__init__(self, name="Kubelet API") - -class KubeProxyEvent(Event, ServiceEvent): - def __init__(self): - ServiceEvent.__init__(self, name="Kubernetes Proxy") \ No newline at end of file diff --git a/modules/events/types/information.py b/modules/events/types/information.py deleted file mode 100644 index 57adaca..0000000 --- a/modules/events/types/information.py +++ /dev/null @@ -1,11 +0,0 @@ -from common import Event - -class Vulnerability(object): - """ Information Events """ - # this kind of events will be triggered when important information is discovered - def __init__(self, name, data=""): - self.name = name - self.data = data - - def explain(self): - return self.data diff --git a/modules/hunting/dashboard.py b/modules/hunting/dashboard.py index 446375e..fb5c246 100644 --- a/modules/hunting/dashboard.py +++ b/modules/hunting/dashboard.py @@ -4,24 +4,13 @@ from ..types import Hunter import requests from ..events import handler -from ..events.types import Vulnerability, Event, KubeDashboardEvent +from ..discovery.dashboard import KubeDashboardEvent @handler.subscribe(KubeDashboardEvent) class KubeDashboard(Hunter): def __init__(self, event): self.event = event - - @property - def accessible(self): - 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.event.secure: - # logging.info("[OPEN SERVICE] SECURE DASHBOARD - {}:{}{}".format(self.event.host, self.event.port, self.event.path)) - # else: - # logging.info("[OPEN SERVICE] INSECURE DASHBOARD - {}:{}{}".format(self.event.host, self.event.port, self.event.path)) + # TODO: implement dashboard hunting + pass \ No newline at end of file diff --git a/modules/hunting/kubelet.py b/modules/hunting/kubelet.py index 48fb8a9..4a45dcc 100644 --- a/modules/hunting/kubelet.py +++ b/modules/hunting/kubelet.py @@ -6,21 +6,97 @@ import requests import urllib3 from ..events import handler -from ..events.types import (Vulnerability, Event, ReadOnlyKubeletEvent, - SecureKubeletEvent) -from ..discovery.kubelet import KubeletOpenHandler +from ..events.types import (KubernetesCluster, Kubelet, Vulnerability, Information, Event) +from ..discovery.kubelet import KubeletExposedHandler, ReadOnlyKubeletEvent, SecureKubeletEvent from ..types import Hunter urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +class ContainerLogsHandler: + """Outputs logs from a running container""" + name="/containerlogs" + remediation="--enable-debugging-handlers=False On Kubelet" + +class RunningPodsHandler: + """Outputs a list of currently runnning pods, and some of their metadata""" + name="/runningpods" + remediation="--enable-debugging-handlers=False On Kubelet" + +class ExecHandler: + """Opens a websocket that enables running and executing arbitrary commands on a container""" + name="/exec" + remediation="--enable-debugging-handlers=False On Kubelet" + +class RunHandler: + """Allows remote arbitrary execution inside a container""" + name="/run" + remediation="--enable-debugging-handlers=False On Kubelet" + +class PortForwardHandler: + """Setting a port forwaring rule on a pod""" + name="/portForward" + remediation="--enable-debugging-handlers=False On Kubelet" + +class AttachHandler: + """Opens a websocket that enables running and executing arbitrary commands on a container""" + name="/attach" + remediation="--enable-debugging-handlers=False On Kubelet" + + +""" Vulnerabilities """ +class K8sVersionDisclosure(Vulnerability, Event): + """Discloses the kubernetes version, exposed from a log on the /metrics endpoint""" + def __init__(self, version): + Vulnerability.__init__(self, Kubelet, "Version Disclosure") + self.version = version + + def proof(self): + return self.version + +class PrivilegedContainers(Vulnerability, Event): + """A priviledged container on a node, can expose the node/cluster to unwanted root operations""" + def __init__(self, containers): + Vulnerability.__init__(self, KubernetesCluster, "Priviledged Container") + self.containers = containers + + def proof(self): + return self.containers + + """ dividing ports for seperate hunters """ @handler.subscribe(ReadOnlyKubeletEvent) class ReadOnlyKubeletPortHunter(Hunter): def __init__(self, event): self.event = event + self.path = "http://{}:{}/".format(self.event.host, self.event.port) + + def get_k8s_version(self): + metrics = requests.get(self.path + "metrics").text + for line in metrics.split("\n"): + if line.startswith("kubernetes_build_info"): + for info in line[line.find('{') + 1: line.find('}')].split(','): + k, v = info.split("=") + if k == "gitVersion": + return v.strip("\"") + + # returns list of tuples of priviledged container and their pod. + def find_privileged_containers(self): + pods = json.loads(requests.get(self.path + "pods").text) + privileged_containers = list() + if "items" in pods: + for pod in pods["items"]: + for container in pod["spec"]["containers"]: + if "securityContext" in container and "privileged" in container["securityContext"] and container["securityContext"]["privileged"]: + privileged_containers.append((pod["metadata"]["name"], container["name"])) + return privileged_containers if len(privileged_containers) > 0 else None def execute(self): - pass + k8s_version = self.get_k8s_version() + privileged_containers = self.find_privileged_containers() + if k8s_version: + self.publish_event(K8sVersionDisclosure(version=k8s_version)) + if privileged_containers: + self.publish_event(PrivilegedContainers(containers=privileged_containers)) @handler.subscribe(SecureKubeletEvent) class SecureKubeletPortHunter(Hunter): @@ -53,7 +129,7 @@ class SecureKubeletPortHunter(Hunter): containerName=self.pod.container ) if self.session.get(logs_url, verify=False).status_code == 200: - return self.Handlers.CONTAINERLOGS.name + return ContainerLogsHandler # need further investigation on websockets protocol for further implementation def test_exec_container(self): @@ -63,10 +139,10 @@ class SecureKubeletPortHunter(Hunter): podNamespace = self.pod.namespace, podID = self.pod.name, containerName = self.pod.container, - cmd = "uname" + cmd = "uname -a" ) if "/cri/exec/" in self.session.get(exec_url, headers=headers, allow_redirects=False ,verify=False).text: - return self.Handlers.EXEC.name + return ExecHandler # need further investigation on websockets protocol for further implementation def test_port_forward(self): @@ -92,17 +168,17 @@ class SecureKubeletPortHunter(Hunter): podNamespace = self.pod.namespace, podID = self.pod.name, containerName = self.pod.container, - cmd = "echo check" + cmd = "uname -a" ) output = requests.post(run_url, allow_redirects=False ,verify=False).text if "echo" not in output and "check" in output: - return self.Handlers.EXEC.name + return RunHandler # returns list of currently running pods def test_running_pods(self): pods_url = self.path + self.Handlers.RUNNINGPODS.value if 'items' in json.loads(self.session.get(pods_url, verify=False).text).keys(): - return self.Handlers.RUNNINGPODS.name + return RunningPodsHandler # need further investigation on the differences between attach and exec def test_attach_container(self): @@ -111,17 +187,17 @@ class SecureKubeletPortHunter(Hunter): podNamespace = self.pod.namespace, podID = self.pod.name, containerName = self.pod.container, - cmd = "uname" + cmd = "uname -a" ) if "/cri/attach/" in self.session.get(attach_url, allow_redirects=False ,verify=False).text: - return self.Handlers.ATTACH.name + return AttachHandler def __init__(self, event): self.event = event self.session = requests.Session() if self.event.secure: - self.session.headers.update({"Authorization": "Bearer {}".format(self.event.auth_token)}) - self.session.cert = self.event.client_cert + self.session.headers.update({"Authorization": "Bearer {}".format(self.event.auth_token)}) + self.session.cert = self.event.client_cert self.path = "https://{}:{}/".format(self.event.host, 10250) def execute(self): @@ -140,10 +216,14 @@ class SecureKubeletPortHunter(Hunter): debug_handlers.test_attach_container ] for test_handler in test_list: - handler_name = test_handler() - if handler_name: - self.publish_event(KubeletOpenHandler(handler=handler_name.lower())) - + try: + handler = test_handler() + if handler: + self.publish_event(KubeletExposedHandler(handler=handler)) + except Exception as ex: + logging.debug("Failed getting handler: {}".format(test_handler)) + pass + def get_self_pod(self): return {"name": "kube-hunter", "namespace": "default", @@ -166,6 +246,26 @@ class SecureKubeletPortHunter(Hunter): "namespace": "default" } + +""" Active Hunting Of Handlers""" +@handler.subscribe(KubeletExposedHandler, predicate=lambda x: x.handler=="exec" and x.active) +class ActiveExecHandler(Hunter): + def __init__(self, event): + self.event = Event + + def execute(self): + pass + +@handler.subscribe(KubeletExposedHandler, predicate=lambda x: x.handler=="run" and x.active) +class ActiveRunHandler(Hunter): + def __init__(self, event): + self.event = Event + + def execute(self): + pass + + + # def get_kubesystem_pod_container(self): # pods_data = json.loads(requests.get("https://{host}:{port}/pods".format(host=self.event.host, port=self.event.port), verify=False).text)['items'] # # filter running kubesystem pod diff --git a/modules/hunting/proxy.py b/modules/hunting/proxy.py index 60bf616..36565b5 100644 --- a/modules/hunting/proxy.py +++ b/modules/hunting/proxy.py @@ -4,7 +4,8 @@ from ..types import Hunter from requests import get from ..events import handler -from ..events.types import KubeDashboardEvent, KubeProxyEvent +from ..discovery.dashboard import KubeDashboardEvent +from ..discovery.proxy import KubeProxyEvent class Service(Enum): DASHBOARD = "kubernetes-dashboard" diff --git a/requirements.txt b/requirements.txt index 48ff2d8..d574476 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ netaddr netifaces enum34 scapy -requests \ No newline at end of file +requests +PrettyTable \ No newline at end of file