From 83b19d4208e9b2d458532c29f2f5b2d89093e6e6 Mon Sep 17 00:00:00 2001 From: danielsagi Date: Thu, 30 Sep 2021 15:25:30 +0300 Subject: [PATCH] Feature: Changed vulnerability categories to support MITRE ATT&CK (#474) * Refactored all categories to the new MITRE attack matrix format * Changed format of vulnerabilities table to display the mitre technique related to the vulnerability --- README.md | 2 +- kube_hunter/core/events/types.py | 73 ++++++-- kube_hunter/core/types.py | 94 ---------- kube_hunter/core/types/__init__.py | 4 + kube_hunter/core/types/components.py | 28 +++ kube_hunter/core/types/hunters.py | 36 ++++ kube_hunter/core/types/vulnerabilities.py | 188 ++++++++++++++++++++ kube_hunter/modules/discovery/hosts.py | 6 +- kube_hunter/modules/hunting/aks.py | 4 +- kube_hunter/modules/hunting/apiserver.py | 47 ++--- kube_hunter/modules/hunting/arp.py | 4 +- kube_hunter/modules/hunting/capabilities.py | 4 +- kube_hunter/modules/hunting/certificates.py | 4 +- kube_hunter/modules/hunting/cves.py | 22 +-- kube_hunter/modules/hunting/dashboard.py | 4 +- kube_hunter/modules/hunting/dns.py | 4 +- kube_hunter/modules/hunting/etcd.py | 16 +- kube_hunter/modules/hunting/kubelet.py | 35 ++-- kube_hunter/modules/hunting/mounts.py | 11 +- kube_hunter/modules/hunting/proxy.py | 5 +- kube_hunter/modules/hunting/secrets.py | 6 +- kube_hunter/modules/report/base.py | 2 +- kube_hunter/modules/report/plain.py | 7 +- tests/hunting/test_apiserver_hunter.py | 7 +- 24 files changed, 409 insertions(+), 204 deletions(-) delete mode 100644 kube_hunter/core/types.py create mode 100644 kube_hunter/core/types/__init__.py create mode 100644 kube_hunter/core/types/components.py create mode 100644 kube_hunter/core/types/hunters.py create mode 100644 kube_hunter/core/types/vulnerabilities.py diff --git a/README.md b/README.md index 6100636..0d59407 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ Available dispatch methods are: * KUBEHUNTER_HTTP_DISPATCH_URL (defaults to: https://localhost) * KUBEHUNTER_HTTP_DISPATCH_METHOD (defaults to: POST) -### Advanced Usage +### Advanced Usage #### Azure Quick Scanning When running **as a Pod in an Azure or AWS environment**, kube-hunter will fetch subnets from the Instance Metadata Service. Naturally this makes the discovery process take longer. To hardlimit subnet scanning to a `/24` CIDR, use the `--quick` option. diff --git a/kube_hunter/core/events/types.py b/kube_hunter/core/events/types.py index aaffd35..030ca32 100644 --- a/kube_hunter/core/events/types.py +++ b/kube_hunter/core/events/types.py @@ -3,15 +3,32 @@ import threading import requests from kube_hunter.conf import get_config -from kube_hunter.core.types import ( - InformationDisclosure, - DenialOfService, - RemoteCodeExec, - IdentityTheft, - PrivilegeEscalation, - AccessRisk, - UnauthenticatedAccess, - KubernetesCluster, +from kube_hunter.core.types import KubernetesCluster +from kube_hunter.core.types.vulnerabilities import ( + GeneralSensitiveInformationTechnique, + ExposedSensitiveInterfacesTechnique, + MountServicePrincipalTechnique, + ListK8sSecretsTechnique, + AccessContainerServiceAccountTechnique, + AccessK8sApiServerTechnique, + AccessKubeletAPITechnique, + AccessK8sDashboardTechnique, + InstanceMetadataApiTechnique, + ExecIntoContainerTechnique, + SidecarInjectionTechnique, + NewContainerTechnique, + GeneralPersistenceTechnique, + HostPathMountPrivilegeEscalationTechnique, + PrivilegedContainerTechnique, + ClusterAdminBindingTechnique, + ARPPoisoningTechnique, + CoreDNSPoisoningTechnique, + DataDestructionTechnique, + GeneralDefenseEvasionTechnique, + ConnectFromProxyServerTechnique, + CVERemoteCodeExecutionCategory, + CVEPrivilegeEscalationCategory, + CVEDenialOfServiceTechnique, ) logger = logging.getLogger(__name__) @@ -102,13 +119,30 @@ class Service: class Vulnerability: severity = dict( { - InformationDisclosure: "medium", - DenialOfService: "medium", - RemoteCodeExec: "high", - IdentityTheft: "high", - PrivilegeEscalation: "high", - AccessRisk: "low", - UnauthenticatedAccess: "low", + GeneralSensitiveInformationTechnique: "low", + ExposedSensitiveInterfacesTechnique: "high", + MountServicePrincipalTechnique: "high", + ListK8sSecretsTechnique: "high", + AccessContainerServiceAccountTechnique: "low", + AccessK8sApiServerTechnique: "medium", + AccessKubeletAPITechnique: "medium", + AccessK8sDashboardTechnique: "medium", + InstanceMetadataApiTechnique: "high", + ExecIntoContainerTechnique: "high", + SidecarInjectionTechnique: "high", + NewContainerTechnique: "high", + GeneralPersistenceTechnique: "high", + HostPathMountPrivilegeEscalationTechnique: "high", + PrivilegedContainerTechnique: "high", + ClusterAdminBindingTechnique: "high", + ARPPoisoningTechnique: "medium", + CoreDNSPoisoningTechnique: "high", + DataDestructionTechnique: "high", + GeneralDefenseEvasionTechnique: "high", + ConnectFromProxyServerTechnique: "low", + CVERemoteCodeExecutionCategory: "high", + CVEPrivilegeEscalationCategory: "high", + CVEDenialOfServiceTechnique: "medium", } ) @@ -213,18 +247,21 @@ class ReportDispatched(Event): class K8sVersionDisclosure(Vulnerability, Event): """The kubernetes version could be obtained from the {} endpoint""" - def __init__(self, version, from_endpoint, extra_info=""): + def __init__(self, version, from_endpoint, extra_info="", category=None): Vulnerability.__init__( self, KubernetesCluster, "K8s Version Disclosure", - category=InformationDisclosure, + category=ExposedSensitiveInterfacesTechnique, vid="KHV002", ) self.version = version self.from_endpoint = from_endpoint self.extra_info = extra_info self.evidence = version + # depending from where the version came from, we might want to also override the category + if category: + self.category = category def explain(self): return self.__doc__.format(self.from_endpoint) + self.extra_info diff --git a/kube_hunter/core/types.py b/kube_hunter/core/types.py deleted file mode 100644 index c4612e9..0000000 --- a/kube_hunter/core/types.py +++ /dev/null @@ -1,94 +0,0 @@ -class HunterBase: - publishedVulnerabilities = 0 - - @staticmethod - def parse_docs(docs): - """returns tuple of (name, docs)""" - if not docs: - return __name__, "" - docs = docs.strip().split("\n") - for i, line in enumerate(docs): - docs[i] = line.strip() - return docs[0], " ".join(docs[1:]) if len(docs[1:]) else "" - - @classmethod - def get_name(cls): - name, _ = cls.parse_docs(cls.__doc__) - return name - - def publish_event(self, event): - handler.publish_event(event, caller=self) - - -class ActiveHunter(HunterBase): - pass - - -class Hunter(HunterBase): - pass - - -class Discovery(HunterBase): - pass - - -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""" - - name = "Kubelet" - - -class AWS(KubernetesCluster): - """AWS Cluster""" - - name = "AWS" - - -class Azure(KubernetesCluster): - """Azure Cluster""" - - name = "Azure" - - -class InformationDisclosure: - name = "Information Disclosure" - - -class RemoteCodeExec: - name = "Remote Code Execution" - - -class IdentityTheft: - name = "Identity Theft" - - -class UnauthenticatedAccess: - name = "Unauthenticated Access" - - -class AccessRisk: - name = "Access Risk" - - -class PrivilegeEscalation(KubernetesCluster): - name = "Privilege Escalation" - - -class DenialOfService: - name = "Denial of Service" - - -# import is in the bottom to break import loops -from .events import handler # noqa diff --git a/kube_hunter/core/types/__init__.py b/kube_hunter/core/types/__init__.py new file mode 100644 index 0000000..0927486 --- /dev/null +++ b/kube_hunter/core/types/__init__.py @@ -0,0 +1,4 @@ +# flake8: noqa: E402 +from .hunters import * +from .components import * +from .vulnerabilities import * diff --git a/kube_hunter/core/types/components.py b/kube_hunter/core/types/components.py new file mode 100644 index 0000000..22f89f9 --- /dev/null +++ b/kube_hunter/core/types/components.py @@ -0,0 +1,28 @@ +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""" + + name = "Kubelet" + + +class AWS(KubernetesCluster): + """AWS Cluster""" + + name = "AWS" + + +class Azure(KubernetesCluster): + """Azure Cluster""" + + name = "Azure" diff --git a/kube_hunter/core/types/hunters.py b/kube_hunter/core/types/hunters.py new file mode 100644 index 0000000..0d7e410 --- /dev/null +++ b/kube_hunter/core/types/hunters.py @@ -0,0 +1,36 @@ +class HunterBase: + publishedVulnerabilities = 0 + + @staticmethod + def parse_docs(docs): + """returns tuple of (name, docs)""" + if not docs: + return __name__, "" + docs = docs.strip().split("\n") + for i, line in enumerate(docs): + docs[i] = line.strip() + return docs[0], " ".join(docs[1:]) if len(docs[1:]) else "" + + @classmethod + def get_name(cls): + name, _ = cls.parse_docs(cls.__doc__) + return name + + def publish_event(self, event): + # Import here to avoid circular import from events package. + # imports are cached in python so this should not affect runtime + from ..events import handler # noqa + + handler.publish_event(event, caller=self) + + +class ActiveHunter(HunterBase): + pass + + +class Hunter(HunterBase): + pass + + +class Discovery(HunterBase): + pass diff --git a/kube_hunter/core/types/vulnerabilities.py b/kube_hunter/core/types/vulnerabilities.py new file mode 100644 index 0000000..d3a8b93 --- /dev/null +++ b/kube_hunter/core/types/vulnerabilities.py @@ -0,0 +1,188 @@ +""" +Vulnerabilities are divided into 2 main categories. + +MITRE Category +-------------- +Vulnerability that correlates to a method in the official MITRE ATT&CK matrix for kubernetes + +CVE Category +------------- +"General" category definition. The category is usually determined by the severity of the CVE +""" + + +class MITRECategory: + @classmethod + def get_name(cls): + """ + Returns the full name of MITRE technique: // + Should only be used on a direct technique class at the end of the MITRE inheritance chain. + + Example inheritance: + MITRECategory -> InitialAccessCategory -> ExposedSensitiveInterfacesTechnique + """ + inheritance_chain = cls.__mro__ + if len(inheritance_chain) >= 4: + # -3 == index of mitreCategory class. (object class is first) + mitre_category_class = inheritance_chain[-3] + return f"{mitre_category_class.name} // {cls.name}" + + +class CVECategory: + @classmethod + def get_name(cls): + """ + Returns the full name of the category: CVE // + """ + return f"CVE // {cls.name}" + + +""" +MITRE ATT&CK Technique Categories +""" + + +class InitialAccessCategory(MITRECategory): + name = "Initial Access" + + +class ExecutionCategory(MITRECategory): + name = "Execution" + + +class PersistenceCategory(MITRECategory): + name = "Persistence" + + +class PrivilegeEscalationCategory(MITRECategory): + name = "Privilege Escalation" + + +class DefenseEvasionCategory(MITRECategory): + name = "Defense Evasion" + + +class CredentialAccessCategory(MITRECategory): + name = "Credential Access" + + +class DiscoveryCategory(MITRECategory): + name = "Discovery" + + +class LateralMovementCategory(MITRECategory): + name = "Lateral Movement" + + +class CollectionCategory(MITRECategory): + name = "Collection" + + +class ImpactCategory(MITRECategory): + name = "Impact" + + +""" +MITRE ATT&CK Techniques +""" + + +class GeneralSensitiveInformationTechnique(InitialAccessCategory): + name = "General Sensitive Information" + + +class ExposedSensitiveInterfacesTechnique(InitialAccessCategory): + name = "Exposed sensitive interfaces" + + +class MountServicePrincipalTechnique(CredentialAccessCategory): + name = "Mount service principal" + + +class ListK8sSecretsTechnique(CredentialAccessCategory): + name = "List K8S secrets" + + +class AccessContainerServiceAccountTechnique(CredentialAccessCategory): + name = "Access container service account" + + +class AccessK8sApiServerTechnique(DiscoveryCategory): + name = "Access the K8S API Server" + + +class AccessKubeletAPITechnique(DiscoveryCategory): + name = "Access Kubelet API" + + +class AccessK8sDashboardTechnique(DiscoveryCategory): + name = "Access Kubernetes Dashboard" + + +class InstanceMetadataApiTechnique(DiscoveryCategory): + name = "Instance Metadata API" + + +class ExecIntoContainerTechnique(ExecutionCategory): + name = "Exec into container" + + +class SidecarInjectionTechnique(ExecutionCategory): + name = "Sidecar injection" + + +class NewContainerTechnique(ExecutionCategory): + name = "New container" + + +class GeneralPersistenceTechnique(PersistenceCategory): + name = "General Peristence" + + +class HostPathMountPrivilegeEscalationTechnique(PrivilegeEscalationCategory): + name = "hostPath mount" + + +class PrivilegedContainerTechnique(PrivilegeEscalationCategory): + name = "Privileged container" + + +class ClusterAdminBindingTechnique(PrivilegeEscalationCategory): + name = "Cluser-admin binding" + + +class ARPPoisoningTechnique(LateralMovementCategory): + name = "ARP poisoning and IP spoofing" + + +class CoreDNSPoisoningTechnique(LateralMovementCategory): + name = "CoreDNS poisoning" + + +class DataDestructionTechnique(ImpactCategory): + name = "Data Destruction" + + +class GeneralDefenseEvasionTechnique(DefenseEvasionCategory): + name = "General Defense Evasion" + + +class ConnectFromProxyServerTechnique(DefenseEvasionCategory): + name = "Connect from Proxy server" + + +""" +CVE Categories +""" + + +class CVERemoteCodeExecutionCategory(CVECategory): + name = "Remote Code Execution (CVE)" + + +class CVEPrivilegeEscalationCategory(CVECategory): + name = "Privilege Escalation (CVE)" + + +class CVEDenialOfServiceTechnique(CVECategory): + name = "Denial Of Service (CVE)" diff --git a/kube_hunter/modules/discovery/hosts.py b/kube_hunter/modules/discovery/hosts.py index 5923acc..76d0b40 100644 --- a/kube_hunter/modules/discovery/hosts.py +++ b/kube_hunter/modules/discovery/hosts.py @@ -11,7 +11,7 @@ from kube_hunter.conf import get_config from kube_hunter.modules.discovery.kubernetes_client import list_all_k8s_cluster_nodes from kube_hunter.core.events import handler from kube_hunter.core.events.types import Event, NewHostEvent, Vulnerability -from kube_hunter.core.types import Discovery, InformationDisclosure, AWS, Azure +from kube_hunter.core.types import Discovery, AWS, Azure, InstanceMetadataApiTechnique logger = logging.getLogger(__name__) @@ -55,7 +55,7 @@ class AWSMetadataApi(Vulnerability, Event): self, AWS, "AWS Metadata Exposure", - category=InformationDisclosure, + category=InstanceMetadataApiTechnique, vid="KHV053", ) self.cidr = cidr @@ -70,7 +70,7 @@ class AzureMetadataApi(Vulnerability, Event): self, Azure, "Azure Metadata Exposure", - category=InformationDisclosure, + category=InstanceMetadataApiTechnique, vid="KHV003", ) self.cidr = cidr diff --git a/kube_hunter/modules/hunting/aks.py b/kube_hunter/modules/hunting/aks.py index e5c4a16..6c0d0a9 100644 --- a/kube_hunter/modules/hunting/aks.py +++ b/kube_hunter/modules/hunting/aks.py @@ -7,7 +7,7 @@ from kube_hunter.conf import get_config from kube_hunter.modules.hunting.kubelet import ExposedPodsHandler, SecureKubeletPortHunter from kube_hunter.core.events import handler from kube_hunter.core.events.types import Event, Vulnerability -from kube_hunter.core.types import Hunter, ActiveHunter, IdentityTheft, Azure +from kube_hunter.core.types import Hunter, ActiveHunter, MountServicePrincipalTechnique, Azure logger = logging.getLogger(__name__) @@ -20,7 +20,7 @@ class AzureSpnExposure(Vulnerability, Event): self, Azure, "Azure SPN Exposure", - category=IdentityTheft, + category=MountServicePrincipalTechnique, vid="KHV004", ) self.container = container diff --git a/kube_hunter/modules/hunting/apiserver.py b/kube_hunter/modules/hunting/apiserver.py index 5e4aa96..8af3dd8 100644 --- a/kube_hunter/modules/hunting/apiserver.py +++ b/kube_hunter/modules/hunting/apiserver.py @@ -8,10 +8,15 @@ from kube_hunter.modules.discovery.apiserver import ApiServer from kube_hunter.core.events import handler from kube_hunter.core.events.types import Vulnerability, Event, K8sVersionDisclosure from kube_hunter.core.types import Hunter, ActiveHunter, KubernetesCluster -from kube_hunter.core.types import ( - AccessRisk, - InformationDisclosure, - UnauthenticatedAccess, +from kube_hunter.core.types.vulnerabilities import ( + AccessK8sApiServerTechnique, + ExposedSensitiveInterfacesTechnique, + GeneralDefenseEvasionTechnique, + DataDestructionTechnique, + ClusterAdminBindingTechnique, + NewContainerTechnique, + PrivilegedContainerTechnique, + SidecarInjectionTechnique, ) logger = logging.getLogger(__name__) @@ -24,10 +29,10 @@ class ServerApiAccess(Vulnerability, Event): def __init__(self, evidence, using_token): if using_token: name = "Access to API using service account token" - category = InformationDisclosure + category = AccessK8sApiServerTechnique else: name = "Unauthenticated access to API" - category = UnauthenticatedAccess + category = ExposedSensitiveInterfacesTechnique Vulnerability.__init__( self, KubernetesCluster, @@ -44,7 +49,7 @@ class ServerApiHTTPAccess(Vulnerability, Event): def __init__(self, evidence): name = "Insecure (HTTP) access to API" - category = UnauthenticatedAccess + category = ExposedSensitiveInterfacesTechnique Vulnerability.__init__( self, KubernetesCluster, @@ -59,7 +64,7 @@ class ApiInfoDisclosure(Vulnerability, Event): """Information Disclosure depending upon RBAC permissions and Kube-Cluster Setup""" def __init__(self, evidence, using_token, name): - category = InformationDisclosure + category = AccessK8sApiServerTechnique if using_token: name += " using default service account token" else: @@ -111,7 +116,7 @@ class CreateANamespace(Vulnerability, Event): self, KubernetesCluster, name="Created a namespace", - category=AccessRisk, + category=GeneralDefenseEvasionTechnique, ) self.evidence = evidence @@ -125,7 +130,7 @@ class DeleteANamespace(Vulnerability, Event): self, KubernetesCluster, name="Delete a namespace", - category=AccessRisk, + category=DataDestructionTechnique, ) self.evidence = evidence @@ -136,7 +141,7 @@ class CreateARole(Vulnerability, Event): """ def __init__(self, evidence): - Vulnerability.__init__(self, KubernetesCluster, name="Created a role", category=AccessRisk) + Vulnerability.__init__(self, KubernetesCluster, name="Created a role", category=GeneralDefenseEvasionTechnique) self.evidence = evidence @@ -150,7 +155,7 @@ class CreateAClusterRole(Vulnerability, Event): self, KubernetesCluster, name="Created a cluster role", - category=AccessRisk, + category=ClusterAdminBindingTechnique, ) self.evidence = evidence @@ -165,7 +170,7 @@ class PatchARole(Vulnerability, Event): self, KubernetesCluster, name="Patched a role", - category=AccessRisk, + category=ClusterAdminBindingTechnique, ) self.evidence = evidence @@ -180,7 +185,7 @@ class PatchAClusterRole(Vulnerability, Event): self, KubernetesCluster, name="Patched a cluster role", - category=AccessRisk, + category=ClusterAdminBindingTechnique, ) self.evidence = evidence @@ -193,7 +198,7 @@ class DeleteARole(Vulnerability, Event): self, KubernetesCluster, name="Deleted a role", - category=AccessRisk, + category=DataDestructionTechnique, ) self.evidence = evidence @@ -206,7 +211,7 @@ class DeleteAClusterRole(Vulnerability, Event): self, KubernetesCluster, name="Deleted a cluster role", - category=AccessRisk, + category=DataDestructionTechnique, ) self.evidence = evidence @@ -219,7 +224,7 @@ class CreateAPod(Vulnerability, Event): self, KubernetesCluster, name="Created A Pod", - category=AccessRisk, + category=NewContainerTechnique, ) self.evidence = evidence @@ -232,7 +237,7 @@ class CreateAPrivilegedPod(Vulnerability, Event): self, KubernetesCluster, name="Created A PRIVILEGED Pod", - category=AccessRisk, + category=PrivilegedContainerTechnique, ) self.evidence = evidence @@ -245,7 +250,7 @@ class PatchAPod(Vulnerability, Event): self, KubernetesCluster, name="Patched A Pod", - category=AccessRisk, + category=SidecarInjectionTechnique, ) self.evidence = evidence @@ -258,7 +263,7 @@ class DeleteAPod(Vulnerability, Event): self, KubernetesCluster, name="Deleted A Pod", - category=AccessRisk, + category=DataDestructionTechnique, ) self.evidence = evidence @@ -377,7 +382,7 @@ class AccessApiServerWithToken(AccessApiServer): super().__init__(event) assert self.event.auth_token self.headers = {"Authorization": f"Bearer {self.event.auth_token}"} - self.category = InformationDisclosure + self.category = AccessK8sApiServerTechnique self.with_token = True diff --git a/kube_hunter/modules/hunting/arp.py b/kube_hunter/modules/hunting/arp.py index 6ae8c35..0a37f1d 100644 --- a/kube_hunter/modules/hunting/arp.py +++ b/kube_hunter/modules/hunting/arp.py @@ -5,7 +5,7 @@ from scapy.all import ARP, IP, ICMP, Ether, sr1, srp from kube_hunter.conf import get_config from kube_hunter.core.events import handler from kube_hunter.core.events.types import Event, Vulnerability -from kube_hunter.core.types import ActiveHunter, KubernetesCluster, IdentityTheft +from kube_hunter.core.types import ActiveHunter, KubernetesCluster, ARPPoisoningTechnique from kube_hunter.modules.hunting.capabilities import CapNetRawEnabled logger = logging.getLogger(__name__) @@ -20,7 +20,7 @@ class PossibleArpSpoofing(Vulnerability, Event): self, KubernetesCluster, "Possible Arp Spoof", - category=IdentityTheft, + category=ARPPoisoningTechnique, vid="KHV020", ) diff --git a/kube_hunter/modules/hunting/capabilities.py b/kube_hunter/modules/hunting/capabilities.py index 802ae93..656618b 100644 --- a/kube_hunter/modules/hunting/capabilities.py +++ b/kube_hunter/modules/hunting/capabilities.py @@ -4,7 +4,7 @@ import logging from kube_hunter.modules.discovery.hosts import RunningAsPodEvent from kube_hunter.core.events import handler from kube_hunter.core.events.types import Event, Vulnerability -from kube_hunter.core.types import Hunter, AccessRisk, KubernetesCluster +from kube_hunter.core.types import Hunter, ARPPoisoningTechnique, KubernetesCluster logger = logging.getLogger(__name__) @@ -20,7 +20,7 @@ class CapNetRawEnabled(Event, Vulnerability): self, KubernetesCluster, name="CAP_NET_RAW Enabled", - category=AccessRisk, + category=ARPPoisoningTechnique, ) diff --git a/kube_hunter/modules/hunting/certificates.py b/kube_hunter/modules/hunting/certificates.py index db12e66..ee60c98 100644 --- a/kube_hunter/modules/hunting/certificates.py +++ b/kube_hunter/modules/hunting/certificates.py @@ -3,7 +3,7 @@ import logging import base64 import re -from kube_hunter.core.types import Hunter, KubernetesCluster, InformationDisclosure +from kube_hunter.core.types import Hunter, KubernetesCluster, GeneralSensitiveInformationTechnique from kube_hunter.core.events import handler from kube_hunter.core.events.types import Vulnerability, Event, Service @@ -21,7 +21,7 @@ class CertificateEmail(Vulnerability, Event): self, KubernetesCluster, "Certificate Includes Email Address", - category=InformationDisclosure, + category=GeneralSensitiveInformationTechnique, vid="KHV021", ) self.email = email diff --git a/kube_hunter/modules/hunting/cves.py b/kube_hunter/modules/hunting/cves.py index 2e8ffc8..7e8d8a9 100644 --- a/kube_hunter/modules/hunting/cves.py +++ b/kube_hunter/modules/hunting/cves.py @@ -6,11 +6,11 @@ from kube_hunter.core.events import handler from kube_hunter.core.events.types import Vulnerability, Event, K8sVersionDisclosure from kube_hunter.core.types import ( Hunter, - KubernetesCluster, - RemoteCodeExec, - PrivilegeEscalation, - DenialOfService, KubectlClient, + KubernetesCluster, + CVERemoteCodeExecutionCategory, + CVEPrivilegeEscalationCategory, + CVEDenialOfServiceTechnique, ) from kube_hunter.modules.discovery.kubectl import KubectlClientEvent @@ -25,7 +25,7 @@ class ServerApiVersionEndPointAccessPE(Vulnerability, Event): self, KubernetesCluster, name="Critical Privilege Escalation CVE", - category=PrivilegeEscalation, + category=CVEPrivilegeEscalationCategory, vid="KHV022", ) self.evidence = evidence @@ -40,7 +40,7 @@ class ServerApiVersionEndPointAccessDos(Vulnerability, Event): self, KubernetesCluster, name="Denial of Service to Kubernetes API Server", - category=DenialOfService, + category=CVEDenialOfServiceTechnique, vid="KHV023", ) self.evidence = evidence @@ -55,7 +55,7 @@ class PingFloodHttp2Implementation(Vulnerability, Event): self, KubernetesCluster, name="Possible Ping Flood Attack", - category=DenialOfService, + category=CVEDenialOfServiceTechnique, vid="KHV024", ) self.evidence = evidence @@ -70,7 +70,7 @@ class ResetFloodHttp2Implementation(Vulnerability, Event): self, KubernetesCluster, name="Possible Reset Flood Attack", - category=DenialOfService, + category=CVEDenialOfServiceTechnique, vid="KHV025", ) self.evidence = evidence @@ -85,7 +85,7 @@ class ServerApiClusterScopedResourcesAccess(Vulnerability, Event): self, KubernetesCluster, name="Arbitrary Access To Cluster Scoped Resources", - category=PrivilegeEscalation, + category=CVEPrivilegeEscalationCategory, vid="KHV026", ) self.evidence = evidence @@ -100,7 +100,7 @@ class IncompleteFixToKubectlCpVulnerability(Vulnerability, Event): self, KubectlClient, "Kubectl Vulnerable To CVE-2019-11246", - category=RemoteCodeExec, + category=CVERemoteCodeExecutionCategory, vid="KHV027", ) self.binary_version = binary_version @@ -116,7 +116,7 @@ class KubectlCpVulnerability(Vulnerability, Event): self, KubectlClient, "Kubectl Vulnerable To CVE-2019-1002101", - category=RemoteCodeExec, + category=CVERemoteCodeExecutionCategory, vid="KHV028", ) self.binary_version = binary_version diff --git a/kube_hunter/modules/hunting/dashboard.py b/kube_hunter/modules/hunting/dashboard.py index bb31b24..956e120 100644 --- a/kube_hunter/modules/hunting/dashboard.py +++ b/kube_hunter/modules/hunting/dashboard.py @@ -3,7 +3,7 @@ import json import requests from kube_hunter.conf import get_config -from kube_hunter.core.types import Hunter, RemoteCodeExec, KubernetesCluster +from kube_hunter.core.types import Hunter, AccessK8sDashboardTechnique, KubernetesCluster from kube_hunter.core.events import handler from kube_hunter.core.events.types import Vulnerability, Event from kube_hunter.modules.discovery.dashboard import KubeDashboardEvent @@ -19,7 +19,7 @@ class DashboardExposed(Vulnerability, Event): self, KubernetesCluster, "Dashboard Exposed", - category=RemoteCodeExec, + category=AccessK8sDashboardTechnique, vid="KHV029", ) self.evidence = "nodes: {}".format(" ".join(nodes)) if nodes else None diff --git a/kube_hunter/modules/hunting/dns.py b/kube_hunter/modules/hunting/dns.py index 6635b37..b0e037b 100644 --- a/kube_hunter/modules/hunting/dns.py +++ b/kube_hunter/modules/hunting/dns.py @@ -6,7 +6,7 @@ from scapy.all import IP, ICMP, UDP, DNS, DNSQR, ARP, Ether, sr1, srp1, srp from kube_hunter.conf import get_config from kube_hunter.core.events import handler from kube_hunter.core.events.types import Event, Vulnerability -from kube_hunter.core.types import ActiveHunter, KubernetesCluster, IdentityTheft +from kube_hunter.core.types import ActiveHunter, KubernetesCluster, CoreDNSPoisoningTechnique from kube_hunter.modules.hunting.arp import PossibleArpSpoofing logger = logging.getLogger(__name__) @@ -21,7 +21,7 @@ class PossibleDnsSpoofing(Vulnerability, Event): self, KubernetesCluster, "Possible DNS Spoof", - category=IdentityTheft, + category=CoreDNSPoisoningTechnique, vid="KHV030", ) self.kubedns_pod_ip = kubedns_pod_ip diff --git a/kube_hunter/modules/hunting/etcd.py b/kube_hunter/modules/hunting/etcd.py index 9118bc9..f087567 100644 --- a/kube_hunter/modules/hunting/etcd.py +++ b/kube_hunter/modules/hunting/etcd.py @@ -8,10 +8,10 @@ from kube_hunter.core.types import ( ActiveHunter, Hunter, KubernetesCluster, - InformationDisclosure, - RemoteCodeExec, - UnauthenticatedAccess, - AccessRisk, + GeneralSensitiveInformationTechnique, + GeneralPersistenceTechnique, + ListK8sSecretsTechnique, + ExposedSensitiveInterfacesTechnique, ) logger = logging.getLogger(__name__) @@ -29,7 +29,7 @@ class EtcdRemoteWriteAccessEvent(Vulnerability, Event): self, KubernetesCluster, name="Etcd Remote Write Access Event", - category=RemoteCodeExec, + category=GeneralPersistenceTechnique, vid="KHV031", ) self.evidence = write_res @@ -43,7 +43,7 @@ class EtcdRemoteReadAccessEvent(Vulnerability, Event): self, KubernetesCluster, name="Etcd Remote Read Access Event", - category=AccessRisk, + category=ListK8sSecretsTechnique, vid="KHV032", ) self.evidence = keys @@ -58,7 +58,7 @@ class EtcdRemoteVersionDisclosureEvent(Vulnerability, Event): self, KubernetesCluster, name="Etcd Remote version disclosure", - category=InformationDisclosure, + category=GeneralSensitiveInformationTechnique, vid="KHV033", ) self.evidence = version @@ -74,7 +74,7 @@ class EtcdAccessEnabledWithoutAuthEvent(Vulnerability, Event): self, KubernetesCluster, name="Etcd is accessible using insecure connection (HTTP)", - category=UnauthenticatedAccess, + category=ExposedSensitiveInterfacesTechnique, vid="KHV034", ) self.evidence = version diff --git a/kube_hunter/modules/hunting/kubelet.py b/kube_hunter/modules/hunting/kubelet.py index 3ff85b8..c67336c 100644 --- a/kube_hunter/modules/hunting/kubelet.py +++ b/kube_hunter/modules/hunting/kubelet.py @@ -16,9 +16,12 @@ from kube_hunter.core.types import ( ActiveHunter, KubernetesCluster, Kubelet, - InformationDisclosure, - RemoteCodeExec, - AccessRisk, + ExposedSensitiveInterfacesTechnique, + ExecIntoContainerTechnique, + GeneralDefenseEvasionTechnique, + GeneralSensitiveInformationTechnique, + PrivilegedContainerTechnique, + AccessKubeletAPITechnique, ) from kube_hunter.modules.discovery.kubelet import ( ReadOnlyKubeletEvent, @@ -35,7 +38,7 @@ class ExposedPodsHandler(Vulnerability, Event): def __init__(self, pods): Vulnerability.__init__( - self, component=Kubelet, name="Exposed Pods", category=InformationDisclosure, vid="KHV052" + self, component=Kubelet, name="Exposed Pods", category=AccessKubeletAPITechnique, vid="KHV052" ) self.pods = pods self.evidence = f"count: {len(self.pods)}" @@ -50,7 +53,7 @@ class AnonymousAuthEnabled(Vulnerability, Event): self, component=Kubelet, name="Anonymous Authentication", - category=RemoteCodeExec, + category=ExposedSensitiveInterfacesTechnique, vid="KHV036", ) @@ -63,7 +66,7 @@ class ExposedContainerLogsHandler(Vulnerability, Event): self, component=Kubelet, name="Exposed Container Logs", - category=InformationDisclosure, + category=AccessKubeletAPITechnique, vid="KHV037", ) @@ -77,7 +80,7 @@ class ExposedRunningPodsHandler(Vulnerability, Event): self, component=Kubelet, name="Exposed Running Pods", - category=InformationDisclosure, + category=AccessKubeletAPITechnique, vid="KHV038", ) self.count = count @@ -92,7 +95,7 @@ class ExposedExecHandler(Vulnerability, Event): self, component=Kubelet, name="Exposed Exec On Container", - category=RemoteCodeExec, + category=ExecIntoContainerTechnique, vid="KHV039", ) @@ -105,7 +108,7 @@ class ExposedRunHandler(Vulnerability, Event): self, component=Kubelet, name="Exposed Run Inside Container", - category=RemoteCodeExec, + category=ExecIntoContainerTechnique, vid="KHV040", ) @@ -118,7 +121,7 @@ class ExposedPortForwardHandler(Vulnerability, Event): self, component=Kubelet, name="Exposed Port Forward", - category=RemoteCodeExec, + category=GeneralDefenseEvasionTechnique, vid="KHV041", ) @@ -132,7 +135,7 @@ class ExposedAttachHandler(Vulnerability, Event): self, component=Kubelet, name="Exposed Attaching To Container", - category=RemoteCodeExec, + category=ExecIntoContainerTechnique, vid="KHV042", ) @@ -146,7 +149,7 @@ class ExposedHealthzHandler(Vulnerability, Event): self, component=Kubelet, name="Cluster Health Disclosure", - category=InformationDisclosure, + category=GeneralSensitiveInformationTechnique, vid="KHV043", ) self.status = status @@ -163,7 +166,7 @@ the whole cluster""" self, component=KubernetesCluster, name="Exposed Existing Privileged Container(s) Via Secure Kubelet Port", - category=AccessRisk, + category=PrivilegedContainerTechnique, vid="KHV051", ) self.exposed_existing_privileged_containers = exposed_existing_privileged_containers @@ -178,7 +181,7 @@ class PrivilegedContainers(Vulnerability, Event): self, component=KubernetesCluster, name="Privileged Container", - category=AccessRisk, + category=PrivilegedContainerTechnique, vid="KHV044", ) self.containers = containers @@ -193,7 +196,7 @@ class ExposedSystemLogs(Vulnerability, Event): self, component=Kubelet, name="Exposed System Logs", - category=InformationDisclosure, + category=AccessKubeletAPITechnique, vid="KHV045", ) @@ -206,7 +209,7 @@ class ExposedKubeletCmdline(Vulnerability, Event): self, component=Kubelet, name="Exposed Kubelet Cmdline", - category=InformationDisclosure, + category=AccessKubeletAPITechnique, vid="KHV046", ) self.cmdline = cmdline diff --git a/kube_hunter/modules/hunting/mounts.py b/kube_hunter/modules/hunting/mounts.py index 6120f59..8eb42d7 100644 --- a/kube_hunter/modules/hunting/mounts.py +++ b/kube_hunter/modules/hunting/mounts.py @@ -5,12 +5,7 @@ import uuid from kube_hunter.conf import get_config from kube_hunter.core.events import handler from kube_hunter.core.events.types import Event, Vulnerability -from kube_hunter.core.types import ( - ActiveHunter, - Hunter, - KubernetesCluster, - PrivilegeEscalation, -) +from kube_hunter.core.types import ActiveHunter, Hunter, KubernetesCluster, HostPathMountPrivilegeEscalationTechnique from kube_hunter.modules.hunting.kubelet import ( ExposedPodsHandler, ExposedRunHandler, @@ -28,7 +23,7 @@ class WriteMountToVarLog(Vulnerability, Event): self, KubernetesCluster, "Pod With Mount To /var/log", - category=PrivilegeEscalation, + category=HostPathMountPrivilegeEscalationTechnique, vid="KHV047", ) self.pods = pods @@ -44,7 +39,7 @@ class DirectoryTraversalWithKubelet(Vulnerability, Event): self, KubernetesCluster, "Root Traversal Read On The Kubelet", - category=PrivilegeEscalation, + category=HostPathMountPrivilegeEscalationTechnique, ) self.output = output self.evidence = f"output: {self.output}" diff --git a/kube_hunter/modules/hunting/proxy.py b/kube_hunter/modules/hunting/proxy.py index 4f5a7af..0570d3d 100644 --- a/kube_hunter/modules/hunting/proxy.py +++ b/kube_hunter/modules/hunting/proxy.py @@ -10,7 +10,7 @@ from kube_hunter.core.types import ( ActiveHunter, Hunter, KubernetesCluster, - InformationDisclosure, + ConnectFromProxyServerTechnique, ) from kube_hunter.modules.discovery.dashboard import KubeDashboardEvent from kube_hunter.modules.discovery.proxy import KubeProxyEvent @@ -26,7 +26,7 @@ class KubeProxyExposed(Vulnerability, Event): self, KubernetesCluster, "Proxy Exposed", - category=InformationDisclosure, + category=ConnectFromProxyServerTechnique, vid="KHV049", ) @@ -123,5 +123,6 @@ class K8sVersionDisclosureProve(ActiveHunter): version=version_metadata["gitVersion"], from_endpoint="/version", extra_info="on kube-proxy", + category=ConnectFromProxyServerTechnique, ) ) diff --git a/kube_hunter/modules/hunting/secrets.py b/kube_hunter/modules/hunting/secrets.py index b505e32..36e09d7 100644 --- a/kube_hunter/modules/hunting/secrets.py +++ b/kube_hunter/modules/hunting/secrets.py @@ -3,7 +3,7 @@ import os from kube_hunter.core.events import handler from kube_hunter.core.events.types import Vulnerability, Event -from kube_hunter.core.types import Hunter, KubernetesCluster, AccessRisk +from kube_hunter.core.types import Hunter, KubernetesCluster, AccessContainerServiceAccountTechnique from kube_hunter.modules.discovery.hosts import RunningAsPodEvent logger = logging.getLogger(__name__) @@ -17,7 +17,7 @@ class ServiceAccountTokenAccess(Vulnerability, Event): self, KubernetesCluster, name="Read access to pod's service account token", - category=AccessRisk, + category=AccessContainerServiceAccountTechnique, vid="KHV050", ) self.evidence = evidence @@ -31,7 +31,7 @@ class SecretsAccess(Vulnerability, Event): self, component=KubernetesCluster, name="Access to pod's secrets", - category=AccessRisk, + category=AccessContainerServiceAccountTechnique, ) self.evidence = evidence diff --git a/kube_hunter/modules/report/base.py b/kube_hunter/modules/report/base.py index d8fed44..3382c35 100644 --- a/kube_hunter/modules/report/base.py +++ b/kube_hunter/modules/report/base.py @@ -36,7 +36,7 @@ class BaseReporter: { "location": vuln.location(), "vid": vuln.get_vid(), - "category": vuln.category.name, + "category": vuln.category.get_name(), "severity": vuln.get_severity(), "vulnerability": vuln.get_name(), "description": vuln.explain(), diff --git a/kube_hunter/modules/report/plain.py b/kube_hunter/modules/report/plain.py index b2eb573..913929c 100644 --- a/kube_hunter/modules/report/plain.py +++ b/kube_hunter/modules/report/plain.py @@ -83,7 +83,7 @@ class PlainReporter(BaseReporter): column_names = [ "ID", "Location", - "Category", + "MITRE Category", "Vulnerability", "Description", "Evidence", @@ -91,7 +91,7 @@ class PlainReporter(BaseReporter): vuln_table = PrettyTable(column_names, hrules=ALL) vuln_table.align = "l" vuln_table.max_width = MAX_TABLE_WIDTH - vuln_table.sortby = "Category" + vuln_table.sortby = "MITRE Category" vuln_table.reversesort = True vuln_table.padding_width = 1 vuln_table.header_style = "upper" @@ -101,10 +101,11 @@ class PlainReporter(BaseReporter): evidence = str(vuln.evidence) if len(evidence) > EVIDENCE_PREVIEW: evidence = evidence[:EVIDENCE_PREVIEW] + "..." + row = [ vuln.get_vid(), vuln.location(), - vuln.category.name, + vuln.category.get_name(), vuln.get_name(), vuln.explain(), evidence, diff --git a/tests/hunting/test_apiserver_hunter.py b/tests/hunting/test_apiserver_hunter.py index 7cd6b20..774d485 100644 --- a/tests/hunting/test_apiserver_hunter.py +++ b/tests/hunting/test_apiserver_hunter.py @@ -1,4 +1,5 @@ # flake8: noqa: E402 +from kube_hunter.core.types.vulnerabilities import AccessK8sApiServerTechnique import requests_mock import time @@ -21,7 +22,7 @@ from kube_hunter.modules.hunting.apiserver import ( from kube_hunter.modules.hunting.apiserver import ApiServerPassiveHunterFinished from kube_hunter.modules.hunting.apiserver import CreateANamespace, DeleteANamespace from kube_hunter.modules.discovery.apiserver import ApiServer -from kube_hunter.core.types import UnauthenticatedAccess, InformationDisclosure +from kube_hunter.core.types import ExposedSensitiveInterfacesTechnique, AccessK8sApiServerTechnique from kube_hunter.core.events import handler counter = 0 @@ -181,10 +182,10 @@ class test_ListClusterRoles: class test_ServerApiAccess: def __init__(self, event): print("ServerApiAccess") - if event.category == UnauthenticatedAccess: + if event.category == ExposedSensitiveInterfacesTechnique: assert event.auth_token is None else: - assert event.category == InformationDisclosure + assert event.category == AccessK8sApiServerTechnique assert event.auth_token == "so-secret" global counter counter += 1