Compare commits

...

18 Commits

Author SHA1 Message Date
Daniel Sagi
b08a86b104 Merge branch 'remove_scapy_usage' of https://github.com/aquasecurity/kube-hunter into remove_scapy_usage 2022-05-07 09:06:34 +03:00
Daniel Sagi
5bed4ca722 ignore B020 flake8 2022-05-07 09:05:42 +03:00
danielsagi
915d2bff8a added explicit new version to plugins in dockerfile installation 2022-05-06 22:47:01 -07:00
Daniel Sagi
8146810d44 added installation of default plugins to Dockerfile 2022-05-07 06:49:25 +03:00
Daniel Sagi
c54859ec37 removed arp and dns hunters usage due to it's violations of the scapy GPL2 license 2022-04-01 19:12:01 +03:00
Owen Rumney
e1896f3983 docs: lowercase the severities for AVD (#495)
Signed-off-by: Owen Rumney <owen@owenrumney.co.uk>
2022-03-25 09:03:43 +00:00
jerbia
fc7fbbf1fc Added severity to the kube-hunter found issues (#492) 2022-03-22 11:03:05 +02:00
danielsagi
7c62cc21af Feature: Custom Hunting (#489)
* added partial and partial-names flag. mechanism for whitelisting hunter subscrption for custom hunts

* changed name from partial to custom

* ran black to format

* flake8 formatting

* added documentation in readme for Custom hunting and made Advanced Usage a higher level topic

* added Collector, StartedInfo and SendFullReport to the core_hunters

* changed old name class-names to raw-hunter-names

* fixed bug in import loop
2022-01-28 18:54:36 +02:00
Juvenile
c17aa17096 ignore https certificate verification (#484) 2022-01-22 16:06:39 +02:00
testn
4204879251 Update README.md (#487)
Fix typo
2022-01-22 16:05:20 +02:00
danielsagi
a746bd0eb1 Added correct exception handling for discovery of Metadata apis (#488)
* Added correct exception handling for discovery of Metadata apis

* fixed linting issues
2022-01-22 15:56:04 +02:00
danielsagi
b379e64314 Added MITRE documentation in README (#485)
* Added documentation about differences between vulnerabilities and the attack matrix techniques

* moved docs to start of README, also created MITRE image, showing covered areas of kube-hunter

* fixed link in readme
2022-01-14 00:00:29 +02:00
danielsagi
00eb0dfa87 Switched CVE Hunting to optional & Minor core feature (#482)
* Removed automatic registration of the k8s CVE hunter

* Made CVE hunting optional, default set to not run
2021-10-16 17:49:00 +03:00
danielsagi
8d045fb1a8 Fix all of github action workflows (#481)
* fixed all of workflows
2021-10-16 17:23:41 +03:00
danielsagi
83b19d4208 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
2021-09-30 15:25:30 +03:00
danielsagi
473e4fe2b5 Make gateway discovery always run when running as pod #471 2021-07-23 21:09:28 +03:00
danielsagi
f67f08225c changed exception logs to debug logs in kubernetes_client nodes discovery (#470) 2021-07-22 15:57:25 +03:00
danielsagi
c96312b91e updated gemfile (#464) 2021-06-24 21:15:18 +03:00
81 changed files with 668 additions and 441 deletions

View File

@@ -1,5 +1,5 @@
[flake8] [flake8]
ignore = E203, E266, E501, W503, B903, T499 ignore = E203, E266, E501, W503, B903, T499, B020
max-line-length = 120 max-line-length = 120
max-complexity = 18 max-complexity = 18
select = B,C,E,F,W,B9,T4 select = B,C,E,F,W,B9,T4

View File

@@ -77,9 +77,10 @@ jobs:
python-version: '3.9' python-version: '3.9'
- name: Install dependencies - name: Install dependencies
shell: bash
run: | run: |
python -m pip install -U pip pip install -U pip
python -m pip install -r requirements-dev.txt make deps
- name: Build project - name: Build project
shell: bash shell: bash

View File

@@ -10,7 +10,7 @@ name: Release
jobs: jobs:
build: build:
name: Upload Release Asset name: Upload Release Asset
runs-on: ubuntu-16.04 runs-on: ubuntu-18.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
@@ -18,12 +18,14 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: '3.9' python-version: '3.8'
- name: Install dependencies - name: Install dependencies
shell: bash
run: | run: |
python -m pip install -U pip pip install -U pip
python -m pip install -r requirements-dev.txt pip install pyinstaller
make deps
- name: Build project - name: Build project
shell: bash shell: bash

View File

@@ -13,7 +13,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["3.6", "3.7", "3.8", "3.9"] python-version: ["3.6", "3.7", "3.8", "3.9"]
os: [ubuntu-20.04, ubuntu-18.04, ubuntu-16.04] os: [ubuntu-20.04, ubuntu-18.04]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -38,11 +38,11 @@ jobs:
${{ matrix.os }}-${{ matrix.python-version }}- ${{ matrix.os }}-${{ matrix.python-version }}-
- name: Install dependencies - name: Install dependencies
shell: bash
run: | run: |
python -m pip install -U pip pip install -U pip
python -m pip install -U wheel make dev-deps
python -m pip install -r requirements.txt make install
python -m pip install -r requirements-dev.txt
- name: Test - name: Test
shell: bash shell: bash

View File

@@ -26,4 +26,7 @@ RUN apk add --no-cache \
COPY --from=builder /usr/local/lib/python3.8/site-packages /usr/local/lib/python3.8/site-packages COPY --from=builder /usr/local/lib/python3.8/site-packages /usr/local/lib/python3.8/site-packages
COPY --from=builder /usr/local/bin/kube-hunter /usr/local/bin/kube-hunter COPY --from=builder /usr/local/bin/kube-hunter /usr/local/bin/kube-hunter
# Add default plugins: https://github.com/aquasecurity/kube-hunter-plugins
RUN pip install kube-hunter-arp-spoof>=0.0.3 kube-hunter-dns-spoof>=0.0.3
ENTRYPOINT ["kube-hunter"] ENTRYPOINT ["kube-hunter"]

BIN
MITRE.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View File

@@ -31,7 +31,7 @@ lint-check:
.PHONY: test .PHONY: test
test: test:
pytest python -m pytest
.PHONY: build .PHONY: build
build: build:

View File

@@ -18,7 +18,8 @@ kube-hunter hunts for security weaknesses in Kubernetes clusters. The tool was d
**Run kube-hunter**: kube-hunter is available as a container (aquasec/kube-hunter), and we also offer a web site at [kube-hunter.aquasec.com](https://kube-hunter.aquasec.com) where you can register online to receive a token allowing you to see and share the results online. You can also run the Python code yourself as described below. **Run kube-hunter**: kube-hunter is available as a container (aquasec/kube-hunter), and we also offer a web site at [kube-hunter.aquasec.com](https://kube-hunter.aquasec.com) where you can register online to receive a token allowing you to see and share the results online. You can also run the Python code yourself as described below.
**Explore vulnerabilities**: The kube-hunter knowledge base includes articles about discoverable vulnerabilities and issues. When kube-hunter reports an issue, it will show its VID (Vulnerability ID) so you can look it up in the KB at https://aquasecurity.github.io/kube-hunter/ **Explore vulnerabilities**: The kube-hunter knowledge base includes articles about discoverable vulnerabilities and issues. When kube-hunter reports an issue, it will show its VID (Vulnerability ID) so you can look it up in the KB at https://aquasecurity.github.io/kube-hunter/
_If you're interested in kube-hunter's integration with the Kubernetes ATT&CK Matrix [Continue Reading](#kuberentes-attck-matrix)_
**Contribute**: We welcome contributions, especially new hunter modules that perform additional tests. If you would like to develop your modules please read [Guidelines For Developing Your First kube-hunter Module](https://github.com/aquasecurity/kube-hunter/blob/main/CONTRIBUTING.md). **Contribute**: We welcome contributions, especially new hunter modules that perform additional tests. If you would like to develop your modules please read [Guidelines For Developing Your First kube-hunter Module](https://github.com/aquasecurity/kube-hunter/blob/main/CONTRIBUTING.md).
@@ -28,6 +29,7 @@ Table of Contents
================= =================
- [Table of Contents](#table-of-contents) - [Table of Contents](#table-of-contents)
- [Kubernetes ATT&CK Matrix](#kubernetes-attck-matrix)
- [Hunting](#hunting) - [Hunting](#hunting)
- [Where should I run kube-hunter?](#where-should-i-run-kube-hunter) - [Where should I run kube-hunter?](#where-should-i-run-kube-hunter)
- [Scanning options](#scanning-options) - [Scanning options](#scanning-options)
@@ -37,8 +39,9 @@ Table of Contents
- [Nodes Mapping](#nodes-mapping) - [Nodes Mapping](#nodes-mapping)
- [Output](#output) - [Output](#output)
- [Dispatching](#dispatching) - [Dispatching](#dispatching)
- [Advanced Usage](#advanced-usage) - [Advanced Usage](#advanced-usage)
- [Azure Quick Scanning](#azure-quick-scanning) - [Azure Quick Scanning](#azure-quick-scanning)
- [Custom Hunting](#custom-hunting)
- [Deployment](#deployment) - [Deployment](#deployment)
- [On Machine](#on-machine) - [On Machine](#on-machine)
- [Prerequisites](#prerequisites) - [Prerequisites](#prerequisites)
@@ -48,9 +51,21 @@ Table of Contents
- [Pod](#pod) - [Pod](#pod)
- [Contribution](#contribution) - [Contribution](#contribution)
- [License](#license) - [License](#license)
## Hunting
---
## Kubernetes ATT&CK Matrix
kube-hunter now supports the new format of the Kubernetes ATT&CK matrix.
While kube-hunter's vulnerabilities are a collection of creative techniques designed to mimic an attacker in the cluster (or outside it)
The Mitre's ATT&CK defines a more general standardised categories of techniques to do so.
You can think of kube-hunter vulnerabilities as small steps for an attacker, which follows the track of a more general technique he would aim for.
Most of kube-hunter's hunters and vulnerabilities can closly fall under those techniques, That's why we moved to follow the Matrix standard.
_Some kube-hunter vulnerabities which we could not map to Mitre technique, are prefixed with the `General` keyword_
![kube-hunter](./MITRE.png)
## Hunting
### Where should I run kube-hunter? ### Where should I run kube-hunter?
There are three different ways to run kube-hunter, each providing a different approach to detecting weaknesses in your cluster: There are three different ways to run kube-hunter, each providing a different approach to detecting weaknesses in your cluster:
@@ -61,6 +76,7 @@ You can run kube-hunter directly on a machine in the cluster, and select the opt
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). (_`--pod` flag_)
### Scanning options ### Scanning options
First check for these **[pre-requisites](#prerequisites)**. First check for these **[pre-requisites](#prerequisites)**.
@@ -141,11 +157,49 @@ Available dispatch methods are:
* KUBEHUNTER_HTTP_DISPATCH_URL (defaults to: https://localhost) * KUBEHUNTER_HTTP_DISPATCH_URL (defaults to: https://localhost)
* KUBEHUNTER_HTTP_DISPATCH_METHOD (defaults to: POST) * KUBEHUNTER_HTTP_DISPATCH_METHOD (defaults to: POST)
### Advanced Usage
#### Azure Quick Scanning ## 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. 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. To hardlimit subnet scanning to a `/24` CIDR, use the `--quick` option.
### Custom Hunting
Custom hunting enables advanced users to have control over what hunters gets registered at the start of a hunt.
**If you know what you are doing**, this can help if you want to adjust kube-hunter's hunting and discovery process for your needs.
Example:
```
kube-hunter --custom <HunterName1> <HunterName2>
```
Enabling Custom hunting removes all hunters from the hunting process, except the given whitelisted hunters.
The `--custom` flag reads a list of hunters class names, in order to view all of kube-hunter's class names, you can combine the flag `--raw-hunter-names` with the `--list` flag.
Example:
```
kube-hunter --active --list --raw-hunter-names
```
**Notice**: Due to kube-huner's architectural design, the following "Core Hunters/Classes" will always register (even when using custom hunting):
* HostDiscovery
* _Generates ip addresses for the hunt by given configurations_
* _Automatically discovers subnets using cloud Metadata APIs_
* FromPodHostDiscovery
* _Auto discover attack surface ip addresses for the hunt by using Pod based environment techniques_
* _Automatically discovers subnets using cloud Metadata APIs_
* PortDiscovery
* _Port scanning given ip addresses for known kubernetes services ports_
* Collector
* _Collects discovered vulnerabilities and open services for future report_
* StartedInfo
* _Prints the start message_
* SendFullReport
* _Dispatching the report based on given configurations_
## Deployment ## Deployment
There are three methods for deploying kube-hunter: There are three methods for deploying kube-hunter:

View File

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

View File

@@ -2,6 +2,7 @@
vid: KHV002 vid: KHV002
title: Kubernetes version disclosure title: Kubernetes version disclosure
categories: [Information Disclosure] categories: [Information Disclosure]
severity: low
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV003 vid: KHV003
title: Azure Metadata Exposure title: Azure Metadata Exposure
categories: [Information Disclosure] categories: [Information Disclosure]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV004 vid: KHV004
title: Azure SPN Exposure title: Azure SPN Exposure
categories: [Identity Theft] categories: [Identity Theft]
severity: medium
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV005 vid: KHV005
title: Access to Kubernetes API title: Access to Kubernetes API
categories: [Information Disclosure, Unauthenticated Access] categories: [Information Disclosure, Unauthenticated Access]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV006 vid: KHV006
title: Insecure (HTTP) access to Kubernetes API title: Insecure (HTTP) access to Kubernetes API
categories: [Unauthenticated Access] categories: [Unauthenticated Access]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV007 vid: KHV007
title: Specific Access to Kubernetes API title: Specific Access to Kubernetes API
categories: [Access Risk] categories: [Access Risk]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV020 vid: KHV020
title: Possible Arp Spoof title: Possible Arp Spoof
categories: [IdentityTheft] categories: [IdentityTheft]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV021 vid: KHV021
title: Certificate Includes Email Address title: Certificate Includes Email Address
categories: [Information Disclosure] categories: [Information Disclosure]
severity: low
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV022 vid: KHV022
title: Critical Privilege Escalation CVE title: Critical Privilege Escalation CVE
categories: [Privilege Escalation] categories: [Privilege Escalation]
severity: critical
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV023 vid: KHV023
title: Denial of Service to Kubernetes API Server title: Denial of Service to Kubernetes API Server
categories: [Denial Of Service] categories: [Denial Of Service]
severity: medium
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV024 vid: KHV024
title: Possible Ping Flood Attack title: Possible Ping Flood Attack
categories: [Denial Of Service] categories: [Denial Of Service]
severity: medium
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV025 vid: KHV025
title: Possible Reset Flood Attack title: Possible Reset Flood Attack
categories: [Denial Of Service] categories: [Denial Of Service]
severity: medium
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV026 vid: KHV026
title: Arbitrary Access To Cluster Scoped Resources title: Arbitrary Access To Cluster Scoped Resources
categories: [PrivilegeEscalation] categories: [PrivilegeEscalation]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV027 vid: KHV027
title: Kubectl Vulnerable To CVE-2019-11246 title: Kubectl Vulnerable To CVE-2019-11246
categories: [Remote Code Execution] categories: [Remote Code Execution]
severity: medium
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV028 vid: KHV028
title: Kubectl Vulnerable To CVE-2019-1002101 title: Kubectl Vulnerable To CVE-2019-1002101
categories: [Remote Code Execution] categories: [Remote Code Execution]
severity: medium
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV029 vid: KHV029
title: Dashboard Exposed title: Dashboard Exposed
categories: [Remote Code Execution] categories: [Remote Code Execution]
severity: critical
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}
@@ -12,4 +13,5 @@ An open Kubernetes Dashboard was detected. The Kubernetes Dashboard can be used
## Remediation ## Remediation
Do not leave the Dashboard insecured. Do not leave the Dashboard insecured.

View File

@@ -2,6 +2,7 @@
vid: KHV030 vid: KHV030
title: Possible DNS Spoof title: Possible DNS Spoof
categories: [Identity Theft] categories: [Identity Theft]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV031 vid: KHV031
title: Etcd Remote Write Access Event title: Etcd Remote Write Access Event
categories: [Remote Code Execution] categories: [Remote Code Execution]
severity: critical
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV032 vid: KHV032
title: Etcd Remote Read Access Event title: Etcd Remote Read Access Event
categories: [Access Risk] categories: [Access Risk]
severity: critical
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV033 vid: KHV033
title: Etcd Remote version disclosure title: Etcd Remote version disclosure
categories: [Information Disclosure] categories: [Information Disclosure]
severity: medium
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV034 vid: KHV034
title: Etcd is accessible using insecure connection (HTTP) title: Etcd is accessible using insecure connection (HTTP)
categories: [Unauthenticated Access] categories: [Unauthenticated Access]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV036 vid: KHV036
title: Anonymous Authentication title: Anonymous Authentication
categories: [Remote Code Execution] categories: [Remote Code Execution]
severity: critical
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV037 vid: KHV037
title: Exposed Container Logs title: Exposed Container Logs
categories: [Information Disclosure] categories: [Information Disclosure]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV038 vid: KHV038
title: Exposed Running Pods title: Exposed Running Pods
categories: [Information Disclosure] categories: [Information Disclosure]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV039 vid: KHV039
title: Exposed Exec On Container title: Exposed Exec On Container
categories: [Remote Code Execution] categories: [Remote Code Execution]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV040 vid: KHV040
title: Exposed Run Inside Container title: Exposed Run Inside Container
categories: [Remote Code Execution] categories: [Remote Code Execution]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV041 vid: KHV041
title: Exposed Port Forward title: Exposed Port Forward
categories: [Remote Code Execution] categories: [Remote Code Execution]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV042 vid: KHV042
title: Exposed Attaching To Container title: Exposed Attaching To Container
categories: [Remote Code Execution] categories: [Remote Code Execution]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV043 vid: KHV043
title: Cluster Health Disclosure title: Cluster Health Disclosure
categories: [Information Disclosure] categories: [Information Disclosure]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV044 vid: KHV044
title: Privileged Container title: Privileged Container
categories: [Access Risk] categories: [Access Risk]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV045 vid: KHV045
title: Exposed System Logs title: Exposed System Logs
categories: [Information Disclosure] categories: [Information Disclosure]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV046 vid: KHV046
title: Exposed Kubelet Cmdline title: Exposed Kubelet Cmdline
categories: [Information Disclosure] categories: [Information Disclosure]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV047 vid: KHV047
title: Pod With Mount To /var/log title: Pod With Mount To /var/log
categories: [Privilege Escalation] categories: [Privilege Escalation]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV049 vid: KHV049
title: kubectl proxy Exposed title: kubectl proxy Exposed
categories: [Information Disclosure] categories: [Information Disclosure]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV050 vid: KHV050
title: Read access to Pod service account token title: Read access to Pod service account token
categories: [Access Risk] categories: [Access Risk]
severity: medium
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV051 vid: KHV051
title: Exposed Existing Privileged Containers Via Secure Kubelet Port title: Exposed Existing Privileged Containers Via Secure Kubelet Port
categories: [Access Risk] categories: [Access Risk]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV052 vid: KHV052
title: Exposed Pods title: Exposed Pods
categories: [Information Disclosure] categories: [Information Disclosure]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -2,6 +2,7 @@
vid: KHV053 vid: KHV053
title: AWS Metadata Exposure title: AWS Metadata Exposure
categories: [Information Disclosure] categories: [Information Disclosure]
severity: high
--- ---
# {{ page.vid }} - {{ page.title }} # {{ page.vid }} - {{ page.title }}

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# flake8: noqa: E402 # flake8: noqa: E402
from functools import partial
import logging import logging
import threading import threading
@@ -28,6 +29,8 @@ config = Config(
k8s_auto_discover_nodes=args.k8s_auto_discover_nodes, k8s_auto_discover_nodes=args.k8s_auto_discover_nodes,
service_account_token=args.service_account_token, service_account_token=args.service_account_token,
kubeconfig=args.kubeconfig, kubeconfig=args.kubeconfig,
enable_cve_hunting=args.enable_cve_hunting,
custom=args.custom,
) )
setup_logger(args.log, args.log_file) setup_logger(args.log, args.log_file)
set_config(config) set_config(config)
@@ -72,16 +75,20 @@ def interactive_set_config():
return True return True
def list_hunters(): def list_hunters(class_names=False):
print("\nPassive Hunters:\n----------------") print("\nPassive Hunters:\n----------------")
for hunter, docs in handler.passive_hunters.items(): for hunter, docs in handler.passive_hunters.items():
name, doc = hunter.parse_docs(docs) name, doc = hunter.parse_docs(docs)
if class_names:
name = hunter.__name__
print(f"* {name}\n {doc}\n") print(f"* {name}\n {doc}\n")
if config.active: if config.active:
print("\n\nActive Hunters:\n---------------") print("\n\nActive Hunters:\n---------------")
for hunter, docs in handler.active_hunters.items(): for hunter, docs in handler.active_hunters.items():
name, doc = hunter.parse_docs(docs) name, doc = hunter.parse_docs(docs)
if class_names:
name = hunter.__name__
print(f"* {name}\n {doc}\n") print(f"* {name}\n {doc}\n")
@@ -94,7 +101,10 @@ def main():
scan_options = [config.pod, config.cidr, config.remote, config.interface, config.k8s_auto_discover_nodes] scan_options = [config.pod, config.cidr, config.remote, config.interface, config.k8s_auto_discover_nodes]
try: try:
if args.list: if args.list:
list_hunters() if args.raw_hunter_names:
list_hunters(class_names=True)
else:
list_hunters()
return return
if not any(scan_options): if not any(scan_options):

View File

@@ -1,7 +1,11 @@
from dataclasses import dataclass from dataclasses import dataclass, field
from typing import Any, Optional from typing import Any, Optional
def get_default_core_hunters():
return ["FromPodHostDiscovery", "HostDiscovery", "PortDiscovery", "SendFullReport", "Collector", "StartedInfo"]
@dataclass @dataclass
class Config: class Config:
"""Config is a configuration container. """Config is a configuration container.
@@ -21,6 +25,7 @@ class Config:
- remote: Hosts to scan - remote: Hosts to scan
- report: Output format - report: Output format
- statistics: Include hunters statistics - statistics: Include hunters statistics
- enable_cve_hunting: enables cve hunting, shows cve results
""" """
active: bool = False active: bool = False
@@ -39,6 +44,10 @@ class Config:
k8s_auto_discover_nodes: bool = False k8s_auto_discover_nodes: bool = False
service_account_token: Optional[str] = None service_account_token: Optional[str] = None
kubeconfig: Optional[str] = None kubeconfig: Optional[str] = None
enable_cve_hunting: bool = False
custom: Optional[list] = None
raw_hunter_names: bool = False
core_hunters: list = field(default_factory=get_default_core_hunters)
_config: Optional[Config] = None _config: Optional[Config] = None

View File

@@ -4,10 +4,6 @@ DEFAULT_LEVEL = logging.INFO
DEFAULT_LEVEL_NAME = logging.getLevelName(DEFAULT_LEVEL) DEFAULT_LEVEL_NAME = logging.getLevelName(DEFAULT_LEVEL)
LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s %(message)s" LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s %(message)s"
# Suppress logging from scapy
logging.getLogger("scapy.runtime").setLevel(logging.CRITICAL)
logging.getLogger("scapy.loading").setLevel(logging.CRITICAL)
def setup_logger(level_name, logfile): def setup_logger(level_name, logfile):
# Remove any existing handlers # Remove any existing handlers

View File

@@ -46,6 +46,22 @@ def parser_add_arguments(parser):
help="One or more remote ip/dns to hunt", help="One or more remote ip/dns to hunt",
) )
parser.add_argument(
"-c",
"--custom",
nargs="+",
metavar="HUNTERS",
default=list(),
help="Custom hunting. Only given hunter names will register in the hunt."
"for a list of options run `--list --raw-hunter-names`",
)
parser.add_argument(
"--raw-hunter-names",
action="store_true",
help="Use in combination with `--list` to display hunter class names to pass for custom hunting flag",
)
parser.add_argument( parser.add_argument(
"--k8s-auto-discover-nodes", "--k8s-auto-discover-nodes",
action="store_true", action="store_true",
@@ -76,6 +92,12 @@ def parser_add_arguments(parser):
parser.add_argument("--active", action="store_true", help="Enables active hunting") parser.add_argument("--active", action="store_true", help="Enables active hunting")
parser.add_argument(
"--enable-cve-hunting",
action="store_true",
help="Show cluster CVEs based on discovered version (Depending on different vendors, may result in False Positives)",
)
parser.add_argument( parser.add_argument(
"--log", "--log",
type=str, type=str,

View File

@@ -62,7 +62,7 @@ class EventQueue(Queue):
###################################################### ######################################################
""" """
def subscribe(self, event, hook=None, predicate=None): def subscribe(self, event, hook=None, predicate=None, is_register=True):
""" """
The Subscribe Decorator - For Regular Registration The Subscribe Decorator - For Regular Registration
Use this to register for one event only. Your hunter will execute each time this event is published Use this to register for one event only. Your hunter will execute each time this event is published
@@ -74,12 +74,12 @@ class EventQueue(Queue):
""" """
def wrapper(hook): def wrapper(hook):
self.subscribe_event(event, hook=hook, predicate=predicate) self.subscribe_event(event, hook=hook, predicate=predicate, is_register=is_register)
return hook return hook
return wrapper return wrapper
def subscribe_many(self, events, hook=None, predicates=None): def subscribe_many(self, events, hook=None, predicates=None, is_register=True):
""" """
The Subscribe Many Decorator - For Multiple Registration, The Subscribe Many Decorator - For Multiple Registration,
When your attack needs several prerequisites to exist in the cluster, You need to register for multiple events. When your attack needs several prerequisites to exist in the cluster, You need to register for multiple events.
@@ -99,12 +99,12 @@ class EventQueue(Queue):
""" """
def wrapper(hook): def wrapper(hook):
self.subscribe_events(events, hook=hook, predicates=predicates) self.subscribe_events(events, hook=hook, predicates=predicates, is_register=is_register)
return hook return hook
return wrapper return wrapper
def subscribe_once(self, event, hook=None, predicate=None): def subscribe_once(self, event, hook=None, predicate=None, is_register=True):
""" """
The Subscribe Once Decorator - For Single Trigger Registration, The Subscribe Once Decorator - For Single Trigger Registration,
Use this when you want your hunter to execute only in your entire program run Use this when you want your hunter to execute only in your entire program run
@@ -125,7 +125,8 @@ class EventQueue(Queue):
hook.__new__ = __new__unsubscribe_self hook.__new__ = __new__unsubscribe_self
self.subscribe_event(event, hook=hook, predicate=predicate) self.subscribe_event(event, hook=hook, predicate=predicate, is_register=is_register)
return hook return hook
return wrapper return wrapper
@@ -256,7 +257,33 @@ class EventQueue(Queue):
self.hooks[event].append((hook, predicate)) self.hooks[event].append((hook, predicate))
logging.debug("{} subscribed to {}".format(hook, event)) logging.debug("{} subscribed to {}".format(hook, event))
def subscribe_event(self, event, hook=None, predicate=None): def allowed_for_custom_registration(self, target_hunter):
"""
Check if the partial input list contains the hunter we are about to register for events
If hunter is considered a Core hunter as specified in `config.core_hunters` we allow it anyway
Returns true if:
1. partial hunt is disabled
2. partial hunt is enabled and hunter is in core hunter class
3. partial hunt is enabled and hunter is specified in config.partial
@param target_hunter: hunter class for registration check
"""
config = get_config()
if not config.custom:
return True
hunter_class_name = target_hunter.__name__
if hunter_class_name in config.core_hunters or hunter_class_name in config.custom:
return True
return False
def subscribe_event(self, event, hook=None, predicate=None, is_register=True):
if not is_register:
return
if not self.allowed_for_custom_registration(hook):
return
if not self._register_hunters(hook): if not self._register_hunters(hook):
return return
@@ -267,9 +294,13 @@ class EventQueue(Queue):
else: else:
self._register_hook(event, hook, predicate) self._register_hook(event, hook, predicate)
def subscribe_events(self, events, hook=None, predicates=None): def subscribe_events(self, events, hook=None, predicates=None, is_register=True):
if not is_register:
return
if not self.allowed_for_custom_registration(hook):
return
if not self._register_hunters(hook): if not self._register_hunters(hook):
return False return
if predicates is None: if predicates is None:
predicates = [None] * len(events) predicates = [None] * len(events)

View File

@@ -3,15 +3,32 @@ import threading
import requests import requests
from kube_hunter.conf import get_config from kube_hunter.conf import get_config
from kube_hunter.core.types import ( from kube_hunter.core.types import KubernetesCluster
InformationDisclosure, from kube_hunter.core.types.vulnerabilities import (
DenialOfService, GeneralSensitiveInformationTechnique,
RemoteCodeExec, ExposedSensitiveInterfacesTechnique,
IdentityTheft, MountServicePrincipalTechnique,
PrivilegeEscalation, ListK8sSecretsTechnique,
AccessRisk, AccessContainerServiceAccountTechnique,
UnauthenticatedAccess, AccessK8sApiServerTechnique,
KubernetesCluster, AccessKubeletAPITechnique,
AccessK8sDashboardTechnique,
InstanceMetadataApiTechnique,
ExecIntoContainerTechnique,
SidecarInjectionTechnique,
NewContainerTechnique,
GeneralPersistenceTechnique,
HostPathMountPrivilegeEscalationTechnique,
PrivilegedContainerTechnique,
ClusterAdminBindingTechnique,
ARPPoisoningTechnique,
CoreDNSPoisoningTechnique,
DataDestructionTechnique,
GeneralDefenseEvasionTechnique,
ConnectFromProxyServerTechnique,
CVERemoteCodeExecutionCategory,
CVEPrivilegeEscalationCategory,
CVEDenialOfServiceTechnique,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -102,13 +119,30 @@ class Service:
class Vulnerability: class Vulnerability:
severity = dict( severity = dict(
{ {
InformationDisclosure: "medium", GeneralSensitiveInformationTechnique: "low",
DenialOfService: "medium", ExposedSensitiveInterfacesTechnique: "high",
RemoteCodeExec: "high", MountServicePrincipalTechnique: "high",
IdentityTheft: "high", ListK8sSecretsTechnique: "high",
PrivilegeEscalation: "high", AccessContainerServiceAccountTechnique: "low",
AccessRisk: "low", AccessK8sApiServerTechnique: "medium",
UnauthenticatedAccess: "low", 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): class K8sVersionDisclosure(Vulnerability, Event):
"""The kubernetes version could be obtained from the {} endpoint""" """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__( Vulnerability.__init__(
self, self,
KubernetesCluster, KubernetesCluster,
"K8s Version Disclosure", "K8s Version Disclosure",
category=InformationDisclosure, category=ExposedSensitiveInterfacesTechnique,
vid="KHV002", vid="KHV002",
) )
self.version = version self.version = version
self.from_endpoint = from_endpoint self.from_endpoint = from_endpoint
self.extra_info = extra_info self.extra_info = extra_info
self.evidence = version 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): def explain(self):
return self.__doc__.format(self.from_endpoint) + self.extra_info return self.__doc__.format(self.from_endpoint) + self.extra_info

View File

@@ -1,94 +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):
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

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

View File

@@ -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"

View File

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

@@ -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: <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.modules.discovery.kubernetes_client import list_all_k8s_cluster_nodes
from kube_hunter.core.events import handler from kube_hunter.core.events import handler
from kube_hunter.core.events.types import Event, NewHostEvent, Vulnerability 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__) logger = logging.getLogger(__name__)
@@ -55,7 +55,7 @@ class AWSMetadataApi(Vulnerability, Event):
self, self,
AWS, AWS,
"AWS Metadata Exposure", "AWS Metadata Exposure",
category=InformationDisclosure, category=InstanceMetadataApiTechnique,
vid="KHV053", vid="KHV053",
) )
self.cidr = cidr self.cidr = cidr
@@ -70,7 +70,7 @@ class AzureMetadataApi(Vulnerability, Event):
self, self,
Azure, Azure,
"Azure Metadata Exposure", "Azure Metadata Exposure",
category=InformationDisclosure, category=InstanceMetadataApiTechnique,
vid="KHV003", vid="KHV003",
) )
self.cidr = cidr self.cidr = cidr
@@ -129,15 +129,15 @@ class FromPodHostDiscovery(Discovery):
self.publish_event(HostScanEvent()) self.publish_event(HostScanEvent())
else: else:
# Discover cluster subnets, we'll scan all these hosts # Discover cluster subnets, we'll scan all these hosts
cloud = None cloud, subnets = None, list()
if self.is_azure_pod(): if self.is_azure_pod():
subnets, cloud = self.azure_metadata_discovery() subnets, cloud = self.azure_metadata_discovery()
elif self.is_aws_pod_v1(): elif self.is_aws_pod_v1():
subnets, cloud = self.aws_metadata_v1_discovery() subnets, cloud = self.aws_metadata_v1_discovery()
elif self.is_aws_pod_v2(): elif self.is_aws_pod_v2():
subnets, cloud = self.aws_metadata_v2_discovery() subnets, cloud = self.aws_metadata_v2_discovery()
else:
subnets = self.gateway_discovery() subnets += self.gateway_discovery()
should_scan_apiserver = False should_scan_apiserver = False
if self.event.kubeservicehost: if self.event.kubeservicehost:
@@ -166,7 +166,9 @@ class FromPodHostDiscovery(Discovery):
return True return True
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
logger.debug("Failed to connect AWS metadata server v1") logger.debug("Failed to connect AWS metadata server v1")
return False except Exception:
logger.debug("Unknown error when trying to connect to AWS metadata v1 API")
return False
def is_aws_pod_v2(self): def is_aws_pod_v2(self):
config = get_config() config = get_config()
@@ -189,7 +191,9 @@ class FromPodHostDiscovery(Discovery):
return True return True
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
logger.debug("Failed to connect AWS metadata server v2") logger.debug("Failed to connect AWS metadata server v2")
return False except Exception:
logger.debug("Unknown error when trying to connect to AWS metadata v2 API")
return False
def is_azure_pod(self): def is_azure_pod(self):
config = get_config() config = get_config()
@@ -206,7 +210,9 @@ class FromPodHostDiscovery(Discovery):
return True return True
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
logger.debug("Failed to connect Azure metadata server") logger.debug("Failed to connect Azure metadata server")
return False except Exception:
logger.debug("Unknown error when trying to connect to Azure metadata server")
return False
# for pod scanning # for pod scanning
def gateway_discovery(self): def gateway_discovery(self):
@@ -221,19 +227,27 @@ class FromPodHostDiscovery(Discovery):
"http://169.254.169.254/latest/meta-data/mac", "http://169.254.169.254/latest/meta-data/mac",
timeout=config.network_timeout, timeout=config.network_timeout,
).text ).text
logger.debug(f"Extracted mac from aws's metadata v1: {mac_address}")
cidr = requests.get( cidr = requests.get(
f"http://169.254.169.254/latest/meta-data/network/interfaces/macs/{mac_address}/subnet-ipv4-cidr-block", f"http://169.254.169.254/latest/meta-data/network/interfaces/macs/{mac_address}/subnet-ipv4-cidr-block",
timeout=config.network_timeout, timeout=config.network_timeout,
).text.split("/") ).text
logger.debug(f"Trying to extract cidr from aws's metadata v1: {cidr}")
address, subnet = (cidr[0], cidr[1]) try:
subnet = subnet if not config.quick else "24" cidr = cidr.split("/")
cidr = f"{address}/{subnet}" address, subnet = (cidr[0], cidr[1])
logger.debug(f"From pod discovered subnet {cidr}") 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 [(address, subnet)], "AWS" return [], "AWS"
# querying AWS's interface metadata api v2 | works only from a pod # querying AWS's interface metadata api v2 | works only from a pod
def aws_metadata_v2_discovery(self): def aws_metadata_v2_discovery(self):
@@ -255,14 +269,19 @@ class FromPodHostDiscovery(Discovery):
timeout=config.network_timeout, timeout=config.network_timeout,
).text.split("/") ).text.split("/")
address, subnet = (cidr[0], cidr[1]) try:
subnet = subnet if not config.quick else "24" address, subnet = (cidr[0], cidr[1])
cidr = f"{address}/{subnet}" subnet = subnet if not config.quick else "24"
logger.debug(f"From pod discovered subnet {cidr}") 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" return [(address, subnet)], "AWS"
except Exception as x:
logger.debug(f"ERROR: could not parse cidr from aws metadata api: {cidr} - {x}")
return [], "AWS"
# querying azure's interface metadata api | works only from a pod # querying azure's interface metadata api | works only from a pod
def azure_metadata_discovery(self): 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__) logger = logging.getLogger(__name__)
try: try:
if kube_config: if kube_config:
logger.info("Attempting to use kubeconfig file: %s", kube_config) logger.debug("Attempting to use kubeconfig file: %s", kube_config)
kubernetes.config.load_kube_config(config_file=kube_config) kubernetes.config.load_kube_config(config_file=kube_config)
else: else:
logger.info("Attempting to use in cluster Kubernetes config") logger.debug("Attempting to use in cluster Kubernetes config")
kubernetes.config.load_incluster_config() kubernetes.config.load_incluster_config()
except kubernetes.config.config_exception.ConfigException as ex: except kubernetes.config.config_exception.ConfigException as ex:
logger.exception(f"Failed to initiate Kubernetes client: {ex}") logger.debug(f"Failed to initiate Kubernetes client: {ex}")
return return
try: try:
@@ -24,4 +24,4 @@ def list_all_k8s_cluster_nodes(kube_config=None, client=None):
for addr in item.status.addresses: for addr in item.status.addresses:
yield addr.address yield addr.address
except Exception as ex: except Exception as ex:
logger.exception(f"Failed to list nodes from Kubernetes: {ex}") logger.debug(f"Failed to list nodes from Kubernetes: {ex}")

View File

@@ -2,12 +2,10 @@
from . import ( from . import (
aks, aks,
apiserver, apiserver,
arp,
capabilities, capabilities,
certificates, certificates,
cves, cves,
dashboard, dashboard,
dns,
etcd, etcd,
kubelet, kubelet,
mounts, mounts,

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

View File

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

View File

@@ -1,71 +0,0 @@
import logging
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.modules.hunting.capabilities import CapNetRawEnabled
logger = logging.getLogger(__name__)
class PossibleArpSpoofing(Vulnerability, Event):
"""A malicious pod running on the cluster could potentially run an ARP Spoof attack
and perform a MITM between pods on the node."""
def __init__(self):
Vulnerability.__init__(
self,
KubernetesCluster,
"Possible Arp Spoof",
category=IdentityTheft,
vid="KHV020",
)
@handler.subscribe(CapNetRawEnabled)
class ArpSpoofHunter(ActiveHunter):
"""Arp Spoof Hunter
Checks for the possibility of running an ARP spoof
attack from within a pod (results are based on the running node)
"""
def __init__(self, event):
self.event = event
def try_getting_mac(self, ip):
config = get_config()
ans = sr1(ARP(op=1, pdst=ip), timeout=config.network_timeout, verbose=0)
return ans[ARP].hwsrc if ans else None
def detect_l3_on_host(self, arp_responses):
"""returns True for an existence of an L3 network plugin"""
logger.debug("Attempting to detect L3 network plugin using ARP")
unique_macs = list({response[ARP].hwsrc for _, response in arp_responses})
# if LAN addresses not unique
if len(unique_macs) == 1:
# if an ip outside the subnets gets a mac address
outside_mac = self.try_getting_mac("1.1.1.1")
# outside mac is the same as lan macs
if outside_mac == unique_macs[0]:
return True
# only one mac address for whole LAN and outside
return False
def execute(self):
config = get_config()
self_ip = sr1(IP(dst="1.1.1.1", ttl=1) / ICMP(), verbose=0, timeout=config.network_timeout)[IP].dst
arp_responses, _ = srp(
Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(op=1, pdst=f"{self_ip}/24"),
timeout=config.network_timeout,
verbose=0,
)
# arp enabled on cluster and more than one pod on node
if len(arp_responses) > 1:
# L3 plugin not installed
if not self.detect_l3_on_host(arp_responses):
self.publish_event(PossibleArpSpoofing())

View File

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

View File

@@ -3,7 +3,7 @@ import logging
import base64 import base64
import re 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 import handler
from kube_hunter.core.events.types import Vulnerability, Event, Service from kube_hunter.core.events.types import Vulnerability, Event, Service
@@ -21,7 +21,7 @@ class CertificateEmail(Vulnerability, Event):
self, self,
KubernetesCluster, KubernetesCluster,
"Certificate Includes Email Address", "Certificate Includes Email Address",
category=InformationDisclosure, category=GeneralSensitiveInformationTechnique,
vid="KHV021", vid="KHV021",
) )
self.email = email self.email = email

View File

@@ -3,18 +3,20 @@ from packaging import version
from kube_hunter.conf import get_config from kube_hunter.conf import get_config
from kube_hunter.core.events import handler from kube_hunter.core.events import handler
from kube_hunter.core.events.types import Vulnerability, Event, K8sVersionDisclosure
from kube_hunter.core.events.types import K8sVersionDisclosure, Vulnerability, Event
from kube_hunter.core.types import ( from kube_hunter.core.types import (
Hunter, Hunter,
KubernetesCluster,
RemoteCodeExec,
PrivilegeEscalation,
DenialOfService,
KubectlClient, KubectlClient,
KubernetesCluster,
CVERemoteCodeExecutionCategory,
CVEPrivilegeEscalationCategory,
CVEDenialOfServiceTechnique,
) )
from kube_hunter.modules.discovery.kubectl import KubectlClientEvent from kube_hunter.modules.discovery.kubectl import KubectlClientEvent
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
config = get_config()
class ServerApiVersionEndPointAccessPE(Vulnerability, Event): class ServerApiVersionEndPointAccessPE(Vulnerability, Event):
@@ -25,7 +27,7 @@ class ServerApiVersionEndPointAccessPE(Vulnerability, Event):
self, self,
KubernetesCluster, KubernetesCluster,
name="Critical Privilege Escalation CVE", name="Critical Privilege Escalation CVE",
category=PrivilegeEscalation, category=CVEPrivilegeEscalationCategory,
vid="KHV022", vid="KHV022",
) )
self.evidence = evidence self.evidence = evidence
@@ -40,7 +42,7 @@ class ServerApiVersionEndPointAccessDos(Vulnerability, Event):
self, self,
KubernetesCluster, KubernetesCluster,
name="Denial of Service to Kubernetes API Server", name="Denial of Service to Kubernetes API Server",
category=DenialOfService, category=CVEDenialOfServiceTechnique,
vid="KHV023", vid="KHV023",
) )
self.evidence = evidence self.evidence = evidence
@@ -55,7 +57,7 @@ class PingFloodHttp2Implementation(Vulnerability, Event):
self, self,
KubernetesCluster, KubernetesCluster,
name="Possible Ping Flood Attack", name="Possible Ping Flood Attack",
category=DenialOfService, category=CVEDenialOfServiceTechnique,
vid="KHV024", vid="KHV024",
) )
self.evidence = evidence self.evidence = evidence
@@ -70,7 +72,7 @@ class ResetFloodHttp2Implementation(Vulnerability, Event):
self, self,
KubernetesCluster, KubernetesCluster,
name="Possible Reset Flood Attack", name="Possible Reset Flood Attack",
category=DenialOfService, category=CVEDenialOfServiceTechnique,
vid="KHV025", vid="KHV025",
) )
self.evidence = evidence self.evidence = evidence
@@ -85,7 +87,7 @@ class ServerApiClusterScopedResourcesAccess(Vulnerability, Event):
self, self,
KubernetesCluster, KubernetesCluster,
name="Arbitrary Access To Cluster Scoped Resources", name="Arbitrary Access To Cluster Scoped Resources",
category=PrivilegeEscalation, category=CVEPrivilegeEscalationCategory,
vid="KHV026", vid="KHV026",
) )
self.evidence = evidence self.evidence = evidence
@@ -100,7 +102,7 @@ class IncompleteFixToKubectlCpVulnerability(Vulnerability, Event):
self, self,
KubectlClient, KubectlClient,
"Kubectl Vulnerable To CVE-2019-11246", "Kubectl Vulnerable To CVE-2019-11246",
category=RemoteCodeExec, category=CVERemoteCodeExecutionCategory,
vid="KHV027", vid="KHV027",
) )
self.binary_version = binary_version self.binary_version = binary_version
@@ -116,7 +118,7 @@ class KubectlCpVulnerability(Vulnerability, Event):
self, self,
KubectlClient, KubectlClient,
"Kubectl Vulnerable To CVE-2019-1002101", "Kubectl Vulnerable To CVE-2019-1002101",
category=RemoteCodeExec, category=CVERemoteCodeExecutionCategory,
vid="KHV028", vid="KHV028",
) )
self.binary_version = binary_version self.binary_version = binary_version
@@ -199,7 +201,7 @@ class CveUtils:
return vulnerable return vulnerable
@handler.subscribe_once(K8sVersionDisclosure) @handler.subscribe_once(K8sVersionDisclosure, is_register=config.enable_cve_hunting)
class K8sClusterCveHunter(Hunter): class K8sClusterCveHunter(Hunter):
"""K8s CVE Hunter """K8s CVE Hunter
Checks if Node is running a Kubernetes version vulnerable to Checks if Node is running a Kubernetes version vulnerable to
@@ -224,6 +226,7 @@ class K8sClusterCveHunter(Hunter):
self.publish_event(vulnerability(self.event.version)) self.publish_event(vulnerability(self.event.version))
# Removed due to incomplete implementation for multiple vendors revisions of kubernetes
@handler.subscribe(KubectlClientEvent) @handler.subscribe(KubectlClientEvent)
class KubectlCVEHunter(Hunter): class KubectlCVEHunter(Hunter):
"""Kubectl CVE Hunter """Kubectl CVE Hunter

View File

@@ -3,7 +3,7 @@ import json
import requests import requests
from kube_hunter.conf import get_config 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 import handler
from kube_hunter.core.events.types import Vulnerability, Event from kube_hunter.core.events.types import Vulnerability, Event
from kube_hunter.modules.discovery.dashboard import KubeDashboardEvent from kube_hunter.modules.discovery.dashboard import KubeDashboardEvent
@@ -19,7 +19,7 @@ class DashboardExposed(Vulnerability, Event):
self, self,
KubernetesCluster, KubernetesCluster,
"Dashboard Exposed", "Dashboard Exposed",
category=RemoteCodeExec, category=AccessK8sDashboardTechnique,
vid="KHV029", vid="KHV029",
) )
self.evidence = "nodes: {}".format(" ".join(nodes)) if nodes else None self.evidence = "nodes: {}".format(" ".join(nodes)) if nodes else None

View File

@@ -1,90 +0,0 @@
import re
import logging
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.modules.hunting.arp import PossibleArpSpoofing
logger = logging.getLogger(__name__)
class PossibleDnsSpoofing(Vulnerability, Event):
"""A malicious pod running on the cluster could potentially run a DNS Spoof attack
and perform a MITM attack on applications running in the cluster."""
def __init__(self, kubedns_pod_ip):
Vulnerability.__init__(
self,
KubernetesCluster,
"Possible DNS Spoof",
category=IdentityTheft,
vid="KHV030",
)
self.kubedns_pod_ip = kubedns_pod_ip
self.evidence = f"kube-dns at: {self.kubedns_pod_ip}"
# Only triggered with RunningAsPod base event
@handler.subscribe(PossibleArpSpoofing)
class DnsSpoofHunter(ActiveHunter):
"""DNS Spoof Hunter
Checks for the possibility for a malicious pod to compromise DNS requests of the cluster
(results are based on the running node)
"""
def __init__(self, event):
self.event = event
def get_cbr0_ip_mac(self):
config = get_config()
res = srp1(Ether() / IP(dst="1.1.1.1", ttl=1) / ICMP(), verbose=0, timeout=config.network_timeout)
return res[IP].src, res.src
def extract_nameserver_ip(self):
with open("/etc/resolv.conf") as f:
# finds first nameserver in /etc/resolv.conf
match = re.search(r"nameserver (\d+.\d+.\d+.\d+)", f.read())
if match:
return match.group(1)
def get_kube_dns_ip_mac(self):
config = get_config()
kubedns_svc_ip = self.extract_nameserver_ip()
# getting actual pod ip of kube-dns service, by comparing the src mac of a dns response and arp scanning.
dns_info_res = srp1(
Ether() / IP(dst=kubedns_svc_ip) / UDP(dport=53) / DNS(rd=1, qd=DNSQR()),
verbose=0,
timeout=config.network_timeout,
)
kubedns_pod_mac = dns_info_res.src
self_ip = dns_info_res[IP].dst
arp_responses, _ = srp(
Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(op=1, pdst=f"{self_ip}/24"),
timeout=config.network_timeout,
verbose=0,
)
for _, response in arp_responses:
if response[Ether].src == kubedns_pod_mac:
return response[ARP].psrc, response.src
def execute(self):
config = get_config()
logger.debug("Attempting to get kube-dns pod ip")
self_ip = sr1(IP(dst="1.1.1.1", ttl=1) / ICMP(), verbose=0, timeout=config.network_timeout)[IP].dst
cbr0_ip, cbr0_mac = self.get_cbr0_ip_mac()
kubedns = self.get_kube_dns_ip_mac()
if kubedns:
kubedns_ip, kubedns_mac = kubedns
logger.debug(f"ip={self_ip} kubednsip={kubedns_ip} cbr0ip={cbr0_ip}")
if kubedns_mac != cbr0_mac:
# if self pod in the same subnet as kube-dns pod
self.publish_event(PossibleDnsSpoofing(kubedns_pod_ip=kubedns_ip))
else:
logger.debug("Could not get kubedns identity")

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ from kube_hunter.core.types import (
ActiveHunter, ActiveHunter,
Hunter, Hunter,
KubernetesCluster, KubernetesCluster,
InformationDisclosure, ConnectFromProxyServerTechnique,
) )
from kube_hunter.modules.discovery.dashboard import KubeDashboardEvent from kube_hunter.modules.discovery.dashboard import KubeDashboardEvent
from kube_hunter.modules.discovery.proxy import KubeProxyEvent from kube_hunter.modules.discovery.proxy import KubeProxyEvent
@@ -26,7 +26,7 @@ class KubeProxyExposed(Vulnerability, Event):
self, self,
KubernetesCluster, KubernetesCluster,
"Proxy Exposed", "Proxy Exposed",
category=InformationDisclosure, category=ConnectFromProxyServerTechnique,
vid="KHV049", vid="KHV049",
) )
@@ -123,5 +123,6 @@ class K8sVersionDisclosureProve(ActiveHunter):
version=version_metadata["gitVersion"], version=version_metadata["gitVersion"],
from_endpoint="/version", from_endpoint="/version",
extra_info="on kube-proxy", 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 import handler
from kube_hunter.core.events.types import Vulnerability, Event 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 from kube_hunter.modules.discovery.hosts import RunningAsPodEvent
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -17,7 +17,7 @@ class ServiceAccountTokenAccess(Vulnerability, Event):
self, self,
KubernetesCluster, KubernetesCluster,
name="Read access to pod's service account token", name="Read access to pod's service account token",
category=AccessRisk, category=AccessContainerServiceAccountTechnique,
vid="KHV050", vid="KHV050",
) )
self.evidence = evidence self.evidence = evidence
@@ -31,7 +31,7 @@ class SecretsAccess(Vulnerability, Event):
self, self,
component=KubernetesCluster, component=KubernetesCluster,
name="Access to pod's secrets", name="Access to pod's secrets",
category=AccessRisk, category=AccessContainerServiceAccountTechnique,
) )
self.evidence = evidence self.evidence = evidence

View File

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

View File

@@ -12,10 +12,7 @@ class HTTPDispatcher:
dispatch_url = os.environ.get("KUBEHUNTER_HTTP_DISPATCH_URL", "https://localhost/") dispatch_url = os.environ.get("KUBEHUNTER_HTTP_DISPATCH_URL", "https://localhost/")
try: try:
r = requests.request( r = requests.request(
dispatch_method, dispatch_method, dispatch_url, json=report, headers={"Content-Type": "application/json"}, verify=False
dispatch_url,
json=report,
headers={"Content-Type": "application/json"},
) )
r.raise_for_status() r.raise_for_status()
logger.info(f"Report was dispatched to: {dispatch_url}") logger.info(f"Report was dispatched to: {dispatch_url}")

View File

@@ -83,7 +83,7 @@ class PlainReporter(BaseReporter):
column_names = [ column_names = [
"ID", "ID",
"Location", "Location",
"Category", "MITRE Category",
"Vulnerability", "Vulnerability",
"Description", "Description",
"Evidence", "Evidence",
@@ -91,7 +91,7 @@ class PlainReporter(BaseReporter):
vuln_table = PrettyTable(column_names, hrules=ALL) vuln_table = PrettyTable(column_names, hrules=ALL)
vuln_table.align = "l" vuln_table.align = "l"
vuln_table.max_width = MAX_TABLE_WIDTH vuln_table.max_width = MAX_TABLE_WIDTH
vuln_table.sortby = "Category" vuln_table.sortby = "MITRE Category"
vuln_table.reversesort = True vuln_table.reversesort = True
vuln_table.padding_width = 1 vuln_table.padding_width = 1
vuln_table.header_style = "upper" vuln_table.header_style = "upper"
@@ -101,10 +101,11 @@ class PlainReporter(BaseReporter):
evidence = str(vuln.evidence) evidence = str(vuln.evidence)
if len(evidence) > EVIDENCE_PREVIEW: if len(evidence) > EVIDENCE_PREVIEW:
evidence = evidence[:EVIDENCE_PREVIEW] + "..." evidence = evidence[:EVIDENCE_PREVIEW] + "..."
row = [ row = [
vuln.get_vid(), vuln.get_vid(),
vuln.location(), vuln.location(),
vuln.category.name, vuln.category.get_name(),
vuln.get_name(), vuln.get_name(),
vuln.explain(), vuln.explain(),
evidence, evidence,

View File

@@ -1,5 +1,3 @@
-r requirements.txt
flake8 flake8
pytest >= 2.9.1 pytest >= 2.9.1
requests-mock >= 1.8 requests-mock >= 1.8

View File

@@ -32,7 +32,6 @@ packages = find:
install_requires = install_requires =
netaddr netaddr
netifaces netifaces
scapy>=2.4.3
requests requests
PrettyTable PrettyTable
urllib3>=1.24.3 urllib3>=1.24.3

View File

@@ -1,6 +1,6 @@
# flake8: noqa: E402 # flake8: noqa: E402
from kube_hunter.conf import Config, set_config from kube_hunter.conf import Config, set_config, get_config
set_config(Config(active=True)) set_config(Config(active=True))
@@ -20,12 +20,12 @@ from kube_hunter.modules.hunting.apiserver import (
AccessApiServerActive, AccessApiServerActive,
AccessApiServerWithToken, AccessApiServerWithToken,
) )
from kube_hunter.modules.hunting.arp import ArpSpoofHunter
from kube_hunter.modules.hunting.capabilities import PodCapabilitiesHunter from kube_hunter.modules.hunting.capabilities import PodCapabilitiesHunter
from kube_hunter.modules.hunting.certificates import CertificateDiscovery from kube_hunter.modules.hunting.certificates import CertificateDiscovery
from kube_hunter.modules.hunting.cves import K8sClusterCveHunter, KubectlCVEHunter
from kube_hunter.modules.hunting.cves import K8sClusterCveHunter
from kube_hunter.modules.hunting.cves import KubectlCVEHunter
from kube_hunter.modules.hunting.dashboard import KubeDashboard from kube_hunter.modules.hunting.dashboard import KubeDashboard
from kube_hunter.modules.hunting.dns import DnsSpoofHunter
from kube_hunter.modules.hunting.etcd import EtcdRemoteAccess, EtcdRemoteAccessActive from kube_hunter.modules.hunting.etcd import EtcdRemoteAccess, EtcdRemoteAccessActive
from kube_hunter.modules.hunting.kubelet import ( from kube_hunter.modules.hunting.kubelet import (
ProveAnonymousAuth, ProveAnonymousAuth,
@@ -40,6 +40,8 @@ from kube_hunter.modules.hunting.mounts import VarLogMountHunter, ProveVarLogMou
from kube_hunter.modules.hunting.proxy import KubeProxy, ProveProxyExposed, K8sVersionDisclosureProve from kube_hunter.modules.hunting.proxy import KubeProxy, ProveProxyExposed, K8sVersionDisclosureProve
from kube_hunter.modules.hunting.secrets import AccessSecrets from kube_hunter.modules.hunting.secrets import AccessSecrets
config = get_config()
PASSIVE_HUNTERS = { PASSIVE_HUNTERS = {
ApiServiceDiscovery, ApiServiceDiscovery,
KubeDashboardDiscovery, KubeDashboardDiscovery,
@@ -56,7 +58,6 @@ PASSIVE_HUNTERS = {
ApiVersionHunter, ApiVersionHunter,
PodCapabilitiesHunter, PodCapabilitiesHunter,
CertificateDiscovery, CertificateDiscovery,
K8sClusterCveHunter,
KubectlCVEHunter, KubectlCVEHunter,
KubeDashboard, KubeDashboard,
EtcdRemoteAccess, EtcdRemoteAccess,
@@ -67,11 +68,12 @@ PASSIVE_HUNTERS = {
AccessSecrets, AccessSecrets,
} }
# if config.enable_cve_hunting:
# PASSIVE_HUNTERS.append(K8sClusterCveHunter)
ACTIVE_HUNTERS = { ACTIVE_HUNTERS = {
ProveAzureSpnExposure, ProveAzureSpnExposure,
AccessApiServerActive, AccessApiServerActive,
ArpSpoofHunter,
DnsSpoofHunter,
EtcdRemoteAccessActive, EtcdRemoteAccessActive,
ProveRunHandler, ProveRunHandler,
ProveContainerLogsHandler, ProveContainerLogsHandler,

View File

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