mirror of
https://github.com/aquasecurity/kube-hunter.git
synced 2026-05-09 10:47:10 +00:00
1. Changed order of modules and pacakges in directories.
2. Changed method of hidden stacking of event, to send self as an argument, by inheriting from "Hunter" class. where the publish acts as a proxy to the handler. 3. Added new way of categorizing events, while added an option to subscribe to a father event. if en event gets publish, if its father event is hooked, the hook will be triggered 4. Added a reporter in log/ which listens to parent events, meanwhile Vulnerability and OpenService were added. all logging will be made from reporter from now on
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
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))
|
||||
@@ -1,12 +1,17 @@
|
||||
#!/bin/env python
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
|
||||
import log
|
||||
|
||||
from events import handler, HostScanEvent
|
||||
from discovery import HostDiscovery
|
||||
import hunting
|
||||
import time
|
||||
import sys
|
||||
import logging
|
||||
# executes all registrations from sub packages
|
||||
import modules
|
||||
|
||||
from modules.discovery import HostDiscovery
|
||||
from modules.events import handler
|
||||
from modules.events.types import HostScanEvent
|
||||
|
||||
|
||||
def main():
|
||||
logging.info("Started")
|
||||
@@ -22,4 +27,4 @@ def main():
|
||||
logging.debug("Cleaned Queue")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
from config import *
|
||||
from config import *
|
||||
from reporter import *
|
||||
27
log/reporter.py
Normal file
27
log/reporter.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import logging
|
||||
from modules.events import handler
|
||||
from modules.events.types import Vulnerability, ServiceEvent
|
||||
|
||||
@handler.subscribe(Vulnerability)
|
||||
class VulnerabilityReport(object):
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
|
||||
def execute(self):
|
||||
vulnerability_type = self.event.__class__.__name__.replace("Vulnerability", "")
|
||||
logging.info("[VULNERABILITY - {type}] - {desc} | location: {host}:{port}".format(type=vulnerability_type,
|
||||
desc=self.event.desc,
|
||||
host=self.event.host,
|
||||
port=self.event.port))
|
||||
|
||||
@handler.subscribe(ServiceEvent)
|
||||
class OpenServiceReport(object):
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
|
||||
def execute(self):
|
||||
service_name = self.event.__class__.__name__.replace("Event", "")
|
||||
logging.info("[OPEN SERVICE - {name}] location: {host}:{port}".format(name=service_name,
|
||||
desc=self.event.desc,
|
||||
host=self.event.host,
|
||||
port=self.event.port))
|
||||
4
modules/__init__.py
Normal file
4
modules/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
import discovery
|
||||
import hunting
|
||||
import events
|
||||
import types
|
||||
@@ -1,8 +1,13 @@
|
||||
from events import handler, OpenPortEvent, KubeDashboardEvent
|
||||
from ..types import Hunter
|
||||
|
||||
import requests
|
||||
|
||||
from ..events import handler
|
||||
from ..events.types import KubeDashboardEvent, OpenPortEvent
|
||||
|
||||
|
||||
@handler.subscribe(OpenPortEvent, predicate=lambda x: x.port == 30000)
|
||||
class KubeDashboard(object):
|
||||
class KubeDashboard(Hunter):
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
self.host = event.host
|
||||
@@ -14,4 +19,4 @@ class KubeDashboard(object):
|
||||
return False
|
||||
|
||||
def execute(self):
|
||||
handler.publish_event(KubeDashboardEvent())
|
||||
self.publish_event(KubeDashboardEvent())
|
||||
@@ -2,18 +2,21 @@ import logging
|
||||
import sys
|
||||
import time
|
||||
from enum import Enum
|
||||
from ..types import Hunter
|
||||
|
||||
from netaddr import IPNetwork
|
||||
|
||||
from events import HostScanEvent, NewHostEvent, handler
|
||||
from ..events import handler
|
||||
from ..events.types import HostScanEvent, NewHostEvent
|
||||
from netifaces import AF_INET, ifaddresses, interfaces
|
||||
|
||||
|
||||
# for comparing prefixes
|
||||
class InterfaceTypes(Enum):
|
||||
LOCALHOST = "127.0.0"
|
||||
|
||||
@handler.subscribe(HostScanEvent)
|
||||
class HostDiscovery(object):
|
||||
class HostDiscovery(Hunter):
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
# self.external = event.external
|
||||
@@ -21,10 +24,10 @@ class HostDiscovery(object):
|
||||
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():
|
||||
for ip in self.generate_addresses(ifaceName):
|
||||
handler.publish_event(NewHostEvent(host=ip))
|
||||
self.publish_event(NewHostEvent(host="acs954agent1.westus2.cloudapp.azure.com")) # test cluster
|
||||
# for ifaceName in interfaces():
|
||||
# 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, [])]:
|
||||
@@ -32,4 +35,4 @@ class HostDiscovery(object):
|
||||
for ip in IPNetwork(subnet):
|
||||
if not self.event.localhost and InterfaceTypes.LOCALHOST.value in ip.__str__():
|
||||
continue
|
||||
yield ip
|
||||
yield ip
|
||||
@@ -1,24 +1,30 @@
|
||||
import json
|
||||
import logging
|
||||
import urllib3
|
||||
from enum import Enum
|
||||
from ..types import Hunter
|
||||
|
||||
import requests
|
||||
import urllib3
|
||||
|
||||
from ..events import handler
|
||||
from ..events.types import (OpenPortEvent, ReadOnlyKubeletEvent,
|
||||
SecureKubeletEvent)
|
||||
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
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):
|
||||
class KubeletDiscovery(Hunter):
|
||||
def __init__(self, event):
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
self.event = event
|
||||
|
||||
@property
|
||||
def read_only_access(self):
|
||||
logging.debug(self.event.host)
|
||||
r = requests.get("http://{host}:{port}/pods".format(host=self.event.host, port=self.event.port))
|
||||
return r.status_code == 200
|
||||
|
||||
@@ -30,6 +36,6 @@ class KubeletDiscovery(object):
|
||||
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())
|
||||
self.publish_event(SecureKubeletEvent())
|
||||
elif self.event.port == KubeletPorts.READ_ONLY.value and self.read_only_access:
|
||||
handler.publish_event(ReadOnlyKubeletEvent())
|
||||
self.publish_event(ReadOnlyKubeletEvent())
|
||||
@@ -1,10 +1,14 @@
|
||||
from events import handler, NewHostEvent, OpenPortEvent
|
||||
from socket import socket
|
||||
from ..types import Hunter
|
||||
|
||||
from ..events import handler
|
||||
from ..events.types import NewHostEvent, OpenPortEvent
|
||||
|
||||
|
||||
default_ports = [8001, 10250, 10255, 30000]
|
||||
|
||||
@handler.subscribe(NewHostEvent)
|
||||
class PortDiscovery(object):
|
||||
class PortDiscovery(Hunter):
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
self.host = event.host
|
||||
@@ -13,7 +17,7 @@ class PortDiscovery(object):
|
||||
def execute(self):
|
||||
for single_port in default_ports:
|
||||
if self.test_connection(self.host, single_port):
|
||||
handler.publish_event(OpenPortEvent(port=single_port))
|
||||
self.publish_event(OpenPortEvent(port=single_port))
|
||||
|
||||
@staticmethod
|
||||
def test_connection(host, port):
|
||||
@@ -1,10 +1,15 @@
|
||||
from events import handler, OpenPortEvent, KubeProxyEvent
|
||||
from collections import defaultdict
|
||||
from requests import get
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from ..types import Hunter
|
||||
|
||||
from requests import get
|
||||
|
||||
from ..events import handler
|
||||
from ..events.types import KubeProxyEvent, OpenPortEvent
|
||||
|
||||
|
||||
@handler.subscribe(OpenPortEvent, predicate=lambda x: x.port == 8001)
|
||||
class KubeProxy(object):
|
||||
class KubeProxy(Hunter):
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
self.host = event.host
|
||||
@@ -16,5 +21,4 @@ class KubeProxy(object):
|
||||
|
||||
def execute(self):
|
||||
if self.accesible:
|
||||
handler.publish_event(KubeProxyEvent())
|
||||
|
||||
self.publish_event(KubeProxyEvent())
|
||||
2
modules/events/__init__.py
Normal file
2
modules/events/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from handler import *
|
||||
import types
|
||||
@@ -26,26 +26,22 @@ class EventQueue(Queue, object):
|
||||
|
||||
# getting uninstantiated event object
|
||||
def subscribe_event(self, event, hook=None, predicate=None):
|
||||
logging.debug('{} subscribed to {}'.format(event.__name__, hook))
|
||||
if hook not in self.hooks[event.__name__]:
|
||||
self.hooks[event.__name__].append((hook, predicate))
|
||||
logging.debug('{} subscribed to {}'.format(event, hook))
|
||||
if hook not in self.hooks[event]:
|
||||
self.hooks[event].append((hook, predicate))
|
||||
|
||||
# getting instantiated event object
|
||||
def publish_event(self, event):
|
||||
logging.debug('Event {} got published with {}'.format(event.__class__.__name__, event))
|
||||
event_name = event.__class__.__name__
|
||||
if event_name in self.hooks:
|
||||
for hook, predicate in self.hooks[event_name]:
|
||||
if predicate and not predicate(event):
|
||||
continue
|
||||
def publish_event(self, event, caller=None):
|
||||
logging.debug('Event {} got published with {}'.format(event.__class__, event))
|
||||
for hooked_event in self.hooks.keys():
|
||||
if hooked_event in event.__class__.__mro__:
|
||||
for hook, predicate in self.hooks[hooked_event]:
|
||||
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))
|
||||
if caller:
|
||||
event.previous = caller.event
|
||||
self.put(hook(event))
|
||||
|
||||
# executes callbacks on dedicated thread as a daemon
|
||||
def worker(self):
|
||||
@@ -53,6 +49,7 @@ class EventQueue(Queue, object):
|
||||
hook = self.get()
|
||||
hook.execute()
|
||||
self.task_done()
|
||||
logging.debug("closing thread...")
|
||||
|
||||
# stops execution of all daemons
|
||||
def free(self):
|
||||
@@ -62,4 +59,3 @@ class EventQueue(Queue, object):
|
||||
|
||||
|
||||
handler = EventQueue(800)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from os.path import dirname, basename, isfile
|
||||
import glob
|
||||
|
||||
# explicitly importing the event handler
|
||||
from handler import handler
|
||||
from common import *
|
||||
from information import *
|
||||
|
||||
# dynamically importing all modules in folder
|
||||
files = glob.glob(dirname(__file__)+"/*.py")
|
||||
@@ -21,8 +21,11 @@ class Event(object):
|
||||
previous = previous.previous
|
||||
return history
|
||||
|
||||
class ServiceEvent(object):
|
||||
pass
|
||||
|
||||
""" Event Objects """
|
||||
|
||||
""" Discovery/Hunting Events """
|
||||
class NewHostEvent(Event):
|
||||
def __init__(self, host):
|
||||
self.host = host
|
||||
@@ -42,20 +45,20 @@ class HostScanEvent(Event):
|
||||
self.internal = interal
|
||||
self.localhost = localhost
|
||||
|
||||
class KubeDashboardEvent(Event):
|
||||
class KubeDashboardEvent(Event, ServiceEvent):
|
||||
def __init__(self, path="/", secure=False):
|
||||
self.path = path
|
||||
self.secure
|
||||
pass
|
||||
|
||||
class ReadOnlyKubeletEvent(Event):
|
||||
class ReadOnlyKubeletEvent(Event, ServiceEvent):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
class SecureKubeletEvent(Event):
|
||||
class SecureKubeletEvent(Event, ServiceEvent):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
class KubeProxyEvent(Event):
|
||||
class KubeProxyEvent(Event, ServiceEvent):
|
||||
def __init__(self):
|
||||
pass
|
||||
pass
|
||||
11
modules/events/types/information.py
Normal file
11
modules/events/types/information.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from defaults import Event
|
||||
|
||||
class Vulnerability(object):
|
||||
""" Information Events """
|
||||
# this kind of events will be triggered when important information is discovered
|
||||
def __init__(self, desc):
|
||||
self.desc = desc
|
||||
|
||||
class KubeletVulnerability(Vulnerability, Event):
|
||||
def __init__(self, **kargs):
|
||||
super(KubeletVulnerability, self).__init__(**kargs)
|
||||
@@ -1,9 +1,14 @@
|
||||
from events import handler, KubeDashboardEvent
|
||||
import logging
|
||||
from ..types import Hunter
|
||||
|
||||
import requests
|
||||
|
||||
from ..events import handler
|
||||
from ..events.types import KubeDashboardEvent
|
||||
|
||||
|
||||
@handler.subscribe(KubeDashboardEvent)
|
||||
class KubeDashboard(object):
|
||||
class KubeDashboard(Hunter):
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
|
||||
@@ -17,7 +22,7 @@ class KubeDashboard(object):
|
||||
if not self.accessible:
|
||||
return
|
||||
|
||||
if self.event.secure:
|
||||
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))
|
||||
57
modules/hunting/kubelet.py
Normal file
57
modules/hunting/kubelet.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import json
|
||||
import logging
|
||||
from ..types import Hunter
|
||||
|
||||
import requests
|
||||
import urllib3
|
||||
|
||||
from ..events import handler
|
||||
from ..events.types import ReadOnlyKubeletEvent, SecureKubeletEvent, KubeletVulnerability
|
||||
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
""" dividing ports for seperate hunters """
|
||||
@handler.subscribe(ReadOnlyKubeletEvent)
|
||||
class ReadOnlyKubeletPortHunter(Hunter):
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
|
||||
def execute(self):
|
||||
pass
|
||||
|
||||
@handler.subscribe(SecureKubeletEvent)
|
||||
class SecurePortKubeletHunter(Hunter):
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
|
||||
def execute(self):
|
||||
self.check_debug_handlers()
|
||||
|
||||
def check_debug_handlers(self):
|
||||
pod, container = self.get_kubesystem_pod_container()
|
||||
if self.exec_handler(pod, container):
|
||||
self.publish_event(KubeletVulnerability(desc="exec enabled"))
|
||||
|
||||
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
|
||||
kubesystem_pod = lambda pod: pod["metadata"]["namespace"] == "kube-system" and pod["status"]["phase"] == "Running"
|
||||
pod_data = (pod_data for pod_data in pods_data if kubesystem_pod(pod_data)).next()
|
||||
|
||||
container_data = (container_data for container_data in pod_data["spec"]["containers"]).next()
|
||||
return pod_data["metadata"]["name"], container_data["name"]
|
||||
|
||||
# returns true if successfull
|
||||
def exec_handler(self, pod, container):
|
||||
headers = {
|
||||
"X-Stream-Protocol-Version": "v2.channel.k8s.io",
|
||||
}
|
||||
exec_url = "https://{host}:10250/exec/{pod_ns}/{pod}/{cont_name}?command={cmd}&input=1&output=1&tty=1".format(
|
||||
host = self.event.host,
|
||||
pod_ns = "kube-system",
|
||||
pod = pod,
|
||||
cont_name = container,
|
||||
cmd = "uname"
|
||||
)
|
||||
stream = requests.post(url=exec_url, headers=headers, verify=False, allow_redirects=False).headers.get("location", default=None)
|
||||
return bool(stream)
|
||||
@@ -1,13 +1,16 @@
|
||||
from enum import Enum
|
||||
from requests import get
|
||||
from events import KubeDashboardEvent, KubeProxyEvent, handler
|
||||
from ..types import Hunter
|
||||
|
||||
from requests import get
|
||||
|
||||
from ..events import handler
|
||||
from ..events.types import KubeDashboardEvent, KubeProxyEvent
|
||||
|
||||
class Service(Enum):
|
||||
DASHBOARD = "kubernetes-dashboard"
|
||||
|
||||
@handler.subscribe(KubeProxyEvent)
|
||||
class KubeProxy(object):
|
||||
class KubeProxy(Hunter):
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
self.api_url = "http://{host}:{port}/api/v1".format(host=self.event.host, port=self.event.port)
|
||||
@@ -17,7 +20,7 @@ class KubeProxy(object):
|
||||
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(path=curr_path, secure=False))
|
||||
self.publish_event(KubeDashboardEvent(path=curr_path, secure=False))
|
||||
|
||||
@property
|
||||
def namespaces(self):
|
||||
1
modules/types/__init__.py
Normal file
1
modules/types/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from defaults import *
|
||||
8
modules/types/defaults.py
Normal file
8
modules/types/defaults.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from ..events import handler
|
||||
|
||||
class Hunter(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def publish_event(self, event):
|
||||
handler.publish_event(event, caller=self)
|
||||
Reference in New Issue
Block a user