mirror of
https://github.com/aquasecurity/kube-hunter.git
synced 2026-05-09 18:57:02 +00:00
1. added log/
2. Started adding kubelet scanning. 3. Changed events architecture. All events are inheriting from "Event" class. when instantiating and defining a new event class, attributes other than what is important for that perticular event are not needed. the event handler will be stacking the events, so that each event will have all the attributes of its successors. This proccess is invisible to the developer, but needs to be acknowledged. *note: from now on, all executors needs to set self.event to given arg on init* Example (pseudo): @subscribe(NewHostEvent) def PortScan(event): publish(OpenPortEvent(port="8080")) @subscribe(OpenPortEvent) def print(event): print(event.host) publish(NewHostEvent(host="0.0.0.0")) >> output: 0.0.0.0 the print function recieves an open port event. even though when publishing the OpenPortEvent we did not specify a host, the print function can access the "host" attribute, as the OpenPortEvent successor was NewHostEvent. if "host" was not defined on the succesors, it is "None"
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -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))
|
||||
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
|
||||
|
||||
35
discovery/kubelet.py
Normal file
35
discovery/kubelet.py
Normal file
@@ -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())
|
||||
@@ -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):
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
handler = EventQueue(800)
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
20
hunting/kubelet.py
Normal file
20
hunting/kubelet.py
Normal file
@@ -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))
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
1
log/__init__.py
Normal file
1
log/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from config import *
|
||||
@@ -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:
|
||||
Reference in New Issue
Block a user