Merge branch 'master' into refactor_host_discovery

This commit is contained in:
danielsagi
2019-07-01 12:38:17 +03:00
committed by GitHub
5 changed files with 123 additions and 8 deletions
+7 -6
View File
@@ -12,8 +12,6 @@ from ..types import ActiveHunter, Hunter, HunterBase
from ...core.events.types import HuntFinished, Vulnerability
import threading
global queue_lock
queue_lock = Lock()
# Inherits Queue object, handles events asynchronously
class EventQueue(Queue, object):
@@ -32,6 +30,7 @@ class EventQueue(Queue, object):
t.daemon = True
t.start()
self.workers.append(t)
t = Thread(target=self.notifier)
t.daemon = True
t.start()
@@ -83,21 +82,23 @@ class EventQueue(Queue, object):
# executes callbacks on dedicated thread as a daemon
def worker(self):
while self.running:
queue_lock.acquire()
hook = self.get()
queue_lock.release()
try:
hook = self.get()
hook.execute()
except Exception as ex:
logging.debug("Exception: {} - {}".format(hook.__class__, ex))
self.task_done()
finally:
self.task_done()
logging.debug("closing thread...")
def notifier(self):
time.sleep(2)
# should consider locking on unfinished_tasks
while self.unfinished_tasks > 0:
logging.debug("{} tasks left".format(self.unfinished_tasks))
time.sleep(3)
if self.unfinished_tasks == 1:
logging.debug("final hook is hanging")
# stops execution of all daemons
def free(self):
+3
View File
@@ -37,6 +37,9 @@ class KubernetesCluster():
"""Kubernetes Cluster"""
name = "Kubernetes Cluster"
class KubectlClient():
"""The kubectl client binary is used by the user to interact with the cluster"""
name = "Kubectl Client"
class Kubelet(KubernetesCluster):
"""The kubelet is the primary "node agent" that runs on each node"""
+46
View File
@@ -0,0 +1,46 @@
import logging
import subprocess
import json
from ...core.types import Discovery
from ...core.events import handler
from ...core.events.types import HuntStarted, Event
class KubectlClientEvent(Event):
"""The API server is in charge of all operations on the cluster."""
def __init__(self, version):
self.version = version
def location(self):
return "local machine"
# Will be triggered on start of every hunt
@handler.subscribe(HuntStarted)
class KubectlClientDiscovery(Discovery):
"""Kubectl Client Discovery
Checks for the existence of a local kubectl client
"""
def __init__(self, event):
self.event = event
def get_kubectl_binary_version(self):
version = None
try:
# kubectl version --client does not make any connection to the cluster/internet whatsoever.
versionInfo = subprocess.check_output("kubectl version --client", stderr=subprocess.STDOUT)
if b"GitVersion" in versionInfo:
# extracting version from kubectl output
versionInfo = versionInfo.decode()
start = versionInfo.find('GitVersion')
version = versionInfo[start + len("GitVersion':\"") : versionInfo.find("\",", start)]
except Exception as x:
logging.debug("Could not find kubectl client")
return version
def execute(self):
logging.debug("Attempting to discover a local kubectl client")
version = self.get_kubectl_binary_version()
if version:
self.publish_event(KubectlClientEvent(version=version))
+64
View File
@@ -0,0 +1,64 @@
import logging
from ...core.events import handler
from ...core.types import Hunter, RemoteCodeExec, KubectlClient
from ...core.events.types import Vulnerability, Event
from ..discovery.kubectl import KubectlClientEvent
from distutils.version import LooseVersion, StrictVersion
class IncompleteFixToKubectlCpVulnerability(Vulnerability, Event):
"""The kubectl client is vulnerable to CVE-2019-11246, an attacker could potentially execute arbitrary code on the client's machine"""
def __init__(self, binary_version):
Vulnerability.__init__(self, KubectlClient, "Kubectl Vulnerable To CVE-2019-11246", category=RemoteCodeExec)
self.binary_version = binary_version
self.evidence = "kubectl version: {}".format(self.binary_version)
class KubectlCpVulnerability(Vulnerability, Event):
"""The kubectl client is vulnerable to CVE-2019-1002101, an attacker could potentially execute arbitrary code on the client's machine"""
def __init__(self, binary_version):
Vulnerability.__init__(self, KubectlClient, "Kubectl Vulnerable To CVE-2019-1002101", category=RemoteCodeExec)
self.binary_version = binary_version
self.evidence = "kubectl version: {}".format(self.binary_version)
@handler.subscribe(KubectlClientEvent)
class KubectlCVEHunter(Hunter):
"""Kubectl CVE Hunter
Compares version of the kubectl binary to known CVE affected versions
"""
def __init__(self, event):
self.event = event
def is_older_than(self, fix_versions, check_version):
"""Function determines if a version is vulnerable, by comparing to given fix versions"""
logging.debug("Passive hunter is comparing the kubectl binary version to vulnerable versions")
# in case version is in short version, converting
if len(LooseVersion(check_version).version) < 3:
check_version += '.0'
vulnerable = False
if check_version not in fix_versions:
for fix_v in fix_versions:
fix_v = LooseVersion(fix_v)
base_v = '.'.join(map(lambda x: str(x), fix_v.version[:2]) )
if check_version.startswith(base_v):
if LooseVersion(check_version) < fix_v:
vulnerable = True
break
# if version is smaller than smaller fix version
if not vulnerable and LooseVersion(check_version) < LooseVersion(fix_versions[0]):
vulnerable = True
return vulnerable
def execute(self):
cve_2019_1002101_fix_versions = ['1.11.9', '1.12.7', '1.13.5' '1.14.0']
cve_2019_11246_fix_versions = ['1.12.9', '1.13.6', '1.14.2']
if self.is_older_than(fix_versions=cve_2019_1002101_fix_versions, check_version=self.event.version):
self.publish_event(KubectlCpVulnerability(binary_version=self.event.version))
if self.is_older_than(fix_versions=cve_2019_11246_fix_versions, check_version=self.event.version):
self.publish_event(IncompleteFixToKubectlCpVulnerability(binary_version=self.event.version))
+3 -2
View File
@@ -213,8 +213,9 @@ class SecureKubeletPortHunter(Hunter):
containerName=self.pod["container"],
cmd = ""
)
status_code = requests.post(run_url, allow_redirects=False, verify=False).status_code
return (status_code != 404 and status_code != 401)
status_code = requests.post(run_url, allow_redirects=False, verify=False).status_code
# check if return value is 4xx
return not 400 <= status_code < 500
# returns list of currently running pods
def test_running_pods(self):