From cb0164edd1863cca90185d73934d071bd4bd3e2b Mon Sep 17 00:00:00 2001 From: Ori Agmon Date: Wed, 5 Dec 2018 23:30:45 +0200 Subject: [PATCH] Shallow detection cve 2018 1002105 (#65) * added the CVE hunter, haven't checked the hunter yet * fixed illegal name for import file * Completed & Tested --- src/core/types.py | 11 ++++ src/modules/hunting/CVE_2018_1002105.py | 74 +++++++++++++++++++++++++ src/modules/hunting/kubelet.py | 27 +++++++-- 3 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 src/modules/hunting/CVE_2018_1002105.py diff --git a/src/core/types.py b/src/core/types.py index 626ebc9..9a404fd 100644 --- a/src/core/types.py +++ b/src/core/types.py @@ -13,29 +13,40 @@ class KubernetesCluster(): """Kubernetes Cluster""" name = "Kubernetes Cluster" + class Kubelet(KubernetesCluster): """The kubelet is the primary "node agent" that runs on each node""" name = "Kubelet" + class Azure(KubernetesCluster): """Azure Cluster""" name = "Azure" + """ Categories """ class InformationDisclosure(object): name = "Information Disclosure" + class RemoteCodeExec(object): name = "Remote Code Execution" + class IdentityTheft(object): name = "Identity Theft" + class UnauthenticatedAccess(object): name = "Unauthenticated Access" + class AccessRisk(object): name = "Access Risk" +class PrivilegeEscalation(KubernetesCluster): + name = "Privilege Escalation" + + from events import handler # import is in the bottom to break import loops \ No newline at end of file diff --git a/src/modules/hunting/CVE_2018_1002105.py b/src/modules/hunting/CVE_2018_1002105.py new file mode 100644 index 0000000..53d8724 --- /dev/null +++ b/src/modules/hunting/CVE_2018_1002105.py @@ -0,0 +1,74 @@ +import logging +import json +import requests +import uuid +import ast + +from ...core.events import handler +from ...core.events.types import Vulnerability, Event, OpenPortEvent +from ...core.types import Hunter, ActiveHunter, KubernetesCluster, RemoteCodeExec, AccessRisk, InformationDisclosure, PrivilegeEscalation + +""" Vulnerabilities """ + + +class ServerApiVersionEndPointAccess(Vulnerability, Event): + """ Accessing the server API within a compromised pod would help an attacker gain full control over the cluster""" + + def __init__(self, evidence): + Vulnerability.__init__(self, KubernetesCluster, name="Critical PrivilegedEscalation CVE", category=PrivilegeEscalation) + self.evidence = evidence + +# Passive Hunter +@handler.subscribe(OpenPortEvent, predicate=lambda x: x.port == 443 or x.port == 6443) +class IsVulnerableToCVEAttack(Hunter): + """ API Server Hunter + Accessing the API server within a compromised pod might grant an attacker full control over the cluster + """ + + def __init__(self, event): + self.event = event + self.headers = dict() + self.path = "https://{}:{}".format(self.event.host, self.event.port) + self.service_account_token_evidence = '' + self.api_server_evidence = '' + self.k8sVersion = '' + + def access_api_server_version_end_point(self): + logging.debug(self.event.host) + logging.debug('Passive Hunter is attempting to access the API server /version end point using the pod\'s service account token') + try: + res = requests.get("{path}/version".format(path=self.path), + headers=self.headers, verify=False) + self.api_server_evidence = res.content + resDict = ast.literal_eval(res.content) + version = resDict["gitVersion"].split('.') + first_two_minor_digists = eval(version[1]) + last_two_minor_digists = eval(version[2]) + + if first_two_minor_digists == 10 and last_two_minor_digists < 11: + return True + elif first_two_minor_digists == 11 and last_two_minor_digists < 5: + return True + elif first_two_minor_digists == 12 and last_two_minor_digists < 3: + return True + elif first_two_minor_digists < 10: + return True + except (requests.exceptions.ConnectionError, KeyError): + return False + + def get_service_account_token(self): + logging.debug(self.event.host) + logging.debug('Passive Hunter is attempting to access pod\'s service account token') + try: + with open('/var/run/secrets/kubernetes.io/serviceaccount/token', 'r') as token: + data = token.read() + self.service_account_token_evidence = data + self.headers = {'Authorization': 'Bearer ' + self.service_account_token_evidence} + return True + except IOError: # Couldn't read file + return False + + def execute(self): + if self.get_service_account_token(): # From within a Pod + if self.access_api_server_version_end_point(): + self.publish_event(ServerApiVersionEndPointAccess(self.api_server_evidence)) diff --git a/src/modules/hunting/kubelet.py b/src/modules/hunting/kubelet.py index cfff332..2ec0fbc 100644 --- a/src/modules/hunting/kubelet.py +++ b/src/modules/hunting/kubelet.py @@ -20,17 +20,20 @@ class ExposedPodsHandler(Vulnerability, Event): Vulnerability.__init__(self, Kubelet, "Exposed Pods", category=InformationDisclosure) self.count = count self.evidence = "count: {}".format(self.count) - + + class AnonymousAuthEnabled(Vulnerability, Event): """The kubelet is misconfigured, potentially allowing secure access to all requests on the kubelet, without the need to authenticate""" def __init__(self): Vulnerability.__init__(self, Kubelet, "Anonymous Authentication", category=RemoteCodeExec) + class ExposedContainerLogsHandler(Vulnerability, Event): """Output logs from a running container are using the exposed /containerLogs endpoint""" def __init__(self): Vulnerability.__init__(self, Kubelet, "Exposed Container Logs", category=InformationDisclosure) - + + class ExposedRunningPodsHandler(Vulnerability, Event): """Outputs a list of currently running pods, and some of their metadata, which can reveal sensitive information""" def __init__(self, count): @@ -38,26 +41,31 @@ class ExposedRunningPodsHandler(Vulnerability, Event): self.count = count self.evidence = "{} running pods".format(self.count) + class ExposedExecHandler(Vulnerability, Event): """An attacker could run arbitrary commands on a container""" def __init__(self): Vulnerability.__init__(self, Kubelet, "Exposed Exec On Container", category=RemoteCodeExec) + class ExposedRunHandler(Vulnerability, Event): """An attacker could run an arbitrary command inside a container""" def __init__(self): Vulnerability.__init__(self, Kubelet, "Exposed Run Inside Container", category=RemoteCodeExec) + class ExposedPortForwardHandler(Vulnerability, Event): """An attacker could set port forwaring rule on a pod""" def __init__(self): Vulnerability.__init__(self, Kubelet, "Exposed Port Forward", category=RemoteCodeExec) + class ExposedAttachHandler(Vulnerability, Event): """Opens a websocket that could enable an attacker to attach to a running container""" def __init__(self): Vulnerability.__init__(self, Kubelet, "Exposed Attaching To Container", category=RemoteCodeExec) + class ExposedHealthzHandler(Vulnerability, Event): """By accessing the open /healthz handler, an attacker could get the cluster health state without authenticating""" def __init__(self, status): @@ -65,19 +73,30 @@ class ExposedHealthzHandler(Vulnerability, Event): self.status = status self.evidence = "status: {}".format(self.status) + class K8sVersionDisclosure(Vulnerability, Event): """The kubernetes version could be obtained from logs in the /metrics endpoint""" def __init__(self, version): Vulnerability.__init__(self, Kubelet, "K8s Version Disclosure", category=InformationDisclosure) self.evidence = version - + + class PrivilegedContainers(Vulnerability, Event): """A Privileged container exist on a node. could expose the node/cluster to unwanted root operations""" def __init__(self, containers): Vulnerability.__init__(self, KubernetesCluster, "Privileged Container", category=AccessRisk) self.containers = containers self.evidence = "pod: {}, container: {}".format(containers[0][0], containers[0][1]) - + + +class PrivilegeEscalation(Vulnerability, Event): + """Privilege escalation allows an attacker to grant root permissions and control the cluster""" + + def __init__(self, containers): + Vulnerability.__init__(self, KubernetesCluster, "Privilege Escalation", category=PrivilegeEscalation) + self.containers = containers + self.evidence = "pod: {}, container: {}".format(containers[0][0], containers[0][1]) + """ dividing ports for seperate hunters """ @handler.subscribe(ReadOnlyKubeletEvent)