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:
daniel_sagi
2018-05-27 17:45:20 +03:00
parent 69dba8f1c7
commit a465c3f2eb
22 changed files with 211 additions and 86 deletions

View File

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

View File

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

View File

@@ -1 +1,2 @@
from config import *
from config import *
from reporter import *

27
log/reporter.py Normal file
View 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
View File

@@ -0,0 +1,4 @@
import discovery
import hunting
import events
import types

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
from handler import *
import types

View File

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

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