Compare commits

..

4 Commits

Author SHA1 Message Date
Daniel Sagi
75a7abe838 fixed linting with exceptions 2021-06-24 18:31:29 +03:00
Daniel Sagi
813c41baae fixed new linting 2021-06-24 18:11:41 +03:00
Daniel Sagi
8ad0775006 Merge branch 'main' into feature/support_multiple_subscription 2021-06-24 18:08:35 +03:00
Daniel Sagi
a0f753fe3b removed redundant call for /pods again from /var/log mount hunter, by using multiple subscription 2021-04-26 22:33:32 +03:00
31 changed files with 263 additions and 507 deletions

View File

@@ -10,7 +10,7 @@ name: Release
jobs:
build:
name: Upload Release Asset
runs-on: ubuntu-latest
runs-on: ubuntu-16.04
steps:
- name: Checkout code
uses: actions/checkout@v2

View File

@@ -13,7 +13,7 @@ jobs:
fail-fast: false
matrix:
python-version: ["3.6", "3.7", "3.8", "3.9"]
os: [ubuntu-latest]
os: [ubuntu-20.04, ubuntu-18.04, ubuntu-16.04]
steps:
- uses: actions/checkout@v2
@@ -23,10 +23,26 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Get pip cache dir
id: pip-cache
run: |
echo "::set-output name=dir::$(pip cache dir)"
- name: Cache
uses: actions/cache@v2
with:
path: ${{ steps.pip-cache.outputs.dir }}
key:
${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('requirements-dev.txt') }}
restore-keys: |
${{ matrix.os }}-${{ matrix.python-version }}-
- name: Install dependencies
run: |
python -m pip install -U pip
python -m pip install -e .
python -m pip install -U wheel
python -m pip install -r requirements.txt
python -m pip install -r requirements-dev.txt
- name: Test
shell: bash

View File

@@ -27,27 +27,21 @@ kube-hunter hunts for security weaknesses in Kubernetes clusters. The tool was d
Table of Contents
=================
- [Table of Contents](#table-of-contents)
- [Hunting](#hunting)
- [Where should I run kube-hunter?](#where-should-i-run-kube-hunter)
- [Scanning options](#scanning-options)
- [Authentication](#authentication)
- [Active Hunting](#active-hunting)
- [List of tests](#list-of-tests)
- [Nodes Mapping](#nodes-mapping)
- [Output](#output)
- [Dispatching](#dispatching)
- [Advanced Usage](#advanced-usage)
- [Azure Quick Scanning](#azure-quick-scanning)
- [Deployment](#deployment)
- [On Machine](#on-machine)
- [Prerequisites](#prerequisites)
- [Install with pip](#install-with-pip)
- [Run from source](#run-from-source)
- [Container](#container)
- [Pod](#pod)
- [Contribution](#contribution)
- [License](#license)
* [Hunting](#hunting)
* [Where should I run kube-hunter?](#where-should-i-run-kube-hunter)
* [Scanning options](#scanning-options)
* [Active Hunting](#active-hunting)
* [List of tests](#list-of-tests)
* [Nodes Mapping](#nodes-mapping)
* [Output](#output)
* [Dispatching](#dispatching)
* [Advanced Usage](#advanced-usage)
* [Deployment](#deployment)
* [On Machine](#on-machine)
* [Prerequisites](#prerequisites)
* [Container](#container)
* [Pod](#pod)
* [Contribution](#contribution)
## Hunting
@@ -59,7 +53,7 @@ Run kube-hunter on any machine (including your laptop), select Remote scanning a
You can run kube-hunter directly on a machine in the cluster, and select the option to probe all the local network interfaces.
You can also run kube-hunter in a pod within the cluster. This indicates how exposed your cluster would be if one of your application pods is compromised (through a software vulnerability, for example). (_`--pod` flag_)
You can also run kube-hunter in a pod within the cluster. This indicates how exposed your cluster would be if one of your application pods is compromised (through a software vulnerability, for example).
### Scanning options
@@ -88,20 +82,6 @@ Set `--k8s-auto-discover-nodes` flag to query Kubernetes for all nodes in the cl
Also note, that this is always done when using `--pod` mode.
### Authentication
In order to mimic an attacker in it's early stages, kube-hunter requires no authentication for the hunt.
* **Impersonate** - You can provide kube-hunter with a specific service account token to use when hunting by manually passing the JWT Bearer token of the service-account secret with the `--service-account-token` flag.
Example:
```bash
$ kube-hunter --active --service-account-token eyJhbGciOiJSUzI1Ni...
```
* When runing with `--pod` flag, kube-hunter uses the service account token [mounted inside the pod](https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/) to authenticate to services it finds during the hunt.
* if specified, `--service-account-token` flag takes priority when running as a pod
### Active Hunting
Active hunting is an option in which kube-hunter will exploit vulnerabilities it finds, to explore for further vulnerabilities.
@@ -141,7 +121,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.

View File

@@ -197,9 +197,9 @@ GEM
html-pipeline (~> 2.2)
jekyll (>= 3.0, < 5.0)
kramdown (2.3.0)
rexml (>= 3.2.5)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (>= 2.3.1)
kramdown (~> 2.0)
liquid (4.0.3)
listen (3.4.0)
rb-fsevent (~> 0.10, >= 0.10.3)
@@ -212,7 +212,7 @@ GEM
jekyll-seo-tag (~> 2.1)
minitest (5.14.3)
multipart-post (2.1.1)
nokogiri (>= 1.11.4)
nokogiri (1.11.1)
mini_portile2 (~> 2.5.0)
racc (~> 1.4)
octokit (4.20.0)

View File

@@ -26,7 +26,6 @@ config = Config(
remote=args.remote,
statistics=args.statistics,
k8s_auto_discover_nodes=args.k8s_auto_discover_nodes,
service_account_token=args.service_account_token,
kubeconfig=args.kubeconfig,
)
setup_logger(args.log, args.log_file)

View File

@@ -37,7 +37,6 @@ class Config:
reporter: Optional[Any] = None
statistics: bool = False
k8s_auto_discover_nodes: bool = False
service_account_token: Optional[str] = None
kubeconfig: Optional[str] = None

View File

@@ -57,14 +57,6 @@ def parser_add_arguments(parser):
"NOTE: this is automatically switched on in --pod mode.",
)
parser.add_argument(
"--service-account-token",
type=str,
metavar="JWT_TOKEN",
help="Manually specify the service account jwt token to use for authenticating in the hunting process "
"NOTE: This overrides the loading of the pod's bounded authentication when running in --pod mode",
)
parser.add_argument(
"--kubeconfig",
type=str,

View File

@@ -3,32 +3,15 @@ import threading
import requests
from kube_hunter.conf import get_config
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,
from kube_hunter.core.types import (
InformationDisclosure,
DenialOfService,
RemoteCodeExec,
IdentityTheft,
PrivilegeEscalation,
AccessRisk,
UnauthenticatedAccess,
KubernetesCluster,
)
logger = logging.getLogger(__name__)
@@ -100,12 +83,6 @@ class Service:
self.path = path
self.role = "Node"
# if a service account token was specified, we load it to the Service class
# We load it here because generally all kuberentes services could be authenticated with the token
config = get_config()
if config.service_account_token:
self.auth_token = config.service_account_token
def get_name(self):
return self.name
@@ -119,30 +96,13 @@ class Service:
class Vulnerability:
severity = dict(
{
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",
InformationDisclosure: "medium",
DenialOfService: "medium",
RemoteCodeExec: "high",
IdentityTheft: "high",
PrivilegeEscalation: "high",
AccessRisk: "low",
UnauthenticatedAccess: "low",
}
)
@@ -247,21 +207,18 @@ class ReportDispatched(Event):
class K8sVersionDisclosure(Vulnerability, Event):
"""The kubernetes version could be obtained from the {} endpoint"""
def __init__(self, version, from_endpoint, extra_info="", category=None):
def __init__(self, version, from_endpoint, extra_info=""):
Vulnerability.__init__(
self,
KubernetesCluster,
"K8s Version Disclosure",
category=ExposedSensitiveInterfacesTechnique,
category=InformationDisclosure,
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

94
kube_hunter/core/types.py Normal file
View File

@@ -0,0 +1,94 @@
class HunterBase:
publishedVulnerabilities = 0
@staticmethod
def parse_docs(docs):
"""returns tuple of (name, docs)"""
if not docs:
return __name__, "<no documentation>"
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 "<no documentation>"
@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

View File

@@ -1,4 +0,0 @@
# flake8: noqa: E402
from .hunters import *
from .components import *
from .vulnerabilities import *

View File

@@ -1,28 +0,0 @@
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"

View File

@@ -1,36 +0,0 @@
class HunterBase:
publishedVulnerabilities = 0
@staticmethod
def parse_docs(docs):
"""returns tuple of (name, docs)"""
if not docs:
return __name__, "<no documentation>"
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 "<no documentation>"
@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

View File

@@ -1,188 +0,0 @@
"""
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: <MITRE CATEGORY> // <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 // <CVE Category name>
"""
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)"

View File

@@ -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, AWS, Azure, InstanceMetadataApiTechnique
from kube_hunter.core.types import Discovery, InformationDisclosure, AWS, Azure
logger = logging.getLogger(__name__)
@@ -19,17 +19,11 @@ logger = logging.getLogger(__name__)
class RunningAsPodEvent(Event):
def __init__(self):
self.name = "Running from within a pod"
self.auth_token = self.get_service_account_file("token")
self.client_cert = self.get_service_account_file("ca.crt")
self.namespace = self.get_service_account_file("namespace")
self.kubeservicehost = os.environ.get("KUBERNETES_SERVICE_HOST", None)
# if service account token was manually specified, we don't load the token file
config = get_config()
if config.service_account_token:
self.auth_token = config.service_account_token
else:
self.auth_token = self.get_service_account_file("token")
# Event's logical location to be used mainly for reports.
def location(self):
location = "Local to Pod"
@@ -55,7 +49,7 @@ class AWSMetadataApi(Vulnerability, Event):
self,
AWS,
"AWS Metadata Exposure",
category=InstanceMetadataApiTechnique,
category=InformationDisclosure,
vid="KHV053",
)
self.cidr = cidr
@@ -70,7 +64,7 @@ class AzureMetadataApi(Vulnerability, Event):
self,
Azure,
"Azure Metadata Exposure",
category=InstanceMetadataApiTechnique,
category=InformationDisclosure,
vid="KHV003",
)
self.cidr = cidr
@@ -129,15 +123,15 @@ class FromPodHostDiscovery(Discovery):
self.publish_event(HostScanEvent())
else:
# Discover cluster subnets, we'll scan all these hosts
cloud, subnets = None, list()
cloud = None
if self.is_azure_pod():
subnets, cloud = self.azure_metadata_discovery()
elif self.is_aws_pod_v1():
subnets, cloud = self.aws_metadata_v1_discovery()
elif self.is_aws_pod_v2():
subnets, cloud = self.aws_metadata_v2_discovery()
subnets += self.gateway_discovery()
else:
subnets = self.gateway_discovery()
should_scan_apiserver = False
if self.event.kubeservicehost:
@@ -221,27 +215,19 @@ class FromPodHostDiscovery(Discovery):
"http://169.254.169.254/latest/meta-data/mac",
timeout=config.network_timeout,
).text
logger.debug(f"Extracted mac from aws's metadata v1: {mac_address}")
cidr = requests.get(
f"http://169.254.169.254/latest/meta-data/network/interfaces/macs/{mac_address}/subnet-ipv4-cidr-block",
timeout=config.network_timeout,
).text
logger.debug(f"Trying to extract cidr from aws's metadata v1: {cidr}")
).text.split("/")
try:
cidr = cidr.split("/")
address, subnet = (cidr[0], cidr[1])
subnet = subnet if not config.quick else "24"
cidr = f"{address}/{subnet}"
logger.debug(f"From pod discovered subnet {cidr}")
address, subnet = (cidr[0], cidr[1])
subnet = subnet if not config.quick else "24"
cidr = f"{address}/{subnet}"
logger.debug(f"From pod discovered subnet {cidr}")
self.publish_event(AWSMetadataApi(cidr=cidr))
return [(address, subnet)], "AWS"
except Exception as x:
logger.debug(f"ERROR: could not parse cidr from aws metadata api: {cidr} - {x}")
self.publish_event(AWSMetadataApi(cidr=cidr))
return [], "AWS"
return [(address, subnet)], "AWS"
# querying AWS's interface metadata api v2 | works only from a pod
def aws_metadata_v2_discovery(self):
@@ -263,19 +249,14 @@ class FromPodHostDiscovery(Discovery):
timeout=config.network_timeout,
).text.split("/")
try:
address, subnet = (cidr[0], cidr[1])
subnet = subnet if not config.quick else "24"
cidr = f"{address}/{subnet}"
logger.debug(f"From pod discovered subnet {cidr}")
address, subnet = (cidr[0], cidr[1])
subnet = subnet if not config.quick else "24"
cidr = f"{address}/{subnet}"
logger.debug(f"From pod discovered subnet {cidr}")
self.publish_event(AWSMetadataApi(cidr=cidr))
self.publish_event(AWSMetadataApi(cidr=cidr))
return [(address, subnet)], "AWS"
except Exception as x:
logger.debug(f"ERROR: could not parse cidr from aws metadata api: {cidr} - {x}")
return [], "AWS"
return [(address, subnet)], "AWS"
# querying azure's interface metadata api | works only from a pod
def azure_metadata_discovery(self):

View File

@@ -6,13 +6,13 @@ def list_all_k8s_cluster_nodes(kube_config=None, client=None):
logger = logging.getLogger(__name__)
try:
if kube_config:
logger.debug("Attempting to use kubeconfig file: %s", kube_config)
logger.info("Attempting to use kubeconfig file: %s", kube_config)
kubernetes.config.load_kube_config(config_file=kube_config)
else:
logger.debug("Attempting to use in cluster Kubernetes config")
logger.info("Attempting to use in cluster Kubernetes config")
kubernetes.config.load_incluster_config()
except kubernetes.config.config_exception.ConfigException as ex:
logger.debug(f"Failed to initiate Kubernetes client: {ex}")
logger.exception(f"Failed to initiate Kubernetes client: {ex}")
return
try:
@@ -24,4 +24,4 @@ def list_all_k8s_cluster_nodes(kube_config=None, client=None):
for addr in item.status.addresses:
yield addr.address
except Exception as ex:
logger.debug(f"Failed to list nodes from Kubernetes: {ex}")
logger.exception(f"Failed to list nodes from Kubernetes: {ex}")

View File

@@ -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, MountServicePrincipalTechnique, Azure
from kube_hunter.core.types import Hunter, ActiveHunter, IdentityTheft, Azure
logger = logging.getLogger(__name__)
@@ -20,7 +20,7 @@ class AzureSpnExposure(Vulnerability, Event):
self,
Azure,
"Azure SPN Exposure",
category=MountServicePrincipalTechnique,
category=IdentityTheft,
vid="KHV004",
)
self.container = container

View File

@@ -8,15 +8,10 @@ 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.vulnerabilities import (
AccessK8sApiServerTechnique,
ExposedSensitiveInterfacesTechnique,
GeneralDefenseEvasionTechnique,
DataDestructionTechnique,
ClusterAdminBindingTechnique,
NewContainerTechnique,
PrivilegedContainerTechnique,
SidecarInjectionTechnique,
from kube_hunter.core.types import (
AccessRisk,
InformationDisclosure,
UnauthenticatedAccess,
)
logger = logging.getLogger(__name__)
@@ -29,10 +24,10 @@ class ServerApiAccess(Vulnerability, Event):
def __init__(self, evidence, using_token):
if using_token:
name = "Access to API using service account token"
category = AccessK8sApiServerTechnique
category = InformationDisclosure
else:
name = "Unauthenticated access to API"
category = ExposedSensitiveInterfacesTechnique
category = UnauthenticatedAccess
Vulnerability.__init__(
self,
KubernetesCluster,
@@ -49,7 +44,7 @@ class ServerApiHTTPAccess(Vulnerability, Event):
def __init__(self, evidence):
name = "Insecure (HTTP) access to API"
category = ExposedSensitiveInterfacesTechnique
category = UnauthenticatedAccess
Vulnerability.__init__(
self,
KubernetesCluster,
@@ -64,7 +59,7 @@ class ApiInfoDisclosure(Vulnerability, Event):
"""Information Disclosure depending upon RBAC permissions and Kube-Cluster Setup"""
def __init__(self, evidence, using_token, name):
category = AccessK8sApiServerTechnique
category = InformationDisclosure
if using_token:
name += " using default service account token"
else:
@@ -116,7 +111,7 @@ class CreateANamespace(Vulnerability, Event):
self,
KubernetesCluster,
name="Created a namespace",
category=GeneralDefenseEvasionTechnique,
category=AccessRisk,
)
self.evidence = evidence
@@ -130,7 +125,7 @@ class DeleteANamespace(Vulnerability, Event):
self,
KubernetesCluster,
name="Delete a namespace",
category=DataDestructionTechnique,
category=AccessRisk,
)
self.evidence = evidence
@@ -141,7 +136,7 @@ class CreateARole(Vulnerability, Event):
"""
def __init__(self, evidence):
Vulnerability.__init__(self, KubernetesCluster, name="Created a role", category=GeneralDefenseEvasionTechnique)
Vulnerability.__init__(self, KubernetesCluster, name="Created a role", category=AccessRisk)
self.evidence = evidence
@@ -155,7 +150,7 @@ class CreateAClusterRole(Vulnerability, Event):
self,
KubernetesCluster,
name="Created a cluster role",
category=ClusterAdminBindingTechnique,
category=AccessRisk,
)
self.evidence = evidence
@@ -170,7 +165,7 @@ class PatchARole(Vulnerability, Event):
self,
KubernetesCluster,
name="Patched a role",
category=ClusterAdminBindingTechnique,
category=AccessRisk,
)
self.evidence = evidence
@@ -185,7 +180,7 @@ class PatchAClusterRole(Vulnerability, Event):
self,
KubernetesCluster,
name="Patched a cluster role",
category=ClusterAdminBindingTechnique,
category=AccessRisk,
)
self.evidence = evidence
@@ -198,7 +193,7 @@ class DeleteARole(Vulnerability, Event):
self,
KubernetesCluster,
name="Deleted a role",
category=DataDestructionTechnique,
category=AccessRisk,
)
self.evidence = evidence
@@ -211,7 +206,7 @@ class DeleteAClusterRole(Vulnerability, Event):
self,
KubernetesCluster,
name="Deleted a cluster role",
category=DataDestructionTechnique,
category=AccessRisk,
)
self.evidence = evidence
@@ -224,7 +219,7 @@ class CreateAPod(Vulnerability, Event):
self,
KubernetesCluster,
name="Created A Pod",
category=NewContainerTechnique,
category=AccessRisk,
)
self.evidence = evidence
@@ -237,7 +232,7 @@ class CreateAPrivilegedPod(Vulnerability, Event):
self,
KubernetesCluster,
name="Created A PRIVILEGED Pod",
category=PrivilegedContainerTechnique,
category=AccessRisk,
)
self.evidence = evidence
@@ -250,7 +245,7 @@ class PatchAPod(Vulnerability, Event):
self,
KubernetesCluster,
name="Patched A Pod",
category=SidecarInjectionTechnique,
category=AccessRisk,
)
self.evidence = evidence
@@ -263,7 +258,7 @@ class DeleteAPod(Vulnerability, Event):
self,
KubernetesCluster,
name="Deleted A Pod",
category=DataDestructionTechnique,
category=AccessRisk,
)
self.evidence = evidence
@@ -382,7 +377,7 @@ class AccessApiServerWithToken(AccessApiServer):
super().__init__(event)
assert self.event.auth_token
self.headers = {"Authorization": f"Bearer {self.event.auth_token}"}
self.category = AccessK8sApiServerTechnique
self.category = InformationDisclosure
self.with_token = True

View File

@@ -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, ARPPoisoningTechnique
from kube_hunter.core.types import ActiveHunter, KubernetesCluster, IdentityTheft
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=ARPPoisoningTechnique,
category=IdentityTheft,
vid="KHV020",
)

View File

@@ -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, ARPPoisoningTechnique, KubernetesCluster
from kube_hunter.core.types import Hunter, AccessRisk, KubernetesCluster
logger = logging.getLogger(__name__)
@@ -20,7 +20,7 @@ class CapNetRawEnabled(Event, Vulnerability):
self,
KubernetesCluster,
name="CAP_NET_RAW Enabled",
category=ARPPoisoningTechnique,
category=AccessRisk,
)

View File

@@ -3,7 +3,7 @@ import logging
import base64
import re
from kube_hunter.core.types import Hunter, KubernetesCluster, GeneralSensitiveInformationTechnique
from kube_hunter.core.types import Hunter, KubernetesCluster, InformationDisclosure
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=GeneralSensitiveInformationTechnique,
category=InformationDisclosure,
vid="KHV021",
)
self.email = email

View File

@@ -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,
KubectlClient,
KubernetesCluster,
CVERemoteCodeExecutionCategory,
CVEPrivilegeEscalationCategory,
CVEDenialOfServiceTechnique,
RemoteCodeExec,
PrivilegeEscalation,
DenialOfService,
KubectlClient,
)
from kube_hunter.modules.discovery.kubectl import KubectlClientEvent
@@ -25,7 +25,7 @@ class ServerApiVersionEndPointAccessPE(Vulnerability, Event):
self,
KubernetesCluster,
name="Critical Privilege Escalation CVE",
category=CVEPrivilegeEscalationCategory,
category=PrivilegeEscalation,
vid="KHV022",
)
self.evidence = evidence
@@ -40,7 +40,7 @@ class ServerApiVersionEndPointAccessDos(Vulnerability, Event):
self,
KubernetesCluster,
name="Denial of Service to Kubernetes API Server",
category=CVEDenialOfServiceTechnique,
category=DenialOfService,
vid="KHV023",
)
self.evidence = evidence
@@ -55,7 +55,7 @@ class PingFloodHttp2Implementation(Vulnerability, Event):
self,
KubernetesCluster,
name="Possible Ping Flood Attack",
category=CVEDenialOfServiceTechnique,
category=DenialOfService,
vid="KHV024",
)
self.evidence = evidence
@@ -70,7 +70,7 @@ class ResetFloodHttp2Implementation(Vulnerability, Event):
self,
KubernetesCluster,
name="Possible Reset Flood Attack",
category=CVEDenialOfServiceTechnique,
category=DenialOfService,
vid="KHV025",
)
self.evidence = evidence
@@ -85,7 +85,7 @@ class ServerApiClusterScopedResourcesAccess(Vulnerability, Event):
self,
KubernetesCluster,
name="Arbitrary Access To Cluster Scoped Resources",
category=CVEPrivilegeEscalationCategory,
category=PrivilegeEscalation,
vid="KHV026",
)
self.evidence = evidence
@@ -100,7 +100,7 @@ class IncompleteFixToKubectlCpVulnerability(Vulnerability, Event):
self,
KubectlClient,
"Kubectl Vulnerable To CVE-2019-11246",
category=CVERemoteCodeExecutionCategory,
category=RemoteCodeExec,
vid="KHV027",
)
self.binary_version = binary_version
@@ -116,7 +116,7 @@ class KubectlCpVulnerability(Vulnerability, Event):
self,
KubectlClient,
"Kubectl Vulnerable To CVE-2019-1002101",
category=CVERemoteCodeExecutionCategory,
category=RemoteCodeExec,
vid="KHV028",
)
self.binary_version = binary_version

View File

@@ -3,7 +3,7 @@ import json
import requests
from kube_hunter.conf import get_config
from kube_hunter.core.types import Hunter, AccessK8sDashboardTechnique, KubernetesCluster
from kube_hunter.core.types import Hunter, RemoteCodeExec, 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=AccessK8sDashboardTechnique,
category=RemoteCodeExec,
vid="KHV029",
)
self.evidence = "nodes: {}".format(" ".join(nodes)) if nodes else None

View File

@@ -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, CoreDNSPoisoningTechnique
from kube_hunter.core.types import ActiveHunter, KubernetesCluster, IdentityTheft
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=CoreDNSPoisoningTechnique,
category=IdentityTheft,
vid="KHV030",
)
self.kubedns_pod_ip = kubedns_pod_ip

View File

@@ -8,10 +8,10 @@ from kube_hunter.core.types import (
ActiveHunter,
Hunter,
KubernetesCluster,
GeneralSensitiveInformationTechnique,
GeneralPersistenceTechnique,
ListK8sSecretsTechnique,
ExposedSensitiveInterfacesTechnique,
InformationDisclosure,
RemoteCodeExec,
UnauthenticatedAccess,
AccessRisk,
)
logger = logging.getLogger(__name__)
@@ -29,7 +29,7 @@ class EtcdRemoteWriteAccessEvent(Vulnerability, Event):
self,
KubernetesCluster,
name="Etcd Remote Write Access Event",
category=GeneralPersistenceTechnique,
category=RemoteCodeExec,
vid="KHV031",
)
self.evidence = write_res
@@ -43,7 +43,7 @@ class EtcdRemoteReadAccessEvent(Vulnerability, Event):
self,
KubernetesCluster,
name="Etcd Remote Read Access Event",
category=ListK8sSecretsTechnique,
category=AccessRisk,
vid="KHV032",
)
self.evidence = keys
@@ -58,7 +58,7 @@ class EtcdRemoteVersionDisclosureEvent(Vulnerability, Event):
self,
KubernetesCluster,
name="Etcd Remote version disclosure",
category=GeneralSensitiveInformationTechnique,
category=InformationDisclosure,
vid="KHV033",
)
self.evidence = version
@@ -74,7 +74,7 @@ class EtcdAccessEnabledWithoutAuthEvent(Vulnerability, Event):
self,
KubernetesCluster,
name="Etcd is accessible using insecure connection (HTTP)",
category=ExposedSensitiveInterfacesTechnique,
category=UnauthenticatedAccess,
vid="KHV034",
)
self.evidence = version

View File

@@ -16,12 +16,9 @@ from kube_hunter.core.types import (
ActiveHunter,
KubernetesCluster,
Kubelet,
ExposedSensitiveInterfacesTechnique,
ExecIntoContainerTechnique,
GeneralDefenseEvasionTechnique,
GeneralSensitiveInformationTechnique,
PrivilegedContainerTechnique,
AccessKubeletAPITechnique,
InformationDisclosure,
RemoteCodeExec,
AccessRisk,
)
from kube_hunter.modules.discovery.kubelet import (
ReadOnlyKubeletEvent,
@@ -38,7 +35,7 @@ class ExposedPodsHandler(Vulnerability, Event):
def __init__(self, pods):
Vulnerability.__init__(
self, component=Kubelet, name="Exposed Pods", category=AccessKubeletAPITechnique, vid="KHV052"
self, component=Kubelet, name="Exposed Pods", category=InformationDisclosure, vid="KHV052"
)
self.pods = pods
self.evidence = f"count: {len(self.pods)}"
@@ -53,7 +50,7 @@ class AnonymousAuthEnabled(Vulnerability, Event):
self,
component=Kubelet,
name="Anonymous Authentication",
category=ExposedSensitiveInterfacesTechnique,
category=RemoteCodeExec,
vid="KHV036",
)
@@ -66,7 +63,7 @@ class ExposedContainerLogsHandler(Vulnerability, Event):
self,
component=Kubelet,
name="Exposed Container Logs",
category=AccessKubeletAPITechnique,
category=InformationDisclosure,
vid="KHV037",
)
@@ -80,7 +77,7 @@ class ExposedRunningPodsHandler(Vulnerability, Event):
self,
component=Kubelet,
name="Exposed Running Pods",
category=AccessKubeletAPITechnique,
category=InformationDisclosure,
vid="KHV038",
)
self.count = count
@@ -95,7 +92,7 @@ class ExposedExecHandler(Vulnerability, Event):
self,
component=Kubelet,
name="Exposed Exec On Container",
category=ExecIntoContainerTechnique,
category=RemoteCodeExec,
vid="KHV039",
)
@@ -108,7 +105,7 @@ class ExposedRunHandler(Vulnerability, Event):
self,
component=Kubelet,
name="Exposed Run Inside Container",
category=ExecIntoContainerTechnique,
category=RemoteCodeExec,
vid="KHV040",
)
@@ -121,7 +118,7 @@ class ExposedPortForwardHandler(Vulnerability, Event):
self,
component=Kubelet,
name="Exposed Port Forward",
category=GeneralDefenseEvasionTechnique,
category=RemoteCodeExec,
vid="KHV041",
)
@@ -135,7 +132,7 @@ class ExposedAttachHandler(Vulnerability, Event):
self,
component=Kubelet,
name="Exposed Attaching To Container",
category=ExecIntoContainerTechnique,
category=RemoteCodeExec,
vid="KHV042",
)
@@ -149,7 +146,7 @@ class ExposedHealthzHandler(Vulnerability, Event):
self,
component=Kubelet,
name="Cluster Health Disclosure",
category=GeneralSensitiveInformationTechnique,
category=InformationDisclosure,
vid="KHV043",
)
self.status = status
@@ -166,7 +163,7 @@ the whole cluster"""
self,
component=KubernetesCluster,
name="Exposed Existing Privileged Container(s) Via Secure Kubelet Port",
category=PrivilegedContainerTechnique,
category=AccessRisk,
vid="KHV051",
)
self.exposed_existing_privileged_containers = exposed_existing_privileged_containers
@@ -181,7 +178,7 @@ class PrivilegedContainers(Vulnerability, Event):
self,
component=KubernetesCluster,
name="Privileged Container",
category=PrivilegedContainerTechnique,
category=AccessRisk,
vid="KHV044",
)
self.containers = containers
@@ -196,7 +193,7 @@ class ExposedSystemLogs(Vulnerability, Event):
self,
component=Kubelet,
name="Exposed System Logs",
category=AccessKubeletAPITechnique,
category=InformationDisclosure,
vid="KHV045",
)
@@ -209,7 +206,7 @@ class ExposedKubeletCmdline(Vulnerability, Event):
self,
component=Kubelet,
name="Exposed Kubelet Cmdline",
category=AccessKubeletAPITechnique,
category=InformationDisclosure,
vid="KHV046",
)
self.cmdline = cmdline

View File

@@ -5,7 +5,12 @@ 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, HostPathMountPrivilegeEscalationTechnique
from kube_hunter.core.types import (
ActiveHunter,
Hunter,
KubernetesCluster,
PrivilegeEscalation,
)
from kube_hunter.modules.hunting.kubelet import (
ExposedPodsHandler,
ExposedRunHandler,
@@ -23,7 +28,7 @@ class WriteMountToVarLog(Vulnerability, Event):
self,
KubernetesCluster,
"Pod With Mount To /var/log",
category=HostPathMountPrivilegeEscalationTechnique,
category=PrivilegeEscalation,
vid="KHV047",
)
self.pods = pods
@@ -39,7 +44,7 @@ class DirectoryTraversalWithKubelet(Vulnerability, Event):
self,
KubernetesCluster,
"Root Traversal Read On The Kubelet",
category=HostPathMountPrivilegeEscalationTechnique,
category=PrivilegeEscalation,
)
self.output = output
self.evidence = f"output: {self.output}"

View File

@@ -10,7 +10,7 @@ from kube_hunter.core.types import (
ActiveHunter,
Hunter,
KubernetesCluster,
ConnectFromProxyServerTechnique,
InformationDisclosure,
)
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=ConnectFromProxyServerTechnique,
category=InformationDisclosure,
vid="KHV049",
)
@@ -123,6 +123,5 @@ class K8sVersionDisclosureProve(ActiveHunter):
version=version_metadata["gitVersion"],
from_endpoint="/version",
extra_info="on kube-proxy",
category=ConnectFromProxyServerTechnique,
)
)

View File

@@ -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, AccessContainerServiceAccountTechnique
from kube_hunter.core.types import Hunter, KubernetesCluster, AccessRisk
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=AccessContainerServiceAccountTechnique,
category=AccessRisk,
vid="KHV050",
)
self.evidence = evidence
@@ -31,7 +31,7 @@ class SecretsAccess(Vulnerability, Event):
self,
component=KubernetesCluster,
name="Access to pod's secrets",
category=AccessContainerServiceAccountTechnique,
category=AccessRisk,
)
self.evidence = evidence

View File

@@ -36,7 +36,7 @@ class BaseReporter:
{
"location": vuln.location(),
"vid": vuln.get_vid(),
"category": vuln.category.get_name(),
"category": vuln.category.name,
"severity": vuln.get_severity(),
"vulnerability": vuln.get_name(),
"description": vuln.explain(),

View File

@@ -83,7 +83,7 @@ class PlainReporter(BaseReporter):
column_names = [
"ID",
"Location",
"MITRE Category",
"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 = "MITRE Category"
vuln_table.sortby = "Category"
vuln_table.reversesort = True
vuln_table.padding_width = 1
vuln_table.header_style = "upper"
@@ -101,11 +101,10 @@ class PlainReporter(BaseReporter):
evidence = str(vuln.evidence)
if len(evidence) > EVIDENCE_PREVIEW:
evidence = evidence[:EVIDENCE_PREVIEW] + "..."
row = [
vuln.get_vid(),
vuln.location(),
vuln.category.get_name(),
vuln.category.name,
vuln.get_name(),
vuln.explain(),
evidence,

View File

@@ -1,5 +1,4 @@
# flake8: noqa: E402
from kube_hunter.core.types.vulnerabilities import AccessK8sApiServerTechnique
import requests_mock
import time
@@ -22,7 +21,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 ExposedSensitiveInterfacesTechnique, AccessK8sApiServerTechnique
from kube_hunter.core.types import UnauthenticatedAccess, InformationDisclosure
from kube_hunter.core.events import handler
counter = 0
@@ -182,10 +181,10 @@ class test_ListClusterRoles:
class test_ServerApiAccess:
def __init__(self, event):
print("ServerApiAccess")
if event.category == ExposedSensitiveInterfacesTechnique:
if event.category == UnauthenticatedAccess:
assert event.auth_token is None
else:
assert event.category == AccessK8sApiServerTechnique
assert event.category == InformationDisclosure
assert event.auth_token == "so-secret"
global counter
counter += 1