From 7201f5e236cb515e63a4ae188a8fac9ff327768e Mon Sep 17 00:00:00 2001 From: "ori.agmon" Date: Tue, 2 Oct 2018 18:55:50 +0300 Subject: [PATCH] Solved some exception bugs & did some refactoring to code & Added event & splited active & passive hunter --- src/core/events/handler.py | 12 +++---- src/modules/discovery/etcd.py | 35 +++++++------------ src/modules/hunting/etcd.py | 65 +++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 29 deletions(-) create mode 100644 src/modules/hunting/etcd.py diff --git a/src/core/events/handler.py b/src/core/events/handler.py index 051087d..f5b4595 100644 --- a/src/core/events/handler.py +++ b/src/core/events/handler.py @@ -41,7 +41,7 @@ class EventQueue(Queue, object): self.subscribe_event(event, hook=hook, predicate=predicate) return hook return wrapper - + # getting uninstantiated event object def subscribe_event(self, event, hook=None, predicate=None): if ActiveHunter in hook.__mro__: @@ -50,7 +50,7 @@ class EventQueue(Queue, object): else: self.active_hunters[hook] = hook.__doc__ elif Hunter in hook.__mro__: - self.passive_hunters[hook] = hook.__doc__ + self.passive_hunters[hook] = hook.__doc__ if hook not in self.hooks[event]: self.hooks[event].append((hook, predicate)) @@ -75,13 +75,13 @@ class EventQueue(Queue, object): hook = self.get() try: hook.execute() - except Exception as ex: + except Exception as ex: logging.debug(ex.message) self.task_done() logging.debug("closing thread...") def notifier(self): - time.sleep(2) + time.sleep(2) while self.unfinished_tasks > 0: logging.debug("{} tasks left".format(self.unfinished_tasks)) time.sleep(3) @@ -91,5 +91,5 @@ class EventQueue(Queue, object): self.running = False with self.mutex: self.queue.clear() - -handler = EventQueue(800) + +handler = EventQueue(800) \ No newline at end of file diff --git a/src/modules/discovery/etcd.py b/src/modules/discovery/etcd.py index 174bf31..3c0119e 100644 --- a/src/modules/discovery/etcd.py +++ b/src/modules/discovery/etcd.py @@ -24,7 +24,7 @@ class etcdRemoteVersionDisclosureEvent(Service, Event): def __init__(self): Service.__init__(self, name="Etcd Remote version disclosure") class etcdAccessEnabledWithoutAuthEvent(Service, Event): - """Remote version disclosure might give an attacker a valuable data to attack a cluster""" + """Etcd is accessible without authorization, it would allow a potential attacker to gain access to the etcd""" def __init__(self): Service.__init__(self, name="Etcd is accessible without authorization") @@ -55,20 +55,6 @@ class etcdRemoteAccess(Hunter): return True return False - def db_keys_write_access(self): - logging.debug(self.event.host) - logging.debug("Active hunter* is attempting to write keys remotely") - data = { - 'value': 'remote write access penetration' - } - r_secure = "https://{host}:{port}/v2/keys/message".format(host=self.event.host, port=2379) - r_not_secure = "https://{host}:{port}/v2/keys/message".format(host=self.event.host, port=2379) - - if self.helperFuncDo2Requests(r_secure, r_not_secure, data=data, reqType="put"): - self.publish_event(etcdRemoteWriteAccessEvent()) - return True - return False - def version_disclosure(self): logging.debug(self.event.host) logging.debug("Passive hunter is attempting to check etcd version remotely") @@ -85,15 +71,18 @@ class etcdRemoteAccess(Hunter): self.db_keys_disclosure() self.db_keys_write_access() - def helperFuncDo2Requests(self, req1, req2, isVerify=False, data=None, reqType="get"): + # Will attempt to do request "req1" with the optional parameters. + # If fails it will attempt to do "req2" with the optional parameters. + # If once of the request success this method will return True, if both fail- False. + def helperFuncDo2Requests(self, req1, req2, is_verify=False, data=None, req_type="get"): try: - r = self.helperDoRequest(req1, isVerify, data, reqType) + r = self.helperDoRequest(req1, is_verify, data, req_type) has_remote_access_gained = (r.status_code == 200 and r.content != "") if has_remote_access_gained: return True except Exception: try: - r = self.helperDoRequest(req2, isVerify, data, reqType) + r = self.helperDoRequest(req2, is_verify, data, req_type) has_remote_access_gained = (r.status_code == 200 and r.content != "") if has_remote_access_gained: return True @@ -101,10 +90,10 @@ class etcdRemoteAccess(Hunter): return False #None of the requests succeded.. return False - def helperDoRequest(self, req, isVerify, data=None, reqType="get"): - if reqType == "put": - r = requests.put(req, verify=isVerify, timeout=3, data=data) + def helperDoRequest(self, req, is_verify, data=None, req_type="get"): + if req_type == "put": + r = requests.put(req, verify=is_verify, timeout=3, data=data) return r - elif reqType == "get": - r = requests.get(req, verify=isVerify, timeout=3, data=data) + elif req_type == "get": + r = requests.get(req, verify=is_verify, timeout=3, data=data) return r \ No newline at end of file diff --git a/src/modules/hunting/etcd.py b/src/modules/hunting/etcd.py new file mode 100644 index 0000000..de1589f --- /dev/null +++ b/src/modules/hunting/etcd.py @@ -0,0 +1,65 @@ +import json +import logging + +import requests + +from ...core.events import handler +from ...core.events.types import Vulnerability, Event, Service, OpenPortEvent +from ...core.types import ActiveHunter + +"""Etcd is a DB that stores cluster's data, + it contains configuration and current state information, and might contain secrets""" +# Vulnerability: +class etcdRemoteWriteAccessEvent(Vulnerability, Event): + """Remote write access might grant an attacker full control over the kubernetes cluster""" + def __init__(self): + Vulnerability.__init__(self, name="Etcd Remote Write Access Event") + +@handler.subscribe(OpenPortEvent, predicate= lambda p: p.port == 2379) +class etcdRemoteAccess(ActiveHunter): + """Etcd Remote Access + Checks for remote write access to etcd + """ + def db_keys_write_access(self): + logging.debug(self.event.host) + logging.debug("Active hunter is attempting to write keys remotely") + data = { + 'value': 'remote write access penetration' + } + r_secure = "https://{host}:{port}/v2/keys/message".format(host=self.event.host, port=2379) + r_not_secure = "https://{host}:{port}/v2/keys/message".format(host=self.event.host, port=2379) + + if self.helperFuncDo2Requests(r_secure, r_not_secure, data=data, req_type="put"): + self.publish_event(etcdRemoteWriteAccessEvent()) + return True + return False + + def execute(self): + self.db_keys_write_access() + + # Will attempt to do request "req1" with the optional parameters. + # If fails it will attempt to do "req2" with the optional parameters. + # If once of the request success this method will return True, if both fail- False. + def helperFuncDo2Requests(self, req1, req2, is_verify=False, data=None, req_type="get"): + try: + r = self.helperDoRequest(req1, is_verify, data, req_type) + has_remote_access_gained = (r.status_code == 200 and r.content != "") + if has_remote_access_gained: + return True + except Exception: + try: + r = self.helperDoRequest(req2, is_verify, data, req_type) + has_remote_access_gained = (r.status_code == 200 and r.content != "") + if has_remote_access_gained: + return True + except Exception: + return False #None of the requests succeded.. + return False + + def helperDoRequest(self, req, is_verify, data=None, req_type="get"): + if req_type == "put": + r = requests.put(req, verify=is_verify, timeout=3, data=data) + return r + elif req_type == "get": + r = requests.get(req, verify=is_verify, timeout=3, data=data) + return r \ No newline at end of file