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:
daniel_sagi
2018-05-24 15:25:43 +03:00
parent 672c59f576
commit 290f87de70
13 changed files with 175 additions and 73 deletions

View File

@@ -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())

View File

@@ -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
View 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())

View File

@@ -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):

View File

@@ -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())

View File

@@ -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

View File

@@ -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)

View File

@@ -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
View 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))

View File

@@ -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):

View File

@@ -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
View File

@@ -0,0 +1 @@
from config import *

View File

@@ -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: