mirror of
https://github.com/aquasecurity/kube-hunter.git
synced 2026-08-23 22:26:23 +00:00
Merge branch 'master' into refactor_host_discovery
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user