Compare commits

...

30 Commits

Author SHA1 Message Date
Kiran Bodipi
bc47f08e88 fix: resolved severity discrepancy between kube-hunter report and docs for khv043 (#551) 2024-03-19 14:30:55 +02:00
Kiran Bodipi
3e1347290b fix: resolved severity discrepancy between kube-hunter report and docs (#550) 2024-03-11 14:22:47 +02:00
Andreas Lindhé
7479aae9ba Fix broken link to Trivy (#546)
Fixes #545
2023-11-15 15:30:45 +02:00
Itay Shakury
e8827b24f6 add maintenance notice (#544) 2023-11-11 01:05:12 +02:00
Itay Shakury
ff9f2c536f update logo (#520) 2022-09-04 09:39:33 +03:00
danielsagi
eb31026d8e Removing netifaces due to lack of maintainer (#519)
* removed dependency on netifaces entirely by using psutil and manually parsing /proc/net/route to figure out default gateway

* Checking if /proc/net/route is accessible. before commiting to parse it

* changed to using pyroute2 instead of manually parsing /proc/net/route and psutil for interface enum

* added pyroute2 as a dependency

* fixed bug in subnets appending

* added windows support using a powershell snippet for interface enum
2022-08-25 21:31:02 +03:00
danielsagi
a578726495 update manifest to 0.6.8 (#509) 2022-05-13 12:49:12 +03:00
rhtenhove
c442172715 pin image version (#504)
* pin image version to job

* change docker tag format

* update semver GA
2022-05-13 00:27:39 +03:00
danielsagi
d7df38fc95 Fix: Removed automatic import of handler object (#506)
* removed automatic import of handler object in events package and renamed handler.py to event_handler.py to solve name collision
2022-05-12 22:12:31 +03:00
danielsagi
9ce385a190 ignore E402 flake8 on test_cloud 2022-05-07 10:22:17 +03:00
danielsagi
ebd8e2e405 Moved config initialize to start of test_cloud.py to solve bug in testing 2022-05-07 10:22:17 +03:00
danielsagi
585b490f19 Changed help message of --num-worker-threads flag 2022-05-07 10:22:17 +03:00
Florian Bachmann
6c4ad4f6fd Solves: Make thread count configurable #433 2022-05-07 09:29:00 +03:00
danielsagi
e6a3c12098 Remove scapy usage (#500)
* removed arp and dns hunters usage due to it's violations of the scapy GPL2 license

* added installation of arp and dns hunters to Dockerfile

* added explicit new version to plugins in dockerfile installation

* ignore B020 flake8
2022-05-07 09:09:09 +03:00
danielsagi
2a7020682e Update image tag of aqua version 2022-03-28 17:33:22 +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
danielsagi
a7d26452fb Feature: New Service Account Token Flag (#463)
* added service account token flag to use in hunting

* added flag to main parsing config creation

* fixed linting issues

* added documentation on the service-account-token flag

* minor readme change
2021-06-24 20:58:43 +03:00
danielsagi
e63efddf9f Support multiple subscription on ProveVarLogMount active hunter (#461)
* removed redundant call for /pods again from /var/log mount hunter, by using multiple subscription

* fixed new linting

* fixed linting with exceptions
2021-06-24 18:43:14 +03:00
101 changed files with 874 additions and 546 deletions

View File

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

View File

@@ -39,7 +39,7 @@ jobs:
password: ${{ secrets.ECR_SECRET_ACCESS_KEY }}
- name: Get version
id: get_version
uses: crazy-max/ghaction-docker-meta@v1
uses: crazy-max/ghaction-docker-meta@v3
with:
images: ${{ env.REP }}
tag-semver: |
@@ -77,9 +77,10 @@ jobs:
python-version: '3.9'
- name: Install dependencies
shell: bash
run: |
python -m pip install -U pip
python -m pip install -r requirements-dev.txt
pip install -U pip
make deps
- name: Build project
shell: bash

View File

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

View File

@@ -13,7 +13,7 @@ jobs:
fail-fast: false
matrix:
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:
- uses: actions/checkout@v2
@@ -38,11 +38,11 @@ jobs:
${{ matrix.os }}-${{ matrix.python-version }}-
- name: Install dependencies
shell: bash
run: |
python -m pip install -U pip
python -m pip install -U wheel
python -m pip install -r requirements.txt
python -m pip install -r requirements-dev.txt
pip install -U pip
make dev-deps
make install
- name: Test
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/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"]

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
test:
pytest
python -m pytest
.PHONY: build
build:

139
README.md
View File

@@ -1,50 +1,56 @@
![kube-hunter](https://github.com/aquasecurity/kube-hunter/blob/main/kube-hunter.png)
## Notice
kube-hunter is not under active development anymore. If you're interested in scanning Kubernetes clusters for known vulnerabilities, we recommend using [Trivy](https://github.com/aquasecurity/trivy). Specifically, Trivy's Kubernetes [misconfiguration scanning](https://blog.aquasec.com/trivy-kubernetes-cis-benchmark-scanning) and [KBOM vulnerability scanning](https://blog.aquasec.com/scanning-kbom-for-vulnerabilities-with-trivy). Learn more in the [Trivy Docs](https://aquasecurity.github.io/trivy/).
[![GitHub Release][release-img]][release]
![Downloads][download]
![Docker Pulls][docker-pull]
[![Build Status](https://github.com/aquasecurity/kube-hunter/workflows/Test/badge.svg)](https://github.com/aquasecurity/kube-hunter/actions)
[![codecov](https://codecov.io/gh/aquasecurity/kube-hunter/branch/main/graph/badge.svg)](https://codecov.io/gh/aquasecurity/kube-hunter)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![License](https://img.shields.io/github/license/aquasecurity/kube-hunter)](https://github.com/aquasecurity/kube-hunter/blob/main/LICENSE)
[![Docker image](https://images.microbadger.com/badges/image/aquasec/kube-hunter.svg)](https://microbadger.com/images/aquasec/kube-hunter "Get your own image badge on microbadger.com")
[download]: https://img.shields.io/github/downloads/aquasecurity/kube-hunter/total?logo=github
[release-img]: https://img.shields.io/github/release/aquasecurity/kube-hunter.svg?logo=github
[release]: https://github.com/aquasecurity/kube-hunter/releases
[docker-pull]: https://img.shields.io/docker/pulls/aquasec/kube-hunter?logo=docker&label=docker%20pulls%20%2F%20kube-hunter
---
kube-hunter hunts for security weaknesses in Kubernetes clusters. The tool was developed to increase awareness and visibility for security issues in Kubernetes environments. **You should NOT run kube-hunter on a Kubernetes cluster that you don't own!**
**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).
[kube-hunter demo video](https://youtu.be/s2-6rTkH8a8?t=57s)
[![kube-hunter demo video](https://github.com/aquasecurity/kube-hunter/blob/main/kube-hunter-screenshot.png)](https://youtu.be/s2-6rTkH8a8?t=57s)
## Table of Contents
Table of Contents
=================
- [Table of Contents](#table-of-contents)
- [Kubernetes ATT&CK Matrix](#kubernetes-attck-matrix)
- [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)
- [Custom Hunting](#custom-hunting)
- [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)
## 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](#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
### 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:
@@ -53,7 +59,8 @@ 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).
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
@@ -82,6 +89,20 @@ 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.
@@ -121,11 +142,49 @@ Available dispatch methods are:
* KUBEHUNTER_HTTP_DISPATCH_URL (defaults to: https://localhost)
* 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.
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
There are three methods for deploying kube-hunter:
@@ -171,7 +230,7 @@ python3 kube_hunter
_If you want to use pyinstaller/py2exe you need to first run the install_imports.py script._
### Container
Aqua Security maintains a containerized version of kube-hunter at `aquasec/kube-hunter`. This container includes this source code, plus an additional (closed source) reporting plugin for uploading results into a report that can be viewed at [kube-hunter.aquasec.com](https://kube-hunter.aquasec.com). Please note, that running the `aquasec/kube-hunter` container and uploading reports data are subject to additional [terms and conditions](https://kube-hunter.aquasec.com/eula.html).
Aqua Security maintains a containerized version of kube-hunter at `aquasec/kube-hunter:aqua`. This container includes this source code, plus an additional (closed source) reporting plugin for uploading results into a report that can be viewed at [kube-hunter.aquasec.com](https://kube-hunter.aquasec.com). Please note, that running the `aquasec/kube-hunter` container and uploading reports data are subject to additional [terms and conditions](https://kube-hunter.aquasec.com/eula.html).
The Dockerfile in this repository allows you to build a containerized version without the reporting plugin.

View File

@@ -197,9 +197,9 @@ GEM
html-pipeline (~> 2.2)
jekyll (>= 3.0, < 5.0)
kramdown (2.3.0)
rexml
rexml (>= 3.2.5)
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
kramdown (>= 2.3.1)
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.1)
nokogiri (>= 1.11.4)
mini_portile2 (~> 2.5.0)
racc (~> 1.4)
octokit (4.20.0)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,11 +5,13 @@ metadata:
name: kube-hunter
spec:
template:
metadata:
labels:
app: kube-hunter
spec:
containers:
- name: kube-hunter
image: aquasec/kube-hunter
image: aquasec/kube-hunter:0.6.8
command: ["kube-hunter"]
args: ["--pod"]
restartPolicy: Never
backoffLimit: 4

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -76,7 +76,7 @@ in order to prevent circular dependency bug.
Following the above example, let's figure out the imports:
```python
from kube_hunter.core.types import Hunter
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import OpenPortEvent
@@ -206,7 +206,7 @@ __Make sure to return the event from the execute method, or the event will not g
For example, if you don't want to hunt services found on a localhost IP, you can create the following module, in the `kube_hunter/modules/report/`
```python
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import Service, EventFilterBase
@handler.subscribe(Service)
@@ -222,7 +222,7 @@ That means other Hunters that are subscribed to this Service will not get trigge
That opens up a wide variety of possible operations, as this not only can __filter out__ events, but you can actually __change event attributes__, for example:
```python
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.types import InformationDisclosure
from kube_hunter.core.events.types import Vulnerability, EventFilterBase

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3
# flake8: noqa: E402
from functools import partial
import logging
import threading
@@ -21,12 +22,16 @@ config = Config(
log_file=args.log_file,
mapping=args.mapping,
network_timeout=args.network_timeout,
num_worker_threads=args.num_worker_threads,
pod=args.pod,
quick=args.quick,
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,
enable_cve_hunting=args.enable_cve_hunting,
custom=args.custom,
)
setup_logger(args.log, args.log_file)
set_config(config)
@@ -34,7 +39,7 @@ set_config(config)
# Running all other registered plugins before execution
pm.hook.load_plugin(args=args)
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import HuntFinished, HuntStarted
from kube_hunter.modules.discovery.hosts import RunningAsPodEvent, HostScanEvent
from kube_hunter.modules.report import get_reporter, get_dispatcher
@@ -71,16 +76,20 @@ def interactive_set_config():
return True
def list_hunters():
def list_hunters(class_names=False):
print("\nPassive Hunters:\n----------------")
for hunter, docs in handler.passive_hunters.items():
name, doc = hunter.parse_docs(docs)
if class_names:
name = hunter.__name__
print(f"* {name}\n {doc}\n")
if config.active:
print("\n\nActive Hunters:\n---------------")
for hunter, docs in handler.active_hunters.items():
name, doc = hunter.parse_docs(docs)
if class_names:
name = hunter.__name__
print(f"* {name}\n {doc}\n")
@@ -93,7 +102,10 @@ def main():
scan_options = [config.pod, config.cidr, config.remote, config.interface, config.k8s_auto_discover_nodes]
try:
if args.list:
list_hunters()
if args.raw_hunter_names:
list_hunters(class_names=True)
else:
list_hunters()
return
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
def get_default_core_hunters():
return ["FromPodHostDiscovery", "HostDiscovery", "PortDiscovery", "SendFullReport", "Collector", "StartedInfo"]
@dataclass
class Config:
"""Config is a configuration container.
@@ -16,11 +20,13 @@ class Config:
- log_file: Log File path
- mapping: Report only found components
- network_timeout: Timeout for network operations
- num_worker_threads: Add a flag --threads to change the default 800 thread count of the event handler
- pod: From pod scanning mode
- quick: Quick scanning mode
- remote: Hosts to scan
- report: Output format
- statistics: Include hunters statistics
- enable_cve_hunting: enables cve hunting, shows cve results
"""
active: bool = False
@@ -31,13 +37,19 @@ class Config:
log_file: Optional[str] = None
mapping: bool = False
network_timeout: float = 5.0
num_worker_threads: int = 800
pod: bool = False
quick: bool = False
remote: Optional[str] = None
reporter: Optional[Any] = None
statistics: bool = False
k8s_auto_discover_nodes: bool = False
service_account_token: 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

View File

@@ -4,10 +4,6 @@ DEFAULT_LEVEL = logging.INFO
DEFAULT_LEVEL_NAME = logging.getLevelName(DEFAULT_LEVEL)
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):
# Remove any existing handlers

View File

@@ -46,6 +46,22 @@ def parser_add_arguments(parser):
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(
"--k8s-auto-discover-nodes",
action="store_true",
@@ -54,7 +70,15 @@ def parser_add_arguments(parser):
"It supports both in-cluster config (when running as a pod), "
"and a specific kubectl config file (use --kubeconfig to set this). "
"By default, when this flag is set, it will use in-cluster config. "
"NOTE: this is automatically switched on in --pod mode."
"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(
@@ -63,11 +87,17 @@ def parser_add_arguments(parser):
metavar="KUBECONFIG",
default=None,
help="Specify the kubeconfig file to use for Kubernetes nodes auto discovery "
" (to be used in conjuction with the --k8s-auto-discover-nodes flag."
" (to be used in conjuction with the --k8s-auto-discover-nodes flag.",
)
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(
"--log",
type=str,
@@ -103,6 +133,14 @@ def parser_add_arguments(parser):
parser.add_argument("--network-timeout", type=float, default=5.0, help="network operations timeout")
parser.add_argument(
"--num-worker-threads",
type=int,
default=800,
help="In some environments the default thread count (800) can cause the process to crash. "
"In the case of a crash try lowering the thread count",
)
def parse_args(add_args_hook):
"""

View File

@@ -1,3 +1,2 @@
# flake8: noqa: E402
from .handler import EventQueue, handler
from . import types

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
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):
self.subscribe_event(event, hook=hook, predicate=predicate)
self.subscribe_event(event, hook=hook, predicate=predicate, is_register=is_register)
return hook
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,
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):
self.subscribe_events(events, hook=hook, predicates=predicates)
self.subscribe_events(events, hook=hook, predicates=predicates, is_register=is_register)
return hook
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,
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
self.subscribe_event(event, hook=hook, predicate=predicate)
self.subscribe_event(event, hook=hook, predicate=predicate, is_register=is_register)
return hook
return wrapper
@@ -256,7 +257,33 @@ class EventQueue(Queue):
self.hooks[event].append((hook, predicate))
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):
return
@@ -267,9 +294,13 @@ class EventQueue(Queue):
else:
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):
return False
return
if predicates is None:
predicates = [None] * len(events)
@@ -335,4 +366,5 @@ class EventQueue(Queue):
self.queue.clear()
handler = EventQueue(800)
config = get_config()
handler = EventQueue(config.num_worker_threads)

View File

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

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.event_handler 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

@@ -2,7 +2,7 @@ import logging
import requests
from kube_hunter.core.types import Discovery
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import OpenPortEvent, Service, Event, EventFilterBase
from kube_hunter.conf import get_config

View File

@@ -3,7 +3,7 @@ import logging
import requests
from kube_hunter.conf import get_config
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import Event, OpenPortEvent, Service
from kube_hunter.core.types import Discovery

View File

@@ -1,4 +1,4 @@
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import Event, OpenPortEvent, Service
from kube_hunter.core.types import Discovery

View File

@@ -1,17 +1,19 @@
import json
import os
import sys
import socket
import logging
import itertools
import requests
from enum import Enum
from netaddr import IPNetwork, IPAddress, AddrFormatError
from netifaces import AF_INET, ifaddresses, interfaces, gateways
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.event_handler import handler
from kube_hunter.core.events.types import Event, NewHostEvent, Vulnerability
from kube_hunter.core.types import Discovery, InformationDisclosure, AWS, Azure
from kube_hunter.core.types import Discovery, AWS, Azure, InstanceMetadataApiTechnique
logger = logging.getLogger(__name__)
@@ -19,11 +21,17 @@ 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"
@@ -49,7 +57,7 @@ class AWSMetadataApi(Vulnerability, Event):
self,
AWS,
"AWS Metadata Exposure",
category=InformationDisclosure,
category=InstanceMetadataApiTechnique,
vid="KHV053",
)
self.cidr = cidr
@@ -64,7 +72,7 @@ class AzureMetadataApi(Vulnerability, Event):
self,
Azure,
"Azure Metadata Exposure",
category=InformationDisclosure,
category=InstanceMetadataApiTechnique,
vid="KHV003",
)
self.cidr = cidr
@@ -123,15 +131,17 @@ class FromPodHostDiscovery(Discovery):
self.publish_event(HostScanEvent())
else:
# Discover cluster subnets, we'll scan all these hosts
cloud = None
cloud, subnets = None, list()
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()
else:
subnets = self.gateway_discovery()
gateway_subnet = self.gateway_discovery()
if gateway_subnet:
subnets.append(gateway_subnet)
should_scan_apiserver = False
if self.event.kubeservicehost:
@@ -160,7 +170,9 @@ class FromPodHostDiscovery(Discovery):
return True
except requests.exceptions.ConnectionError:
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):
config = get_config()
@@ -183,7 +195,9 @@ class FromPodHostDiscovery(Discovery):
return True
except requests.exceptions.ConnectionError:
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):
config = get_config()
@@ -200,12 +214,33 @@ class FromPodHostDiscovery(Discovery):
return True
except requests.exceptions.ConnectionError:
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
def gateway_discovery(self):
"""Retrieving default gateway of pod, which is usually also a contact point with the host"""
return [[gateways()["default"][AF_INET][0], "24"]]
# read the default gateway directly from /proc
# netifaces currently does not have a maintainer. so we backported to linux support only for this cause.
# TODO: implement WMI queries for windows support
# https://stackoverflow.com/a/6556951
if sys.platform in ["linux", "linux2"]:
try:
from pyroute2 import IPDB
ip = IPDB()
gateway_ip = ip.routes["default"]["gateway"]
ip.release()
return [gateway_ip, "24"]
except Exception as x:
logging.debug(f"Exception while fetching default gateway from container - {x}")
finally:
ip.release()
else:
logging.debug("Not running in a linux env, will not scan default subnet")
return False
# querying AWS's interface metadata api v1 | works only from a pod
def aws_metadata_v1_discovery(self):
@@ -215,19 +250,27 @@ 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.split("/")
).text
logger.debug(f"Trying to extract cidr from aws's metadata v1: {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}")
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}")
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
def aws_metadata_v2_discovery(self):
@@ -249,14 +292,19 @@ class FromPodHostDiscovery(Discovery):
timeout=config.network_timeout,
).text.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}")
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}")
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
def azure_metadata_discovery(self):
@@ -313,13 +361,62 @@ class HostDiscovery(Discovery):
# generate all subnets from all internal network interfaces
def generate_interfaces_subnet(self, sn="24"):
for ifaceName in interfaces():
for ip in [i["addr"] for i in ifaddresses(ifaceName).setdefault(AF_INET, [])]:
if not self.event.localhost and InterfaceTypes.LOCALHOST.value in ip.__str__():
if sys.platform == "win32":
return self.generate_interfaces_subnet_windows()
elif sys.platform in ["linux", "linux2"]:
return self.generate_interfaces_subnet_linux()
def generate_interfaces_subnet_linux(self, sn="24"):
try:
from pyroute2 import IPRoute
ip = IPRoute()
for i in ip.get_addr():
# whitelist only ipv4 ips
if i["family"] == socket.AF_INET:
ipaddress = i[0].get_attr("IFA_ADDRESS")
# TODO: add this instead of hardcoded 24 subnet, (add a flag for full scan option)
# subnet = i['prefixlen']
# unless specified explicitly with localhost scan flag, skip localhost ip addresses
if not self.event.localhost and ipaddress.startswith(InterfaceTypes.LOCALHOST.value):
continue
ip_network = IPNetwork(f"{ipaddress}/{sn}")
for ip in ip_network:
yield ip
except Exception as x:
logging.debug(f"Exception while generating subnet scan from local interfaces: {x}")
finally:
ip.release()
def generate_interfaces_subnet_windows(self, sn="24"):
from subprocess import check_output
local_subnets = (
check_output(
"powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy bypass -Command "
' "& {'
"Get-NetIPConfiguration | Get-NetIPAddress | Where-Object {$_.AddressFamily -eq 'IPv4'}"
" | Select-Object -Property IPAddress, PrefixLength | ConvertTo-Json "
' "}',
shell=True,
)
.decode()
.strip()
)
try:
subnets = json.loads(local_subnets)
for subnet in subnets:
if not self.event.localhost and subnet["IPAddress"].startswith(InterfaceTypes.LOCALHOST.value):
continue
for ip in IPNetwork(f"{ip}/{sn}"):
ip_network = IPNetwork(f"{subnet['IPAddress']}/{sn}")
for ip in ip_network:
yield ip
except Exception as x:
logging.debug(f"ERROR: Could not extract interface information using powershell - {x}")
# for comparing prefixes
class InterfaceTypes(Enum):

View File

@@ -2,7 +2,7 @@ import logging
import subprocess
from kube_hunter.core.types import Discovery
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import HuntStarted, Event
logger = logging.getLogger(__name__)

View File

@@ -5,7 +5,7 @@ from enum import Enum
from kube_hunter.conf import get_config
from kube_hunter.core.types import Discovery
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import OpenPortEvent, Event, Service
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

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.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)
else:
logger.info("Attempting to use in cluster Kubernetes config")
logger.debug("Attempting to use in cluster Kubernetes config")
kubernetes.config.load_incluster_config()
except kubernetes.config.config_exception.ConfigException:
logger.exception("Failed to initiate Kubernetes client")
except kubernetes.config.config_exception.ConfigException as ex:
logger.debug(f"Failed to initiate Kubernetes client: {ex}")
return
try:
@@ -23,5 +23,5 @@ def list_all_k8s_cluster_nodes(kube_config=None, client=None):
for item in ret.items:
for addr in item.status.addresses:
yield addr.address
except:
logger.exception("Failed to list nodes from Kubernetes")
except Exception as ex:
logger.debug(f"Failed to list nodes from Kubernetes: {ex}")

View File

@@ -2,7 +2,7 @@ import logging
from socket import socket
from kube_hunter.core.types import Discovery
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import NewHostEvent, OpenPortEvent
logger = logging.getLogger(__name__)

View File

@@ -3,7 +3,7 @@ import requests
from kube_hunter.conf import get_config
from kube_hunter.core.types import Discovery
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import Service, Event, OpenPortEvent
logger = logging.getLogger(__name__)

View File

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

View File

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

View File

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

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

@@ -2,9 +2,9 @@ import socket
import logging
from kube_hunter.modules.discovery.hosts import RunningAsPodEvent
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import Event, Vulnerability
from kube_hunter.core.types import Hunter, AccessRisk, KubernetesCluster
from kube_hunter.core.types import Hunter, ARPPoisoningTechnique, KubernetesCluster
logger = logging.getLogger(__name__)
@@ -20,7 +20,7 @@ class CapNetRawEnabled(Event, Vulnerability):
self,
KubernetesCluster,
name="CAP_NET_RAW Enabled",
category=AccessRisk,
category=ARPPoisoningTechnique,
)

View File

@@ -3,8 +3,8 @@ import logging
import base64
import re
from kube_hunter.core.types import Hunter, KubernetesCluster, InformationDisclosure
from kube_hunter.core.events import handler
from kube_hunter.core.types import Hunter, KubernetesCluster, GeneralSensitiveInformationTechnique
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import Vulnerability, Event, Service
logger = logging.getLogger(__name__)
@@ -21,7 +21,7 @@ class CertificateEmail(Vulnerability, Event):
self,
KubernetesCluster,
"Certificate Includes Email Address",
category=InformationDisclosure,
category=GeneralSensitiveInformationTechnique,
vid="KHV021",
)
self.email = email

View File

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

View File

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

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

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

View File

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

View File

@@ -3,14 +3,9 @@ import re
import uuid
from kube_hunter.conf import get_config
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import Event, Vulnerability
from kube_hunter.core.types import (
ActiveHunter,
Hunter,
KubernetesCluster,
PrivilegeEscalation,
)
from kube_hunter.core.types import ActiveHunter, Hunter, KubernetesCluster, HostPathMountPrivilegeEscalationTechnique
from kube_hunter.modules.hunting.kubelet import (
ExposedPodsHandler,
ExposedRunHandler,
@@ -28,7 +23,7 @@ class WriteMountToVarLog(Vulnerability, Event):
self,
KubernetesCluster,
"Pod With Mount To /var/log",
category=PrivilegeEscalation,
category=HostPathMountPrivilegeEscalationTechnique,
vid="KHV047",
)
self.pods = pods
@@ -44,7 +39,7 @@ class DirectoryTraversalWithKubelet(Vulnerability, Event):
self,
KubernetesCluster,
"Root Traversal Read On The Kubelet",
category=PrivilegeEscalation,
category=HostPathMountPrivilegeEscalationTechnique,
)
self.output = output
self.evidence = f"output: {self.output}"
@@ -77,15 +72,17 @@ class VarLogMountHunter(Hunter):
self.publish_event(WriteMountToVarLog(pods=pe_pods))
@handler.subscribe(ExposedRunHandler)
@handler.subscribe_many([ExposedRunHandler, WriteMountToVarLog])
class ProveVarLogMount(ActiveHunter):
"""Prove /var/log Mount Hunter
Tries to read /etc/shadow on the host by running commands inside a pod with host mount to /var/log
"""
def __init__(self, event):
self.event = event
self.base_path = f"https://{self.event.host}:{self.event.port}"
self.write_mount_event = self.event.get_by_class(WriteMountToVarLog)
self.event = self.write_mount_event
self.base_path = f"https://{self.write_mount_event.host}:{self.write_mount_event.port}"
def run(self, command, container):
run_url = KubeletHandlers.RUN.value.format(
@@ -96,20 +93,6 @@ class ProveVarLogMount(ActiveHunter):
)
return self.event.session.post(f"{self.base_path}/{run_url}", verify=False).text
# TODO: replace with multiple subscription to WriteMountToVarLog as well
def get_varlog_mounters(self):
config = get_config()
logger.debug("accessing /pods manually on ProveVarLogMount")
pods = self.event.session.get(
f"{self.base_path}/" + KubeletHandlers.PODS.value,
verify=False,
timeout=config.network_timeout,
).json()["items"]
for pod in pods:
volume = VarLogMountHunter(ExposedPodsHandler(pods=pods)).has_write_mount_to(pod, "/var/log")
if volume:
yield pod, volume
def mount_path_from_mountname(self, pod, mount_name):
"""returns container name, and container mount path correlated to mount_name"""
for container in pod["spec"]["containers"]:
@@ -138,7 +121,7 @@ class ProveVarLogMount(ActiveHunter):
return content
def execute(self):
for pod, volume in self.get_varlog_mounters():
for pod, volume in self.write_mount_event.pe_pods():
for container, mount_path in self.mount_path_from_mountname(pod, volume["name"]):
logger.debug("Correlated container to mount_name")
cont = {

View File

@@ -4,13 +4,13 @@ import requests
from enum import Enum
from kube_hunter.conf import get_config
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import Event, Vulnerability, K8sVersionDisclosure
from kube_hunter.core.types import (
ActiveHunter,
Hunter,
KubernetesCluster,
InformationDisclosure,
ConnectFromProxyServerTechnique,
)
from kube_hunter.modules.discovery.dashboard import KubeDashboardEvent
from kube_hunter.modules.discovery.proxy import KubeProxyEvent
@@ -26,7 +26,7 @@ class KubeProxyExposed(Vulnerability, Event):
self,
KubernetesCluster,
"Proxy Exposed",
category=InformationDisclosure,
category=ConnectFromProxyServerTechnique,
vid="KHV049",
)
@@ -123,5 +123,6 @@ class K8sVersionDisclosureProve(ActiveHunter):
version=version_metadata["gitVersion"],
from_endpoint="/version",
extra_info="on kube-proxy",
category=ConnectFromProxyServerTechnique,
)
)

View File

@@ -1,9 +1,9 @@
import logging
import os
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import Vulnerability, Event
from kube_hunter.core.types import Hunter, KubernetesCluster, AccessRisk
from kube_hunter.core.types import Hunter, KubernetesCluster, AccessContainerServiceAccountTechnique
from kube_hunter.modules.discovery.hosts import RunningAsPodEvent
logger = logging.getLogger(__name__)
@@ -17,7 +17,7 @@ class ServiceAccountTokenAccess(Vulnerability, Event):
self,
KubernetesCluster,
name="Read access to pod's service account token",
category=AccessRisk,
category=AccessContainerServiceAccountTechnique,
vid="KHV050",
)
self.evidence = evidence
@@ -31,7 +31,7 @@ class SecretsAccess(Vulnerability, Event):
self,
component=KubernetesCluster,
name="Access to pod's secrets",
category=AccessRisk,
category=AccessContainerServiceAccountTechnique,
)
self.evidence = evidence

View File

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

View File

@@ -2,7 +2,7 @@ import logging
import threading
from kube_hunter.conf import get_config
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import (
Event,
Service,

View File

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

View File

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

View File

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

View File

@@ -31,8 +31,7 @@ zip_safe = False
packages = find:
install_requires =
netaddr
netifaces
scapy>=2.4.3
pyroute2
requests
PrettyTable
urllib3>=1.24.3

View File

@@ -1,11 +1,13 @@
# flake8: noqa: E402
import requests_mock
import json
from kube_hunter.conf import Config, set_config
from kube_hunter.core.events.types import NewHostEvent
set_config(Config())
from kube_hunter.core.events.types import NewHostEvent
def test_presetcloud():
"""Testing if it doesn't try to run get_cloud if the cloud type is already set.

View File

@@ -1,10 +1,10 @@
# 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))
from kube_hunter.core.events.handler import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.modules.discovery.apiserver import ApiServiceDiscovery
from kube_hunter.modules.discovery.dashboard import KubeDashboard as KubeDashboardDiscovery
from kube_hunter.modules.discovery.etcd import EtcdRemoteAccess as EtcdRemoteAccessDiscovery
@@ -20,12 +20,12 @@ from kube_hunter.modules.hunting.apiserver import (
AccessApiServerActive,
AccessApiServerWithToken,
)
from kube_hunter.modules.hunting.arp import ArpSpoofHunter
from kube_hunter.modules.hunting.capabilities import PodCapabilitiesHunter
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.dns import DnsSpoofHunter
from kube_hunter.modules.hunting.etcd import EtcdRemoteAccess, EtcdRemoteAccessActive
from kube_hunter.modules.hunting.kubelet import (
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.secrets import AccessSecrets
config = get_config()
PASSIVE_HUNTERS = {
ApiServiceDiscovery,
KubeDashboardDiscovery,
@@ -56,7 +58,6 @@ PASSIVE_HUNTERS = {
ApiVersionHunter,
PodCapabilitiesHunter,
CertificateDiscovery,
K8sClusterCveHunter,
KubectlCVEHunter,
KubeDashboard,
EtcdRemoteAccess,
@@ -67,11 +68,12 @@ PASSIVE_HUNTERS = {
AccessSecrets,
}
# if config.enable_cve_hunting:
# PASSIVE_HUNTERS.append(K8sClusterCveHunter)
ACTIVE_HUNTERS = {
ProveAzureSpnExposure,
AccessApiServerActive,
ArpSpoofHunter,
DnsSpoofHunter,
EtcdRemoteAccessActive,
ProveRunHandler,
ProveContainerLogsHandler,

View File

@@ -3,7 +3,7 @@ import time
from kube_hunter.conf import Config, set_config
from kube_hunter.core.types import Hunter
from kube_hunter.core.events.types import Event, Service
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
counter = 0
first_run = True

View File

@@ -8,7 +8,7 @@ set_config(Config())
from kube_hunter.modules.discovery.apiserver import ApiServer, ApiServiceDiscovery
from kube_hunter.core.events.types import Event
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
counter = 0

View File

@@ -6,7 +6,7 @@ from kube_hunter.modules.discovery.hosts import (
HostDiscoveryHelpers,
)
from kube_hunter.core.types import Hunter
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
import json
import requests_mock
import pytest

View File

@@ -1,10 +1,9 @@
from kube_hunter.conf import Config, set_config
set_config(Config())
from kube_hunter.modules.discovery.kubernetes_client import list_all_k8s_cluster_nodes
from unittest.mock import MagicMock, patch
set_config(Config())
def test_client_yields_ips():
@@ -18,7 +17,7 @@ def test_client_yields_ips():
response.items[1].status.addresses = [MagicMock()]
response.items[1].status.addresses[0].address = "127.0.0.3"
with patch('kubernetes.config.load_incluster_config') as m:
with patch("kubernetes.config.load_incluster_config") as m:
output = list(list_all_k8s_cluster_nodes(client=client))
m.assert_called_once()
@@ -26,6 +25,6 @@ def test_client_yields_ips():
def test_client_uses_kubeconfig():
with patch('kubernetes.config.load_kube_config') as m:
with patch("kubernetes.config.load_kube_config") as m:
list(list_all_k8s_cluster_nodes(kube_config="/location", client=MagicMock()))
m.assert_called_once_with(config_file="/location")

View File

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

View File

@@ -5,7 +5,7 @@ set_config(Config())
from kube_hunter.core.events.types import Event
from kube_hunter.modules.hunting.certificates import CertificateDiscovery, CertificateEmail
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
def test_CertificateDiscovery():

View File

@@ -5,7 +5,7 @@ from kube_hunter.conf import Config, set_config
set_config(Config())
from kube_hunter.core.events import handler
from kube_hunter.core.events.event_handler import handler
from kube_hunter.core.events.types import K8sVersionDisclosure
from kube_hunter.modules.hunting.cves import (
K8sClusterCveHunter,

Some files were not shown because too many files have changed in this diff Show More