mirror of
https://github.com/aquasecurity/kube-hunter.git
synced 2026-04-07 02:56:59 +00:00
Compare commits
18 Commits
update-doc
...
remove_sca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b08a86b104 | ||
|
|
5bed4ca722 | ||
|
|
915d2bff8a | ||
|
|
8146810d44 | ||
|
|
c54859ec37 | ||
|
|
e1896f3983 | ||
|
|
fc7fbbf1fc | ||
|
|
7c62cc21af | ||
|
|
c17aa17096 | ||
|
|
4204879251 | ||
|
|
a746bd0eb1 | ||
|
|
b379e64314 | ||
|
|
00eb0dfa87 | ||
|
|
8d045fb1a8 | ||
|
|
83b19d4208 | ||
|
|
473e4fe2b5 | ||
|
|
f67f08225c | ||
|
|
c96312b91e |
2
.flake8
2
.flake8
@@ -1,5 +1,5 @@
|
|||||||
[flake8]
|
[flake8]
|
||||||
ignore = E203, E266, E501, W503, B903, T499
|
ignore = E203, E266, E501, W503, B903, T499, B020
|
||||||
max-line-length = 120
|
max-line-length = 120
|
||||||
max-complexity = 18
|
max-complexity = 18
|
||||||
select = B,C,E,F,W,B9,T4
|
select = B,C,E,F,W,B9,T4
|
||||||
|
|||||||
5
.github/workflows/publish.yml
vendored
5
.github/workflows/publish.yml
vendored
@@ -77,9 +77,10 @@ jobs:
|
|||||||
python-version: '3.9'
|
python-version: '3.9'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
python -m pip install -U pip
|
pip install -U pip
|
||||||
python -m pip install -r requirements-dev.txt
|
make deps
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -10,7 +10,7 @@ name: Release
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Upload Release Asset
|
name: Upload Release Asset
|
||||||
runs-on: ubuntu-16.04
|
runs-on: ubuntu-18.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@@ -18,12 +18,14 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.9'
|
python-version: '3.8'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
python -m pip install -U pip
|
pip install -U pip
|
||||||
python -m pip install -r requirements-dev.txt
|
pip install pyinstaller
|
||||||
|
make deps
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
10
.github/workflows/test.yml
vendored
10
.github/workflows/test.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.6", "3.7", "3.8", "3.9"]
|
python-version: ["3.6", "3.7", "3.8", "3.9"]
|
||||||
os: [ubuntu-20.04, ubuntu-18.04, ubuntu-16.04]
|
os: [ubuntu-20.04, ubuntu-18.04]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -38,11 +38,11 @@ jobs:
|
|||||||
${{ matrix.os }}-${{ matrix.python-version }}-
|
${{ matrix.os }}-${{ matrix.python-version }}-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
python -m pip install -U pip
|
pip install -U pip
|
||||||
python -m pip install -U wheel
|
make dev-deps
|
||||||
python -m pip install -r requirements.txt
|
make install
|
||||||
python -m pip install -r requirements-dev.txt
|
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -26,4 +26,7 @@ RUN apk add --no-cache \
|
|||||||
COPY --from=builder /usr/local/lib/python3.8/site-packages /usr/local/lib/python3.8/site-packages
|
COPY --from=builder /usr/local/lib/python3.8/site-packages /usr/local/lib/python3.8/site-packages
|
||||||
COPY --from=builder /usr/local/bin/kube-hunter /usr/local/bin/kube-hunter
|
COPY --from=builder /usr/local/bin/kube-hunter /usr/local/bin/kube-hunter
|
||||||
|
|
||||||
|
# Add default plugins: https://github.com/aquasecurity/kube-hunter-plugins
|
||||||
|
RUN pip install kube-hunter-arp-spoof>=0.0.3 kube-hunter-dns-spoof>=0.0.3
|
||||||
|
|
||||||
ENTRYPOINT ["kube-hunter"]
|
ENTRYPOINT ["kube-hunter"]
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -31,7 +31,7 @@ lint-check:
|
|||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
pytest
|
python -m pytest
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build:
|
build:
|
||||||
|
|||||||
68
README.md
68
README.md
@@ -18,7 +18,8 @@ kube-hunter hunts for security weaknesses in Kubernetes clusters. The tool was d
|
|||||||
|
|
||||||
**Run kube-hunter**: kube-hunter is available as a container (aquasec/kube-hunter), and we also offer a web site at [kube-hunter.aquasec.com](https://kube-hunter.aquasec.com) where you can register online to receive a token allowing you to see and share the results online. You can also run the Python code yourself as described below.
|
**Run kube-hunter**: kube-hunter is available as a container (aquasec/kube-hunter), and we also offer a web site at [kube-hunter.aquasec.com](https://kube-hunter.aquasec.com) where you can register online to receive a token allowing you to see and share the results online. You can also run the Python code yourself as described below.
|
||||||
|
|
||||||
**Explore vulnerabilities**: The kube-hunter knowledge base includes articles about discoverable vulnerabilities and issues. When kube-hunter reports an issue, it will show its VID (Vulnerability ID) so you can look it up in the KB at https://aquasecurity.github.io/kube-hunter/
|
**Explore vulnerabilities**: The kube-hunter knowledge base includes articles about discoverable vulnerabilities and issues. When kube-hunter reports an issue, it will show its VID (Vulnerability ID) so you can look it up in the KB at https://aquasecurity.github.io/kube-hunter/
|
||||||
|
_If you're interested in kube-hunter's integration with the Kubernetes ATT&CK Matrix [Continue Reading](#kuberentes-attck-matrix)_
|
||||||
|
|
||||||
**Contribute**: We welcome contributions, especially new hunter modules that perform additional tests. If you would like to develop your modules please read [Guidelines For Developing Your First kube-hunter Module](https://github.com/aquasecurity/kube-hunter/blob/main/CONTRIBUTING.md).
|
**Contribute**: We welcome contributions, especially new hunter modules that perform additional tests. If you would like to develop your modules please read [Guidelines For Developing Your First kube-hunter Module](https://github.com/aquasecurity/kube-hunter/blob/main/CONTRIBUTING.md).
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ Table of Contents
|
|||||||
=================
|
=================
|
||||||
|
|
||||||
- [Table of Contents](#table-of-contents)
|
- [Table of Contents](#table-of-contents)
|
||||||
|
- [Kubernetes ATT&CK Matrix](#kubernetes-attck-matrix)
|
||||||
- [Hunting](#hunting)
|
- [Hunting](#hunting)
|
||||||
- [Where should I run kube-hunter?](#where-should-i-run-kube-hunter)
|
- [Where should I run kube-hunter?](#where-should-i-run-kube-hunter)
|
||||||
- [Scanning options](#scanning-options)
|
- [Scanning options](#scanning-options)
|
||||||
@@ -37,8 +39,9 @@ Table of Contents
|
|||||||
- [Nodes Mapping](#nodes-mapping)
|
- [Nodes Mapping](#nodes-mapping)
|
||||||
- [Output](#output)
|
- [Output](#output)
|
||||||
- [Dispatching](#dispatching)
|
- [Dispatching](#dispatching)
|
||||||
- [Advanced Usage](#advanced-usage)
|
- [Advanced Usage](#advanced-usage)
|
||||||
- [Azure Quick Scanning](#azure-quick-scanning)
|
- [Azure Quick Scanning](#azure-quick-scanning)
|
||||||
|
- [Custom Hunting](#custom-hunting)
|
||||||
- [Deployment](#deployment)
|
- [Deployment](#deployment)
|
||||||
- [On Machine](#on-machine)
|
- [On Machine](#on-machine)
|
||||||
- [Prerequisites](#prerequisites)
|
- [Prerequisites](#prerequisites)
|
||||||
@@ -48,9 +51,21 @@ Table of Contents
|
|||||||
- [Pod](#pod)
|
- [Pod](#pod)
|
||||||
- [Contribution](#contribution)
|
- [Contribution](#contribution)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
## Hunting
|
|
||||||
|
|
||||||
|
---
|
||||||
|
## Kubernetes ATT&CK Matrix
|
||||||
|
|
||||||
|
kube-hunter now supports the new format of the Kubernetes ATT&CK matrix.
|
||||||
|
While kube-hunter's vulnerabilities are a collection of creative techniques designed to mimic an attacker in the cluster (or outside it)
|
||||||
|
The Mitre's ATT&CK defines a more general standardised categories of techniques to do so.
|
||||||
|
|
||||||
|
You can think of kube-hunter vulnerabilities as small steps for an attacker, which follows the track of a more general technique he would aim for.
|
||||||
|
Most of kube-hunter's hunters and vulnerabilities can closly fall under those techniques, That's why we moved to follow the Matrix standard.
|
||||||
|
|
||||||
|
_Some kube-hunter vulnerabities which we could not map to Mitre technique, are prefixed with the `General` keyword_
|
||||||
|

|
||||||
|
|
||||||
|
## Hunting
|
||||||
### Where should I run kube-hunter?
|
### Where should I run kube-hunter?
|
||||||
|
|
||||||
There are three different ways to run kube-hunter, each providing a different approach to detecting weaknesses in your cluster:
|
There are three different ways to run kube-hunter, each providing a different approach to detecting weaknesses in your cluster:
|
||||||
@@ -61,6 +76,7 @@ You can run kube-hunter directly on a machine in the cluster, and select the opt
|
|||||||
|
|
||||||
You can also run kube-hunter in a pod within the cluster. This indicates how exposed your cluster would be if one of your application pods is compromised (through a software vulnerability, for example). (_`--pod` flag_)
|
You can also run kube-hunter in a pod within the cluster. This indicates how exposed your cluster would be if one of your application pods is compromised (through a software vulnerability, for example). (_`--pod` flag_)
|
||||||
|
|
||||||
|
|
||||||
### Scanning options
|
### Scanning options
|
||||||
|
|
||||||
First check for these **[pre-requisites](#prerequisites)**.
|
First check for these **[pre-requisites](#prerequisites)**.
|
||||||
@@ -141,11 +157,49 @@ Available dispatch methods are:
|
|||||||
* KUBEHUNTER_HTTP_DISPATCH_URL (defaults to: https://localhost)
|
* KUBEHUNTER_HTTP_DISPATCH_URL (defaults to: https://localhost)
|
||||||
* KUBEHUNTER_HTTP_DISPATCH_METHOD (defaults to: POST)
|
* KUBEHUNTER_HTTP_DISPATCH_METHOD (defaults to: POST)
|
||||||
|
|
||||||
### Advanced Usage
|
|
||||||
#### Azure Quick Scanning
|
## Advanced Usage
|
||||||
|
### Azure Quick Scanning
|
||||||
When running **as a Pod in an Azure or AWS environment**, kube-hunter will fetch subnets from the Instance Metadata Service. Naturally this makes the discovery process take longer.
|
When running **as a Pod in an Azure or AWS environment**, kube-hunter will fetch subnets from the Instance Metadata Service. Naturally this makes the discovery process take longer.
|
||||||
To hardlimit subnet scanning to a `/24` CIDR, use the `--quick` option.
|
To hardlimit subnet scanning to a `/24` CIDR, use the `--quick` option.
|
||||||
|
|
||||||
|
### Custom Hunting
|
||||||
|
Custom hunting enables advanced users to have control over what hunters gets registered at the start of a hunt.
|
||||||
|
**If you know what you are doing**, this can help if you want to adjust kube-hunter's hunting and discovery process for your needs.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
kube-hunter --custom <HunterName1> <HunterName2>
|
||||||
|
```
|
||||||
|
Enabling Custom hunting removes all hunters from the hunting process, except the given whitelisted hunters.
|
||||||
|
|
||||||
|
The `--custom` flag reads a list of hunters class names, in order to view all of kube-hunter's class names, you can combine the flag `--raw-hunter-names` with the `--list` flag.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
kube-hunter --active --list --raw-hunter-names
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notice**: Due to kube-huner's architectural design, the following "Core Hunters/Classes" will always register (even when using custom hunting):
|
||||||
|
* HostDiscovery
|
||||||
|
* _Generates ip addresses for the hunt by given configurations_
|
||||||
|
* _Automatically discovers subnets using cloud Metadata APIs_
|
||||||
|
* FromPodHostDiscovery
|
||||||
|
* _Auto discover attack surface ip addresses for the hunt by using Pod based environment techniques_
|
||||||
|
* _Automatically discovers subnets using cloud Metadata APIs_
|
||||||
|
* PortDiscovery
|
||||||
|
* _Port scanning given ip addresses for known kubernetes services ports_
|
||||||
|
* Collector
|
||||||
|
* _Collects discovered vulnerabilities and open services for future report_
|
||||||
|
* StartedInfo
|
||||||
|
* _Prints the start message_
|
||||||
|
* SendFullReport
|
||||||
|
* _Dispatching the report based on given configurations_
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
There are three methods for deploying kube-hunter:
|
There are three methods for deploying kube-hunter:
|
||||||
|
|
||||||
|
|||||||
@@ -197,9 +197,9 @@ GEM
|
|||||||
html-pipeline (~> 2.2)
|
html-pipeline (~> 2.2)
|
||||||
jekyll (>= 3.0, < 5.0)
|
jekyll (>= 3.0, < 5.0)
|
||||||
kramdown (2.3.0)
|
kramdown (2.3.0)
|
||||||
rexml
|
rexml (>= 3.2.5)
|
||||||
kramdown-parser-gfm (1.1.0)
|
kramdown-parser-gfm (1.1.0)
|
||||||
kramdown (~> 2.0)
|
kramdown (>= 2.3.1)
|
||||||
liquid (4.0.3)
|
liquid (4.0.3)
|
||||||
listen (3.4.0)
|
listen (3.4.0)
|
||||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||||
@@ -212,7 +212,7 @@ GEM
|
|||||||
jekyll-seo-tag (~> 2.1)
|
jekyll-seo-tag (~> 2.1)
|
||||||
minitest (5.14.3)
|
minitest (5.14.3)
|
||||||
multipart-post (2.1.1)
|
multipart-post (2.1.1)
|
||||||
nokogiri (1.11.1)
|
nokogiri (>= 1.11.4)
|
||||||
mini_portile2 (~> 2.5.0)
|
mini_portile2 (~> 2.5.0)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
octokit (4.20.0)
|
octokit (4.20.0)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV002
|
vid: KHV002
|
||||||
title: Kubernetes version disclosure
|
title: Kubernetes version disclosure
|
||||||
categories: [Information Disclosure]
|
categories: [Information Disclosure]
|
||||||
|
severity: low
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV003
|
vid: KHV003
|
||||||
title: Azure Metadata Exposure
|
title: Azure Metadata Exposure
|
||||||
categories: [Information Disclosure]
|
categories: [Information Disclosure]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV004
|
vid: KHV004
|
||||||
title: Azure SPN Exposure
|
title: Azure SPN Exposure
|
||||||
categories: [Identity Theft]
|
categories: [Identity Theft]
|
||||||
|
severity: medium
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV005
|
vid: KHV005
|
||||||
title: Access to Kubernetes API
|
title: Access to Kubernetes API
|
||||||
categories: [Information Disclosure, Unauthenticated Access]
|
categories: [Information Disclosure, Unauthenticated Access]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV006
|
vid: KHV006
|
||||||
title: Insecure (HTTP) access to Kubernetes API
|
title: Insecure (HTTP) access to Kubernetes API
|
||||||
categories: [Unauthenticated Access]
|
categories: [Unauthenticated Access]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV007
|
vid: KHV007
|
||||||
title: Specific Access to Kubernetes API
|
title: Specific Access to Kubernetes API
|
||||||
categories: [Access Risk]
|
categories: [Access Risk]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV020
|
vid: KHV020
|
||||||
title: Possible Arp Spoof
|
title: Possible Arp Spoof
|
||||||
categories: [IdentityTheft]
|
categories: [IdentityTheft]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV021
|
vid: KHV021
|
||||||
title: Certificate Includes Email Address
|
title: Certificate Includes Email Address
|
||||||
categories: [Information Disclosure]
|
categories: [Information Disclosure]
|
||||||
|
severity: low
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV022
|
vid: KHV022
|
||||||
title: Critical Privilege Escalation CVE
|
title: Critical Privilege Escalation CVE
|
||||||
categories: [Privilege Escalation]
|
categories: [Privilege Escalation]
|
||||||
|
severity: critical
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV023
|
vid: KHV023
|
||||||
title: Denial of Service to Kubernetes API Server
|
title: Denial of Service to Kubernetes API Server
|
||||||
categories: [Denial Of Service]
|
categories: [Denial Of Service]
|
||||||
|
severity: medium
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV024
|
vid: KHV024
|
||||||
title: Possible Ping Flood Attack
|
title: Possible Ping Flood Attack
|
||||||
categories: [Denial Of Service]
|
categories: [Denial Of Service]
|
||||||
|
severity: medium
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV025
|
vid: KHV025
|
||||||
title: Possible Reset Flood Attack
|
title: Possible Reset Flood Attack
|
||||||
categories: [Denial Of Service]
|
categories: [Denial Of Service]
|
||||||
|
severity: medium
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV026
|
vid: KHV026
|
||||||
title: Arbitrary Access To Cluster Scoped Resources
|
title: Arbitrary Access To Cluster Scoped Resources
|
||||||
categories: [PrivilegeEscalation]
|
categories: [PrivilegeEscalation]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV027
|
vid: KHV027
|
||||||
title: Kubectl Vulnerable To CVE-2019-11246
|
title: Kubectl Vulnerable To CVE-2019-11246
|
||||||
categories: [Remote Code Execution]
|
categories: [Remote Code Execution]
|
||||||
|
severity: medium
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV028
|
vid: KHV028
|
||||||
title: Kubectl Vulnerable To CVE-2019-1002101
|
title: Kubectl Vulnerable To CVE-2019-1002101
|
||||||
categories: [Remote Code Execution]
|
categories: [Remote Code Execution]
|
||||||
|
severity: medium
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV029
|
vid: KHV029
|
||||||
title: Dashboard Exposed
|
title: Dashboard Exposed
|
||||||
categories: [Remote Code Execution]
|
categories: [Remote Code Execution]
|
||||||
|
severity: critical
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
@@ -12,4 +13,5 @@ An open Kubernetes Dashboard was detected. The Kubernetes Dashboard can be used
|
|||||||
|
|
||||||
## Remediation
|
## Remediation
|
||||||
|
|
||||||
Do not leave the Dashboard insecured.
|
Do not leave the Dashboard insecured.
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV030
|
vid: KHV030
|
||||||
title: Possible DNS Spoof
|
title: Possible DNS Spoof
|
||||||
categories: [Identity Theft]
|
categories: [Identity Theft]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV031
|
vid: KHV031
|
||||||
title: Etcd Remote Write Access Event
|
title: Etcd Remote Write Access Event
|
||||||
categories: [Remote Code Execution]
|
categories: [Remote Code Execution]
|
||||||
|
severity: critical
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV032
|
vid: KHV032
|
||||||
title: Etcd Remote Read Access Event
|
title: Etcd Remote Read Access Event
|
||||||
categories: [Access Risk]
|
categories: [Access Risk]
|
||||||
|
severity: critical
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV033
|
vid: KHV033
|
||||||
title: Etcd Remote version disclosure
|
title: Etcd Remote version disclosure
|
||||||
categories: [Information Disclosure]
|
categories: [Information Disclosure]
|
||||||
|
severity: medium
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV034
|
vid: KHV034
|
||||||
title: Etcd is accessible using insecure connection (HTTP)
|
title: Etcd is accessible using insecure connection (HTTP)
|
||||||
categories: [Unauthenticated Access]
|
categories: [Unauthenticated Access]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV036
|
vid: KHV036
|
||||||
title: Anonymous Authentication
|
title: Anonymous Authentication
|
||||||
categories: [Remote Code Execution]
|
categories: [Remote Code Execution]
|
||||||
|
severity: critical
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV037
|
vid: KHV037
|
||||||
title: Exposed Container Logs
|
title: Exposed Container Logs
|
||||||
categories: [Information Disclosure]
|
categories: [Information Disclosure]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV038
|
vid: KHV038
|
||||||
title: Exposed Running Pods
|
title: Exposed Running Pods
|
||||||
categories: [Information Disclosure]
|
categories: [Information Disclosure]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV039
|
vid: KHV039
|
||||||
title: Exposed Exec On Container
|
title: Exposed Exec On Container
|
||||||
categories: [Remote Code Execution]
|
categories: [Remote Code Execution]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV040
|
vid: KHV040
|
||||||
title: Exposed Run Inside Container
|
title: Exposed Run Inside Container
|
||||||
categories: [Remote Code Execution]
|
categories: [Remote Code Execution]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV041
|
vid: KHV041
|
||||||
title: Exposed Port Forward
|
title: Exposed Port Forward
|
||||||
categories: [Remote Code Execution]
|
categories: [Remote Code Execution]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV042
|
vid: KHV042
|
||||||
title: Exposed Attaching To Container
|
title: Exposed Attaching To Container
|
||||||
categories: [Remote Code Execution]
|
categories: [Remote Code Execution]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV043
|
vid: KHV043
|
||||||
title: Cluster Health Disclosure
|
title: Cluster Health Disclosure
|
||||||
categories: [Information Disclosure]
|
categories: [Information Disclosure]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV044
|
vid: KHV044
|
||||||
title: Privileged Container
|
title: Privileged Container
|
||||||
categories: [Access Risk]
|
categories: [Access Risk]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV045
|
vid: KHV045
|
||||||
title: Exposed System Logs
|
title: Exposed System Logs
|
||||||
categories: [Information Disclosure]
|
categories: [Information Disclosure]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV046
|
vid: KHV046
|
||||||
title: Exposed Kubelet Cmdline
|
title: Exposed Kubelet Cmdline
|
||||||
categories: [Information Disclosure]
|
categories: [Information Disclosure]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV047
|
vid: KHV047
|
||||||
title: Pod With Mount To /var/log
|
title: Pod With Mount To /var/log
|
||||||
categories: [Privilege Escalation]
|
categories: [Privilege Escalation]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV049
|
vid: KHV049
|
||||||
title: kubectl proxy Exposed
|
title: kubectl proxy Exposed
|
||||||
categories: [Information Disclosure]
|
categories: [Information Disclosure]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV050
|
vid: KHV050
|
||||||
title: Read access to Pod service account token
|
title: Read access to Pod service account token
|
||||||
categories: [Access Risk]
|
categories: [Access Risk]
|
||||||
|
severity: medium
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV051
|
vid: KHV051
|
||||||
title: Exposed Existing Privileged Containers Via Secure Kubelet Port
|
title: Exposed Existing Privileged Containers Via Secure Kubelet Port
|
||||||
categories: [Access Risk]
|
categories: [Access Risk]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV052
|
vid: KHV052
|
||||||
title: Exposed Pods
|
title: Exposed Pods
|
||||||
categories: [Information Disclosure]
|
categories: [Information Disclosure]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
vid: KHV053
|
vid: KHV053
|
||||||
title: AWS Metadata Exposure
|
title: AWS Metadata Exposure
|
||||||
categories: [Information Disclosure]
|
categories: [Information Disclosure]
|
||||||
|
severity: high
|
||||||
---
|
---
|
||||||
|
|
||||||
# {{ page.vid }} - {{ page.title }}
|
# {{ page.vid }} - {{ page.title }}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# flake8: noqa: E402
|
# flake8: noqa: E402
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
@@ -28,6 +29,8 @@ config = Config(
|
|||||||
k8s_auto_discover_nodes=args.k8s_auto_discover_nodes,
|
k8s_auto_discover_nodes=args.k8s_auto_discover_nodes,
|
||||||
service_account_token=args.service_account_token,
|
service_account_token=args.service_account_token,
|
||||||
kubeconfig=args.kubeconfig,
|
kubeconfig=args.kubeconfig,
|
||||||
|
enable_cve_hunting=args.enable_cve_hunting,
|
||||||
|
custom=args.custom,
|
||||||
)
|
)
|
||||||
setup_logger(args.log, args.log_file)
|
setup_logger(args.log, args.log_file)
|
||||||
set_config(config)
|
set_config(config)
|
||||||
@@ -72,16 +75,20 @@ def interactive_set_config():
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def list_hunters():
|
def list_hunters(class_names=False):
|
||||||
print("\nPassive Hunters:\n----------------")
|
print("\nPassive Hunters:\n----------------")
|
||||||
for hunter, docs in handler.passive_hunters.items():
|
for hunter, docs in handler.passive_hunters.items():
|
||||||
name, doc = hunter.parse_docs(docs)
|
name, doc = hunter.parse_docs(docs)
|
||||||
|
if class_names:
|
||||||
|
name = hunter.__name__
|
||||||
print(f"* {name}\n {doc}\n")
|
print(f"* {name}\n {doc}\n")
|
||||||
|
|
||||||
if config.active:
|
if config.active:
|
||||||
print("\n\nActive Hunters:\n---------------")
|
print("\n\nActive Hunters:\n---------------")
|
||||||
for hunter, docs in handler.active_hunters.items():
|
for hunter, docs in handler.active_hunters.items():
|
||||||
name, doc = hunter.parse_docs(docs)
|
name, doc = hunter.parse_docs(docs)
|
||||||
|
if class_names:
|
||||||
|
name = hunter.__name__
|
||||||
print(f"* {name}\n {doc}\n")
|
print(f"* {name}\n {doc}\n")
|
||||||
|
|
||||||
|
|
||||||
@@ -94,7 +101,10 @@ def main():
|
|||||||
scan_options = [config.pod, config.cidr, config.remote, config.interface, config.k8s_auto_discover_nodes]
|
scan_options = [config.pod, config.cidr, config.remote, config.interface, config.k8s_auto_discover_nodes]
|
||||||
try:
|
try:
|
||||||
if args.list:
|
if args.list:
|
||||||
list_hunters()
|
if args.raw_hunter_names:
|
||||||
|
list_hunters(class_names=True)
|
||||||
|
else:
|
||||||
|
list_hunters()
|
||||||
return
|
return
|
||||||
|
|
||||||
if not any(scan_options):
|
if not any(scan_options):
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_core_hunters():
|
||||||
|
return ["FromPodHostDiscovery", "HostDiscovery", "PortDiscovery", "SendFullReport", "Collector", "StartedInfo"]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Config:
|
class Config:
|
||||||
"""Config is a configuration container.
|
"""Config is a configuration container.
|
||||||
@@ -21,6 +25,7 @@ class Config:
|
|||||||
- remote: Hosts to scan
|
- remote: Hosts to scan
|
||||||
- report: Output format
|
- report: Output format
|
||||||
- statistics: Include hunters statistics
|
- statistics: Include hunters statistics
|
||||||
|
- enable_cve_hunting: enables cve hunting, shows cve results
|
||||||
"""
|
"""
|
||||||
|
|
||||||
active: bool = False
|
active: bool = False
|
||||||
@@ -39,6 +44,10 @@ class Config:
|
|||||||
k8s_auto_discover_nodes: bool = False
|
k8s_auto_discover_nodes: bool = False
|
||||||
service_account_token: Optional[str] = None
|
service_account_token: Optional[str] = None
|
||||||
kubeconfig: Optional[str] = None
|
kubeconfig: Optional[str] = None
|
||||||
|
enable_cve_hunting: bool = False
|
||||||
|
custom: Optional[list] = None
|
||||||
|
raw_hunter_names: bool = False
|
||||||
|
core_hunters: list = field(default_factory=get_default_core_hunters)
|
||||||
|
|
||||||
|
|
||||||
_config: Optional[Config] = None
|
_config: Optional[Config] = None
|
||||||
|
|||||||
@@ -4,10 +4,6 @@ DEFAULT_LEVEL = logging.INFO
|
|||||||
DEFAULT_LEVEL_NAME = logging.getLevelName(DEFAULT_LEVEL)
|
DEFAULT_LEVEL_NAME = logging.getLevelName(DEFAULT_LEVEL)
|
||||||
LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s %(message)s"
|
LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s %(message)s"
|
||||||
|
|
||||||
# Suppress logging from scapy
|
|
||||||
logging.getLogger("scapy.runtime").setLevel(logging.CRITICAL)
|
|
||||||
logging.getLogger("scapy.loading").setLevel(logging.CRITICAL)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_logger(level_name, logfile):
|
def setup_logger(level_name, logfile):
|
||||||
# Remove any existing handlers
|
# Remove any existing handlers
|
||||||
|
|||||||
@@ -46,6 +46,22 @@ def parser_add_arguments(parser):
|
|||||||
help="One or more remote ip/dns to hunt",
|
help="One or more remote ip/dns to hunt",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-c",
|
||||||
|
"--custom",
|
||||||
|
nargs="+",
|
||||||
|
metavar="HUNTERS",
|
||||||
|
default=list(),
|
||||||
|
help="Custom hunting. Only given hunter names will register in the hunt."
|
||||||
|
"for a list of options run `--list --raw-hunter-names`",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--raw-hunter-names",
|
||||||
|
action="store_true",
|
||||||
|
help="Use in combination with `--list` to display hunter class names to pass for custom hunting flag",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--k8s-auto-discover-nodes",
|
"--k8s-auto-discover-nodes",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -76,6 +92,12 @@ def parser_add_arguments(parser):
|
|||||||
|
|
||||||
parser.add_argument("--active", action="store_true", help="Enables active hunting")
|
parser.add_argument("--active", action="store_true", help="Enables active hunting")
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--enable-cve-hunting",
|
||||||
|
action="store_true",
|
||||||
|
help="Show cluster CVEs based on discovered version (Depending on different vendors, may result in False Positives)",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--log",
|
"--log",
|
||||||
type=str,
|
type=str,
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class EventQueue(Queue):
|
|||||||
######################################################
|
######################################################
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def subscribe(self, event, hook=None, predicate=None):
|
def subscribe(self, event, hook=None, predicate=None, is_register=True):
|
||||||
"""
|
"""
|
||||||
The Subscribe Decorator - For Regular Registration
|
The Subscribe Decorator - For Regular Registration
|
||||||
Use this to register for one event only. Your hunter will execute each time this event is published
|
Use this to register for one event only. Your hunter will execute each time this event is published
|
||||||
@@ -74,12 +74,12 @@ class EventQueue(Queue):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def wrapper(hook):
|
def wrapper(hook):
|
||||||
self.subscribe_event(event, hook=hook, predicate=predicate)
|
self.subscribe_event(event, hook=hook, predicate=predicate, is_register=is_register)
|
||||||
return hook
|
return hook
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def subscribe_many(self, events, hook=None, predicates=None):
|
def subscribe_many(self, events, hook=None, predicates=None, is_register=True):
|
||||||
"""
|
"""
|
||||||
The Subscribe Many Decorator - For Multiple Registration,
|
The Subscribe Many Decorator - For Multiple Registration,
|
||||||
When your attack needs several prerequisites to exist in the cluster, You need to register for multiple events.
|
When your attack needs several prerequisites to exist in the cluster, You need to register for multiple events.
|
||||||
@@ -99,12 +99,12 @@ class EventQueue(Queue):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def wrapper(hook):
|
def wrapper(hook):
|
||||||
self.subscribe_events(events, hook=hook, predicates=predicates)
|
self.subscribe_events(events, hook=hook, predicates=predicates, is_register=is_register)
|
||||||
return hook
|
return hook
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def subscribe_once(self, event, hook=None, predicate=None):
|
def subscribe_once(self, event, hook=None, predicate=None, is_register=True):
|
||||||
"""
|
"""
|
||||||
The Subscribe Once Decorator - For Single Trigger Registration,
|
The Subscribe Once Decorator - For Single Trigger Registration,
|
||||||
Use this when you want your hunter to execute only in your entire program run
|
Use this when you want your hunter to execute only in your entire program run
|
||||||
@@ -125,7 +125,8 @@ class EventQueue(Queue):
|
|||||||
|
|
||||||
hook.__new__ = __new__unsubscribe_self
|
hook.__new__ = __new__unsubscribe_self
|
||||||
|
|
||||||
self.subscribe_event(event, hook=hook, predicate=predicate)
|
self.subscribe_event(event, hook=hook, predicate=predicate, is_register=is_register)
|
||||||
|
|
||||||
return hook
|
return hook
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
@@ -256,7 +257,33 @@ class EventQueue(Queue):
|
|||||||
self.hooks[event].append((hook, predicate))
|
self.hooks[event].append((hook, predicate))
|
||||||
logging.debug("{} subscribed to {}".format(hook, event))
|
logging.debug("{} subscribed to {}".format(hook, event))
|
||||||
|
|
||||||
def subscribe_event(self, event, hook=None, predicate=None):
|
def allowed_for_custom_registration(self, target_hunter):
|
||||||
|
"""
|
||||||
|
Check if the partial input list contains the hunter we are about to register for events
|
||||||
|
If hunter is considered a Core hunter as specified in `config.core_hunters` we allow it anyway
|
||||||
|
|
||||||
|
Returns true if:
|
||||||
|
1. partial hunt is disabled
|
||||||
|
2. partial hunt is enabled and hunter is in core hunter class
|
||||||
|
3. partial hunt is enabled and hunter is specified in config.partial
|
||||||
|
|
||||||
|
@param target_hunter: hunter class for registration check
|
||||||
|
"""
|
||||||
|
config = get_config()
|
||||||
|
if not config.custom:
|
||||||
|
return True
|
||||||
|
|
||||||
|
hunter_class_name = target_hunter.__name__
|
||||||
|
if hunter_class_name in config.core_hunters or hunter_class_name in config.custom:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def subscribe_event(self, event, hook=None, predicate=None, is_register=True):
|
||||||
|
if not is_register:
|
||||||
|
return
|
||||||
|
if not self.allowed_for_custom_registration(hook):
|
||||||
|
return
|
||||||
if not self._register_hunters(hook):
|
if not self._register_hunters(hook):
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -267,9 +294,13 @@ class EventQueue(Queue):
|
|||||||
else:
|
else:
|
||||||
self._register_hook(event, hook, predicate)
|
self._register_hook(event, hook, predicate)
|
||||||
|
|
||||||
def subscribe_events(self, events, hook=None, predicates=None):
|
def subscribe_events(self, events, hook=None, predicates=None, is_register=True):
|
||||||
|
if not is_register:
|
||||||
|
return
|
||||||
|
if not self.allowed_for_custom_registration(hook):
|
||||||
|
return
|
||||||
if not self._register_hunters(hook):
|
if not self._register_hunters(hook):
|
||||||
return False
|
return
|
||||||
|
|
||||||
if predicates is None:
|
if predicates is None:
|
||||||
predicates = [None] * len(events)
|
predicates = [None] * len(events)
|
||||||
|
|||||||
@@ -3,15 +3,32 @@ import threading
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from kube_hunter.conf import get_config
|
from kube_hunter.conf import get_config
|
||||||
from kube_hunter.core.types import (
|
from kube_hunter.core.types import KubernetesCluster
|
||||||
InformationDisclosure,
|
from kube_hunter.core.types.vulnerabilities import (
|
||||||
DenialOfService,
|
GeneralSensitiveInformationTechnique,
|
||||||
RemoteCodeExec,
|
ExposedSensitiveInterfacesTechnique,
|
||||||
IdentityTheft,
|
MountServicePrincipalTechnique,
|
||||||
PrivilegeEscalation,
|
ListK8sSecretsTechnique,
|
||||||
AccessRisk,
|
AccessContainerServiceAccountTechnique,
|
||||||
UnauthenticatedAccess,
|
AccessK8sApiServerTechnique,
|
||||||
KubernetesCluster,
|
AccessKubeletAPITechnique,
|
||||||
|
AccessK8sDashboardTechnique,
|
||||||
|
InstanceMetadataApiTechnique,
|
||||||
|
ExecIntoContainerTechnique,
|
||||||
|
SidecarInjectionTechnique,
|
||||||
|
NewContainerTechnique,
|
||||||
|
GeneralPersistenceTechnique,
|
||||||
|
HostPathMountPrivilegeEscalationTechnique,
|
||||||
|
PrivilegedContainerTechnique,
|
||||||
|
ClusterAdminBindingTechnique,
|
||||||
|
ARPPoisoningTechnique,
|
||||||
|
CoreDNSPoisoningTechnique,
|
||||||
|
DataDestructionTechnique,
|
||||||
|
GeneralDefenseEvasionTechnique,
|
||||||
|
ConnectFromProxyServerTechnique,
|
||||||
|
CVERemoteCodeExecutionCategory,
|
||||||
|
CVEPrivilegeEscalationCategory,
|
||||||
|
CVEDenialOfServiceTechnique,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -102,13 +119,30 @@ class Service:
|
|||||||
class Vulnerability:
|
class Vulnerability:
|
||||||
severity = dict(
|
severity = dict(
|
||||||
{
|
{
|
||||||
InformationDisclosure: "medium",
|
GeneralSensitiveInformationTechnique: "low",
|
||||||
DenialOfService: "medium",
|
ExposedSensitiveInterfacesTechnique: "high",
|
||||||
RemoteCodeExec: "high",
|
MountServicePrincipalTechnique: "high",
|
||||||
IdentityTheft: "high",
|
ListK8sSecretsTechnique: "high",
|
||||||
PrivilegeEscalation: "high",
|
AccessContainerServiceAccountTechnique: "low",
|
||||||
AccessRisk: "low",
|
AccessK8sApiServerTechnique: "medium",
|
||||||
UnauthenticatedAccess: "low",
|
AccessKubeletAPITechnique: "medium",
|
||||||
|
AccessK8sDashboardTechnique: "medium",
|
||||||
|
InstanceMetadataApiTechnique: "high",
|
||||||
|
ExecIntoContainerTechnique: "high",
|
||||||
|
SidecarInjectionTechnique: "high",
|
||||||
|
NewContainerTechnique: "high",
|
||||||
|
GeneralPersistenceTechnique: "high",
|
||||||
|
HostPathMountPrivilegeEscalationTechnique: "high",
|
||||||
|
PrivilegedContainerTechnique: "high",
|
||||||
|
ClusterAdminBindingTechnique: "high",
|
||||||
|
ARPPoisoningTechnique: "medium",
|
||||||
|
CoreDNSPoisoningTechnique: "high",
|
||||||
|
DataDestructionTechnique: "high",
|
||||||
|
GeneralDefenseEvasionTechnique: "high",
|
||||||
|
ConnectFromProxyServerTechnique: "low",
|
||||||
|
CVERemoteCodeExecutionCategory: "high",
|
||||||
|
CVEPrivilegeEscalationCategory: "high",
|
||||||
|
CVEDenialOfServiceTechnique: "medium",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -213,18 +247,21 @@ class ReportDispatched(Event):
|
|||||||
class K8sVersionDisclosure(Vulnerability, Event):
|
class K8sVersionDisclosure(Vulnerability, Event):
|
||||||
"""The kubernetes version could be obtained from the {} endpoint"""
|
"""The kubernetes version could be obtained from the {} endpoint"""
|
||||||
|
|
||||||
def __init__(self, version, from_endpoint, extra_info=""):
|
def __init__(self, version, from_endpoint, extra_info="", category=None):
|
||||||
Vulnerability.__init__(
|
Vulnerability.__init__(
|
||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
"K8s Version Disclosure",
|
"K8s Version Disclosure",
|
||||||
category=InformationDisclosure,
|
category=ExposedSensitiveInterfacesTechnique,
|
||||||
vid="KHV002",
|
vid="KHV002",
|
||||||
)
|
)
|
||||||
self.version = version
|
self.version = version
|
||||||
self.from_endpoint = from_endpoint
|
self.from_endpoint = from_endpoint
|
||||||
self.extra_info = extra_info
|
self.extra_info = extra_info
|
||||||
self.evidence = version
|
self.evidence = version
|
||||||
|
# depending from where the version came from, we might want to also override the category
|
||||||
|
if category:
|
||||||
|
self.category = category
|
||||||
|
|
||||||
def explain(self):
|
def explain(self):
|
||||||
return self.__doc__.format(self.from_endpoint) + self.extra_info
|
return self.__doc__.format(self.from_endpoint) + self.extra_info
|
||||||
|
|||||||
@@ -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
|
|
||||||
4
kube_hunter/core/types/__init__.py
Normal file
4
kube_hunter/core/types/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# flake8: noqa: E402
|
||||||
|
from .hunters import *
|
||||||
|
from .components import *
|
||||||
|
from .vulnerabilities import *
|
||||||
28
kube_hunter/core/types/components.py
Normal file
28
kube_hunter/core/types/components.py
Normal 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"
|
||||||
36
kube_hunter/core/types/hunters.py
Normal file
36
kube_hunter/core/types/hunters.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
class HunterBase:
|
||||||
|
publishedVulnerabilities = 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_docs(docs):
|
||||||
|
"""returns tuple of (name, docs)"""
|
||||||
|
if not docs:
|
||||||
|
return __name__, "<no documentation>"
|
||||||
|
docs = docs.strip().split("\n")
|
||||||
|
for i, line in enumerate(docs):
|
||||||
|
docs[i] = line.strip()
|
||||||
|
return docs[0], " ".join(docs[1:]) if len(docs[1:]) else "<no documentation>"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_name(cls):
|
||||||
|
name, _ = cls.parse_docs(cls.__doc__)
|
||||||
|
return name
|
||||||
|
|
||||||
|
def publish_event(self, event):
|
||||||
|
# Import here to avoid circular import from events package.
|
||||||
|
# imports are cached in python so this should not affect runtime
|
||||||
|
from ..events import handler # noqa
|
||||||
|
|
||||||
|
handler.publish_event(event, caller=self)
|
||||||
|
|
||||||
|
|
||||||
|
class ActiveHunter(HunterBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Hunter(HunterBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Discovery(HunterBase):
|
||||||
|
pass
|
||||||
188
kube_hunter/core/types/vulnerabilities.py
Normal file
188
kube_hunter/core/types/vulnerabilities.py
Normal 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)"
|
||||||
@@ -11,7 +11,7 @@ from kube_hunter.conf import get_config
|
|||||||
from kube_hunter.modules.discovery.kubernetes_client import list_all_k8s_cluster_nodes
|
from kube_hunter.modules.discovery.kubernetes_client import list_all_k8s_cluster_nodes
|
||||||
from kube_hunter.core.events import handler
|
from kube_hunter.core.events import handler
|
||||||
from kube_hunter.core.events.types import Event, NewHostEvent, Vulnerability
|
from kube_hunter.core.events.types import Event, NewHostEvent, Vulnerability
|
||||||
from kube_hunter.core.types import Discovery, InformationDisclosure, AWS, Azure
|
from kube_hunter.core.types import Discovery, AWS, Azure, InstanceMetadataApiTechnique
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ class AWSMetadataApi(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
AWS,
|
AWS,
|
||||||
"AWS Metadata Exposure",
|
"AWS Metadata Exposure",
|
||||||
category=InformationDisclosure,
|
category=InstanceMetadataApiTechnique,
|
||||||
vid="KHV053",
|
vid="KHV053",
|
||||||
)
|
)
|
||||||
self.cidr = cidr
|
self.cidr = cidr
|
||||||
@@ -70,7 +70,7 @@ class AzureMetadataApi(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
Azure,
|
Azure,
|
||||||
"Azure Metadata Exposure",
|
"Azure Metadata Exposure",
|
||||||
category=InformationDisclosure,
|
category=InstanceMetadataApiTechnique,
|
||||||
vid="KHV003",
|
vid="KHV003",
|
||||||
)
|
)
|
||||||
self.cidr = cidr
|
self.cidr = cidr
|
||||||
@@ -129,15 +129,15 @@ class FromPodHostDiscovery(Discovery):
|
|||||||
self.publish_event(HostScanEvent())
|
self.publish_event(HostScanEvent())
|
||||||
else:
|
else:
|
||||||
# Discover cluster subnets, we'll scan all these hosts
|
# Discover cluster subnets, we'll scan all these hosts
|
||||||
cloud = None
|
cloud, subnets = None, list()
|
||||||
if self.is_azure_pod():
|
if self.is_azure_pod():
|
||||||
subnets, cloud = self.azure_metadata_discovery()
|
subnets, cloud = self.azure_metadata_discovery()
|
||||||
elif self.is_aws_pod_v1():
|
elif self.is_aws_pod_v1():
|
||||||
subnets, cloud = self.aws_metadata_v1_discovery()
|
subnets, cloud = self.aws_metadata_v1_discovery()
|
||||||
elif self.is_aws_pod_v2():
|
elif self.is_aws_pod_v2():
|
||||||
subnets, cloud = self.aws_metadata_v2_discovery()
|
subnets, cloud = self.aws_metadata_v2_discovery()
|
||||||
else:
|
|
||||||
subnets = self.gateway_discovery()
|
subnets += self.gateway_discovery()
|
||||||
|
|
||||||
should_scan_apiserver = False
|
should_scan_apiserver = False
|
||||||
if self.event.kubeservicehost:
|
if self.event.kubeservicehost:
|
||||||
@@ -166,7 +166,9 @@ class FromPodHostDiscovery(Discovery):
|
|||||||
return True
|
return True
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
logger.debug("Failed to connect AWS metadata server v1")
|
logger.debug("Failed to connect AWS metadata server v1")
|
||||||
return False
|
except Exception:
|
||||||
|
logger.debug("Unknown error when trying to connect to AWS metadata v1 API")
|
||||||
|
return False
|
||||||
|
|
||||||
def is_aws_pod_v2(self):
|
def is_aws_pod_v2(self):
|
||||||
config = get_config()
|
config = get_config()
|
||||||
@@ -189,7 +191,9 @@ class FromPodHostDiscovery(Discovery):
|
|||||||
return True
|
return True
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
logger.debug("Failed to connect AWS metadata server v2")
|
logger.debug("Failed to connect AWS metadata server v2")
|
||||||
return False
|
except Exception:
|
||||||
|
logger.debug("Unknown error when trying to connect to AWS metadata v2 API")
|
||||||
|
return False
|
||||||
|
|
||||||
def is_azure_pod(self):
|
def is_azure_pod(self):
|
||||||
config = get_config()
|
config = get_config()
|
||||||
@@ -206,7 +210,9 @@ class FromPodHostDiscovery(Discovery):
|
|||||||
return True
|
return True
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
logger.debug("Failed to connect Azure metadata server")
|
logger.debug("Failed to connect Azure metadata server")
|
||||||
return False
|
except Exception:
|
||||||
|
logger.debug("Unknown error when trying to connect to Azure metadata server")
|
||||||
|
return False
|
||||||
|
|
||||||
# for pod scanning
|
# for pod scanning
|
||||||
def gateway_discovery(self):
|
def gateway_discovery(self):
|
||||||
@@ -221,19 +227,27 @@ class FromPodHostDiscovery(Discovery):
|
|||||||
"http://169.254.169.254/latest/meta-data/mac",
|
"http://169.254.169.254/latest/meta-data/mac",
|
||||||
timeout=config.network_timeout,
|
timeout=config.network_timeout,
|
||||||
).text
|
).text
|
||||||
|
logger.debug(f"Extracted mac from aws's metadata v1: {mac_address}")
|
||||||
|
|
||||||
cidr = requests.get(
|
cidr = requests.get(
|
||||||
f"http://169.254.169.254/latest/meta-data/network/interfaces/macs/{mac_address}/subnet-ipv4-cidr-block",
|
f"http://169.254.169.254/latest/meta-data/network/interfaces/macs/{mac_address}/subnet-ipv4-cidr-block",
|
||||||
timeout=config.network_timeout,
|
timeout=config.network_timeout,
|
||||||
).text.split("/")
|
).text
|
||||||
|
logger.debug(f"Trying to extract cidr from aws's metadata v1: {cidr}")
|
||||||
|
|
||||||
address, subnet = (cidr[0], cidr[1])
|
try:
|
||||||
subnet = subnet if not config.quick else "24"
|
cidr = cidr.split("/")
|
||||||
cidr = f"{address}/{subnet}"
|
address, subnet = (cidr[0], cidr[1])
|
||||||
logger.debug(f"From pod discovered subnet {cidr}")
|
subnet = subnet if not config.quick else "24"
|
||||||
|
cidr = f"{address}/{subnet}"
|
||||||
|
logger.debug(f"From pod discovered subnet {cidr}")
|
||||||
|
|
||||||
self.publish_event(AWSMetadataApi(cidr=cidr))
|
self.publish_event(AWSMetadataApi(cidr=cidr))
|
||||||
|
return [(address, subnet)], "AWS"
|
||||||
|
except Exception as x:
|
||||||
|
logger.debug(f"ERROR: could not parse cidr from aws metadata api: {cidr} - {x}")
|
||||||
|
|
||||||
return [(address, subnet)], "AWS"
|
return [], "AWS"
|
||||||
|
|
||||||
# querying AWS's interface metadata api v2 | works only from a pod
|
# querying AWS's interface metadata api v2 | works only from a pod
|
||||||
def aws_metadata_v2_discovery(self):
|
def aws_metadata_v2_discovery(self):
|
||||||
@@ -255,14 +269,19 @@ class FromPodHostDiscovery(Discovery):
|
|||||||
timeout=config.network_timeout,
|
timeout=config.network_timeout,
|
||||||
).text.split("/")
|
).text.split("/")
|
||||||
|
|
||||||
address, subnet = (cidr[0], cidr[1])
|
try:
|
||||||
subnet = subnet if not config.quick else "24"
|
address, subnet = (cidr[0], cidr[1])
|
||||||
cidr = f"{address}/{subnet}"
|
subnet = subnet if not config.quick else "24"
|
||||||
logger.debug(f"From pod discovered subnet {cidr}")
|
cidr = f"{address}/{subnet}"
|
||||||
|
logger.debug(f"From pod discovered subnet {cidr}")
|
||||||
|
|
||||||
self.publish_event(AWSMetadataApi(cidr=cidr))
|
self.publish_event(AWSMetadataApi(cidr=cidr))
|
||||||
|
|
||||||
return [(address, subnet)], "AWS"
|
return [(address, subnet)], "AWS"
|
||||||
|
except Exception as x:
|
||||||
|
logger.debug(f"ERROR: could not parse cidr from aws metadata api: {cidr} - {x}")
|
||||||
|
|
||||||
|
return [], "AWS"
|
||||||
|
|
||||||
# querying azure's interface metadata api | works only from a pod
|
# querying azure's interface metadata api | works only from a pod
|
||||||
def azure_metadata_discovery(self):
|
def azure_metadata_discovery(self):
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ def list_all_k8s_cluster_nodes(kube_config=None, client=None):
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
try:
|
try:
|
||||||
if kube_config:
|
if kube_config:
|
||||||
logger.info("Attempting to use kubeconfig file: %s", kube_config)
|
logger.debug("Attempting to use kubeconfig file: %s", kube_config)
|
||||||
kubernetes.config.load_kube_config(config_file=kube_config)
|
kubernetes.config.load_kube_config(config_file=kube_config)
|
||||||
else:
|
else:
|
||||||
logger.info("Attempting to use in cluster Kubernetes config")
|
logger.debug("Attempting to use in cluster Kubernetes config")
|
||||||
kubernetes.config.load_incluster_config()
|
kubernetes.config.load_incluster_config()
|
||||||
except kubernetes.config.config_exception.ConfigException as ex:
|
except kubernetes.config.config_exception.ConfigException as ex:
|
||||||
logger.exception(f"Failed to initiate Kubernetes client: {ex}")
|
logger.debug(f"Failed to initiate Kubernetes client: {ex}")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -24,4 +24,4 @@ def list_all_k8s_cluster_nodes(kube_config=None, client=None):
|
|||||||
for addr in item.status.addresses:
|
for addr in item.status.addresses:
|
||||||
yield addr.address
|
yield addr.address
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.exception(f"Failed to list nodes from Kubernetes: {ex}")
|
logger.debug(f"Failed to list nodes from Kubernetes: {ex}")
|
||||||
|
|||||||
@@ -2,12 +2,10 @@
|
|||||||
from . import (
|
from . import (
|
||||||
aks,
|
aks,
|
||||||
apiserver,
|
apiserver,
|
||||||
arp,
|
|
||||||
capabilities,
|
capabilities,
|
||||||
certificates,
|
certificates,
|
||||||
cves,
|
cves,
|
||||||
dashboard,
|
dashboard,
|
||||||
dns,
|
|
||||||
etcd,
|
etcd,
|
||||||
kubelet,
|
kubelet,
|
||||||
mounts,
|
mounts,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from kube_hunter.conf import get_config
|
|||||||
from kube_hunter.modules.hunting.kubelet import ExposedPodsHandler, SecureKubeletPortHunter
|
from kube_hunter.modules.hunting.kubelet import ExposedPodsHandler, SecureKubeletPortHunter
|
||||||
from kube_hunter.core.events import handler
|
from kube_hunter.core.events import handler
|
||||||
from kube_hunter.core.events.types import Event, Vulnerability
|
from kube_hunter.core.events.types import Event, Vulnerability
|
||||||
from kube_hunter.core.types import Hunter, ActiveHunter, IdentityTheft, Azure
|
from kube_hunter.core.types import Hunter, ActiveHunter, MountServicePrincipalTechnique, Azure
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ class AzureSpnExposure(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
Azure,
|
Azure,
|
||||||
"Azure SPN Exposure",
|
"Azure SPN Exposure",
|
||||||
category=IdentityTheft,
|
category=MountServicePrincipalTechnique,
|
||||||
vid="KHV004",
|
vid="KHV004",
|
||||||
)
|
)
|
||||||
self.container = container
|
self.container = container
|
||||||
|
|||||||
@@ -8,10 +8,15 @@ from kube_hunter.modules.discovery.apiserver import ApiServer
|
|||||||
from kube_hunter.core.events import handler
|
from kube_hunter.core.events import handler
|
||||||
from kube_hunter.core.events.types import Vulnerability, Event, K8sVersionDisclosure
|
from kube_hunter.core.events.types import Vulnerability, Event, K8sVersionDisclosure
|
||||||
from kube_hunter.core.types import Hunter, ActiveHunter, KubernetesCluster
|
from kube_hunter.core.types import Hunter, ActiveHunter, KubernetesCluster
|
||||||
from kube_hunter.core.types import (
|
from kube_hunter.core.types.vulnerabilities import (
|
||||||
AccessRisk,
|
AccessK8sApiServerTechnique,
|
||||||
InformationDisclosure,
|
ExposedSensitiveInterfacesTechnique,
|
||||||
UnauthenticatedAccess,
|
GeneralDefenseEvasionTechnique,
|
||||||
|
DataDestructionTechnique,
|
||||||
|
ClusterAdminBindingTechnique,
|
||||||
|
NewContainerTechnique,
|
||||||
|
PrivilegedContainerTechnique,
|
||||||
|
SidecarInjectionTechnique,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -24,10 +29,10 @@ class ServerApiAccess(Vulnerability, Event):
|
|||||||
def __init__(self, evidence, using_token):
|
def __init__(self, evidence, using_token):
|
||||||
if using_token:
|
if using_token:
|
||||||
name = "Access to API using service account token"
|
name = "Access to API using service account token"
|
||||||
category = InformationDisclosure
|
category = AccessK8sApiServerTechnique
|
||||||
else:
|
else:
|
||||||
name = "Unauthenticated access to API"
|
name = "Unauthenticated access to API"
|
||||||
category = UnauthenticatedAccess
|
category = ExposedSensitiveInterfacesTechnique
|
||||||
Vulnerability.__init__(
|
Vulnerability.__init__(
|
||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
@@ -44,7 +49,7 @@ class ServerApiHTTPAccess(Vulnerability, Event):
|
|||||||
|
|
||||||
def __init__(self, evidence):
|
def __init__(self, evidence):
|
||||||
name = "Insecure (HTTP) access to API"
|
name = "Insecure (HTTP) access to API"
|
||||||
category = UnauthenticatedAccess
|
category = ExposedSensitiveInterfacesTechnique
|
||||||
Vulnerability.__init__(
|
Vulnerability.__init__(
|
||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
@@ -59,7 +64,7 @@ class ApiInfoDisclosure(Vulnerability, Event):
|
|||||||
"""Information Disclosure depending upon RBAC permissions and Kube-Cluster Setup"""
|
"""Information Disclosure depending upon RBAC permissions and Kube-Cluster Setup"""
|
||||||
|
|
||||||
def __init__(self, evidence, using_token, name):
|
def __init__(self, evidence, using_token, name):
|
||||||
category = InformationDisclosure
|
category = AccessK8sApiServerTechnique
|
||||||
if using_token:
|
if using_token:
|
||||||
name += " using default service account token"
|
name += " using default service account token"
|
||||||
else:
|
else:
|
||||||
@@ -111,7 +116,7 @@ class CreateANamespace(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Created a namespace",
|
name="Created a namespace",
|
||||||
category=AccessRisk,
|
category=GeneralDefenseEvasionTechnique,
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
|
|
||||||
@@ -125,7 +130,7 @@ class DeleteANamespace(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Delete a namespace",
|
name="Delete a namespace",
|
||||||
category=AccessRisk,
|
category=DataDestructionTechnique,
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
|
|
||||||
@@ -136,7 +141,7 @@ class CreateARole(Vulnerability, Event):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, evidence):
|
def __init__(self, evidence):
|
||||||
Vulnerability.__init__(self, KubernetesCluster, name="Created a role", category=AccessRisk)
|
Vulnerability.__init__(self, KubernetesCluster, name="Created a role", category=GeneralDefenseEvasionTechnique)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
|
|
||||||
|
|
||||||
@@ -150,7 +155,7 @@ class CreateAClusterRole(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Created a cluster role",
|
name="Created a cluster role",
|
||||||
category=AccessRisk,
|
category=ClusterAdminBindingTechnique,
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
|
|
||||||
@@ -165,7 +170,7 @@ class PatchARole(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Patched a role",
|
name="Patched a role",
|
||||||
category=AccessRisk,
|
category=ClusterAdminBindingTechnique,
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
|
|
||||||
@@ -180,7 +185,7 @@ class PatchAClusterRole(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Patched a cluster role",
|
name="Patched a cluster role",
|
||||||
category=AccessRisk,
|
category=ClusterAdminBindingTechnique,
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
|
|
||||||
@@ -193,7 +198,7 @@ class DeleteARole(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Deleted a role",
|
name="Deleted a role",
|
||||||
category=AccessRisk,
|
category=DataDestructionTechnique,
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
|
|
||||||
@@ -206,7 +211,7 @@ class DeleteAClusterRole(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Deleted a cluster role",
|
name="Deleted a cluster role",
|
||||||
category=AccessRisk,
|
category=DataDestructionTechnique,
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
|
|
||||||
@@ -219,7 +224,7 @@ class CreateAPod(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Created A Pod",
|
name="Created A Pod",
|
||||||
category=AccessRisk,
|
category=NewContainerTechnique,
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
|
|
||||||
@@ -232,7 +237,7 @@ class CreateAPrivilegedPod(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Created A PRIVILEGED Pod",
|
name="Created A PRIVILEGED Pod",
|
||||||
category=AccessRisk,
|
category=PrivilegedContainerTechnique,
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
|
|
||||||
@@ -245,7 +250,7 @@ class PatchAPod(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Patched A Pod",
|
name="Patched A Pod",
|
||||||
category=AccessRisk,
|
category=SidecarInjectionTechnique,
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
|
|
||||||
@@ -258,7 +263,7 @@ class DeleteAPod(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Deleted A Pod",
|
name="Deleted A Pod",
|
||||||
category=AccessRisk,
|
category=DataDestructionTechnique,
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
|
|
||||||
@@ -377,7 +382,7 @@ class AccessApiServerWithToken(AccessApiServer):
|
|||||||
super().__init__(event)
|
super().__init__(event)
|
||||||
assert self.event.auth_token
|
assert self.event.auth_token
|
||||||
self.headers = {"Authorization": f"Bearer {self.event.auth_token}"}
|
self.headers = {"Authorization": f"Bearer {self.event.auth_token}"}
|
||||||
self.category = InformationDisclosure
|
self.category = AccessK8sApiServerTechnique
|
||||||
self.with_token = True
|
self.with_token = True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
|
||||||
@@ -4,7 +4,7 @@ import logging
|
|||||||
from kube_hunter.modules.discovery.hosts import RunningAsPodEvent
|
from kube_hunter.modules.discovery.hosts import RunningAsPodEvent
|
||||||
from kube_hunter.core.events import handler
|
from kube_hunter.core.events import handler
|
||||||
from kube_hunter.core.events.types import Event, Vulnerability
|
from kube_hunter.core.events.types import Event, Vulnerability
|
||||||
from kube_hunter.core.types import Hunter, AccessRisk, KubernetesCluster
|
from kube_hunter.core.types import Hunter, ARPPoisoningTechnique, KubernetesCluster
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ class CapNetRawEnabled(Event, Vulnerability):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="CAP_NET_RAW Enabled",
|
name="CAP_NET_RAW Enabled",
|
||||||
category=AccessRisk,
|
category=ARPPoisoningTechnique,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
import base64
|
import base64
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from kube_hunter.core.types import Hunter, KubernetesCluster, InformationDisclosure
|
from kube_hunter.core.types import Hunter, KubernetesCluster, GeneralSensitiveInformationTechnique
|
||||||
from kube_hunter.core.events import handler
|
from kube_hunter.core.events import handler
|
||||||
from kube_hunter.core.events.types import Vulnerability, Event, Service
|
from kube_hunter.core.events.types import Vulnerability, Event, Service
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ class CertificateEmail(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
"Certificate Includes Email Address",
|
"Certificate Includes Email Address",
|
||||||
category=InformationDisclosure,
|
category=GeneralSensitiveInformationTechnique,
|
||||||
vid="KHV021",
|
vid="KHV021",
|
||||||
)
|
)
|
||||||
self.email = email
|
self.email = email
|
||||||
|
|||||||
@@ -3,18 +3,20 @@ from packaging import version
|
|||||||
|
|
||||||
from kube_hunter.conf import get_config
|
from kube_hunter.conf import get_config
|
||||||
from kube_hunter.core.events import handler
|
from kube_hunter.core.events import handler
|
||||||
from kube_hunter.core.events.types import Vulnerability, Event, K8sVersionDisclosure
|
|
||||||
|
from kube_hunter.core.events.types import K8sVersionDisclosure, Vulnerability, Event
|
||||||
from kube_hunter.core.types import (
|
from kube_hunter.core.types import (
|
||||||
Hunter,
|
Hunter,
|
||||||
KubernetesCluster,
|
|
||||||
RemoteCodeExec,
|
|
||||||
PrivilegeEscalation,
|
|
||||||
DenialOfService,
|
|
||||||
KubectlClient,
|
KubectlClient,
|
||||||
|
KubernetesCluster,
|
||||||
|
CVERemoteCodeExecutionCategory,
|
||||||
|
CVEPrivilegeEscalationCategory,
|
||||||
|
CVEDenialOfServiceTechnique,
|
||||||
)
|
)
|
||||||
from kube_hunter.modules.discovery.kubectl import KubectlClientEvent
|
from kube_hunter.modules.discovery.kubectl import KubectlClientEvent
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
config = get_config()
|
||||||
|
|
||||||
|
|
||||||
class ServerApiVersionEndPointAccessPE(Vulnerability, Event):
|
class ServerApiVersionEndPointAccessPE(Vulnerability, Event):
|
||||||
@@ -25,7 +27,7 @@ class ServerApiVersionEndPointAccessPE(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Critical Privilege Escalation CVE",
|
name="Critical Privilege Escalation CVE",
|
||||||
category=PrivilegeEscalation,
|
category=CVEPrivilegeEscalationCategory,
|
||||||
vid="KHV022",
|
vid="KHV022",
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
@@ -40,7 +42,7 @@ class ServerApiVersionEndPointAccessDos(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Denial of Service to Kubernetes API Server",
|
name="Denial of Service to Kubernetes API Server",
|
||||||
category=DenialOfService,
|
category=CVEDenialOfServiceTechnique,
|
||||||
vid="KHV023",
|
vid="KHV023",
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
@@ -55,7 +57,7 @@ class PingFloodHttp2Implementation(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Possible Ping Flood Attack",
|
name="Possible Ping Flood Attack",
|
||||||
category=DenialOfService,
|
category=CVEDenialOfServiceTechnique,
|
||||||
vid="KHV024",
|
vid="KHV024",
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
@@ -70,7 +72,7 @@ class ResetFloodHttp2Implementation(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Possible Reset Flood Attack",
|
name="Possible Reset Flood Attack",
|
||||||
category=DenialOfService,
|
category=CVEDenialOfServiceTechnique,
|
||||||
vid="KHV025",
|
vid="KHV025",
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
@@ -85,7 +87,7 @@ class ServerApiClusterScopedResourcesAccess(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Arbitrary Access To Cluster Scoped Resources",
|
name="Arbitrary Access To Cluster Scoped Resources",
|
||||||
category=PrivilegeEscalation,
|
category=CVEPrivilegeEscalationCategory,
|
||||||
vid="KHV026",
|
vid="KHV026",
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
@@ -100,7 +102,7 @@ class IncompleteFixToKubectlCpVulnerability(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubectlClient,
|
KubectlClient,
|
||||||
"Kubectl Vulnerable To CVE-2019-11246",
|
"Kubectl Vulnerable To CVE-2019-11246",
|
||||||
category=RemoteCodeExec,
|
category=CVERemoteCodeExecutionCategory,
|
||||||
vid="KHV027",
|
vid="KHV027",
|
||||||
)
|
)
|
||||||
self.binary_version = binary_version
|
self.binary_version = binary_version
|
||||||
@@ -116,7 +118,7 @@ class KubectlCpVulnerability(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubectlClient,
|
KubectlClient,
|
||||||
"Kubectl Vulnerable To CVE-2019-1002101",
|
"Kubectl Vulnerable To CVE-2019-1002101",
|
||||||
category=RemoteCodeExec,
|
category=CVERemoteCodeExecutionCategory,
|
||||||
vid="KHV028",
|
vid="KHV028",
|
||||||
)
|
)
|
||||||
self.binary_version = binary_version
|
self.binary_version = binary_version
|
||||||
@@ -199,7 +201,7 @@ class CveUtils:
|
|||||||
return vulnerable
|
return vulnerable
|
||||||
|
|
||||||
|
|
||||||
@handler.subscribe_once(K8sVersionDisclosure)
|
@handler.subscribe_once(K8sVersionDisclosure, is_register=config.enable_cve_hunting)
|
||||||
class K8sClusterCveHunter(Hunter):
|
class K8sClusterCveHunter(Hunter):
|
||||||
"""K8s CVE Hunter
|
"""K8s CVE Hunter
|
||||||
Checks if Node is running a Kubernetes version vulnerable to
|
Checks if Node is running a Kubernetes version vulnerable to
|
||||||
@@ -224,6 +226,7 @@ class K8sClusterCveHunter(Hunter):
|
|||||||
self.publish_event(vulnerability(self.event.version))
|
self.publish_event(vulnerability(self.event.version))
|
||||||
|
|
||||||
|
|
||||||
|
# Removed due to incomplete implementation for multiple vendors revisions of kubernetes
|
||||||
@handler.subscribe(KubectlClientEvent)
|
@handler.subscribe(KubectlClientEvent)
|
||||||
class KubectlCVEHunter(Hunter):
|
class KubectlCVEHunter(Hunter):
|
||||||
"""Kubectl CVE Hunter
|
"""Kubectl CVE Hunter
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import json
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from kube_hunter.conf import get_config
|
from kube_hunter.conf import get_config
|
||||||
from kube_hunter.core.types import Hunter, RemoteCodeExec, KubernetesCluster
|
from kube_hunter.core.types import Hunter, AccessK8sDashboardTechnique, KubernetesCluster
|
||||||
from kube_hunter.core.events import handler
|
from kube_hunter.core.events import handler
|
||||||
from kube_hunter.core.events.types import Vulnerability, Event
|
from kube_hunter.core.events.types import Vulnerability, Event
|
||||||
from kube_hunter.modules.discovery.dashboard import KubeDashboardEvent
|
from kube_hunter.modules.discovery.dashboard import KubeDashboardEvent
|
||||||
@@ -19,7 +19,7 @@ class DashboardExposed(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
"Dashboard Exposed",
|
"Dashboard Exposed",
|
||||||
category=RemoteCodeExec,
|
category=AccessK8sDashboardTechnique,
|
||||||
vid="KHV029",
|
vid="KHV029",
|
||||||
)
|
)
|
||||||
self.evidence = "nodes: {}".format(" ".join(nodes)) if nodes else None
|
self.evidence = "nodes: {}".format(" ".join(nodes)) if nodes else None
|
||||||
|
|||||||
@@ -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")
|
|
||||||
@@ -8,10 +8,10 @@ from kube_hunter.core.types import (
|
|||||||
ActiveHunter,
|
ActiveHunter,
|
||||||
Hunter,
|
Hunter,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
InformationDisclosure,
|
GeneralSensitiveInformationTechnique,
|
||||||
RemoteCodeExec,
|
GeneralPersistenceTechnique,
|
||||||
UnauthenticatedAccess,
|
ListK8sSecretsTechnique,
|
||||||
AccessRisk,
|
ExposedSensitiveInterfacesTechnique,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -29,7 +29,7 @@ class EtcdRemoteWriteAccessEvent(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Etcd Remote Write Access Event",
|
name="Etcd Remote Write Access Event",
|
||||||
category=RemoteCodeExec,
|
category=GeneralPersistenceTechnique,
|
||||||
vid="KHV031",
|
vid="KHV031",
|
||||||
)
|
)
|
||||||
self.evidence = write_res
|
self.evidence = write_res
|
||||||
@@ -43,7 +43,7 @@ class EtcdRemoteReadAccessEvent(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Etcd Remote Read Access Event",
|
name="Etcd Remote Read Access Event",
|
||||||
category=AccessRisk,
|
category=ListK8sSecretsTechnique,
|
||||||
vid="KHV032",
|
vid="KHV032",
|
||||||
)
|
)
|
||||||
self.evidence = keys
|
self.evidence = keys
|
||||||
@@ -58,7 +58,7 @@ class EtcdRemoteVersionDisclosureEvent(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Etcd Remote version disclosure",
|
name="Etcd Remote version disclosure",
|
||||||
category=InformationDisclosure,
|
category=GeneralSensitiveInformationTechnique,
|
||||||
vid="KHV033",
|
vid="KHV033",
|
||||||
)
|
)
|
||||||
self.evidence = version
|
self.evidence = version
|
||||||
@@ -74,7 +74,7 @@ class EtcdAccessEnabledWithoutAuthEvent(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Etcd is accessible using insecure connection (HTTP)",
|
name="Etcd is accessible using insecure connection (HTTP)",
|
||||||
category=UnauthenticatedAccess,
|
category=ExposedSensitiveInterfacesTechnique,
|
||||||
vid="KHV034",
|
vid="KHV034",
|
||||||
)
|
)
|
||||||
self.evidence = version
|
self.evidence = version
|
||||||
|
|||||||
@@ -16,9 +16,12 @@ from kube_hunter.core.types import (
|
|||||||
ActiveHunter,
|
ActiveHunter,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
Kubelet,
|
Kubelet,
|
||||||
InformationDisclosure,
|
ExposedSensitiveInterfacesTechnique,
|
||||||
RemoteCodeExec,
|
ExecIntoContainerTechnique,
|
||||||
AccessRisk,
|
GeneralDefenseEvasionTechnique,
|
||||||
|
GeneralSensitiveInformationTechnique,
|
||||||
|
PrivilegedContainerTechnique,
|
||||||
|
AccessKubeletAPITechnique,
|
||||||
)
|
)
|
||||||
from kube_hunter.modules.discovery.kubelet import (
|
from kube_hunter.modules.discovery.kubelet import (
|
||||||
ReadOnlyKubeletEvent,
|
ReadOnlyKubeletEvent,
|
||||||
@@ -35,7 +38,7 @@ class ExposedPodsHandler(Vulnerability, Event):
|
|||||||
|
|
||||||
def __init__(self, pods):
|
def __init__(self, pods):
|
||||||
Vulnerability.__init__(
|
Vulnerability.__init__(
|
||||||
self, component=Kubelet, name="Exposed Pods", category=InformationDisclosure, vid="KHV052"
|
self, component=Kubelet, name="Exposed Pods", category=AccessKubeletAPITechnique, vid="KHV052"
|
||||||
)
|
)
|
||||||
self.pods = pods
|
self.pods = pods
|
||||||
self.evidence = f"count: {len(self.pods)}"
|
self.evidence = f"count: {len(self.pods)}"
|
||||||
@@ -50,7 +53,7 @@ class AnonymousAuthEnabled(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
component=Kubelet,
|
component=Kubelet,
|
||||||
name="Anonymous Authentication",
|
name="Anonymous Authentication",
|
||||||
category=RemoteCodeExec,
|
category=ExposedSensitiveInterfacesTechnique,
|
||||||
vid="KHV036",
|
vid="KHV036",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,7 +66,7 @@ class ExposedContainerLogsHandler(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
component=Kubelet,
|
component=Kubelet,
|
||||||
name="Exposed Container Logs",
|
name="Exposed Container Logs",
|
||||||
category=InformationDisclosure,
|
category=AccessKubeletAPITechnique,
|
||||||
vid="KHV037",
|
vid="KHV037",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -77,7 +80,7 @@ class ExposedRunningPodsHandler(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
component=Kubelet,
|
component=Kubelet,
|
||||||
name="Exposed Running Pods",
|
name="Exposed Running Pods",
|
||||||
category=InformationDisclosure,
|
category=AccessKubeletAPITechnique,
|
||||||
vid="KHV038",
|
vid="KHV038",
|
||||||
)
|
)
|
||||||
self.count = count
|
self.count = count
|
||||||
@@ -92,7 +95,7 @@ class ExposedExecHandler(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
component=Kubelet,
|
component=Kubelet,
|
||||||
name="Exposed Exec On Container",
|
name="Exposed Exec On Container",
|
||||||
category=RemoteCodeExec,
|
category=ExecIntoContainerTechnique,
|
||||||
vid="KHV039",
|
vid="KHV039",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -105,7 +108,7 @@ class ExposedRunHandler(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
component=Kubelet,
|
component=Kubelet,
|
||||||
name="Exposed Run Inside Container",
|
name="Exposed Run Inside Container",
|
||||||
category=RemoteCodeExec,
|
category=ExecIntoContainerTechnique,
|
||||||
vid="KHV040",
|
vid="KHV040",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -118,7 +121,7 @@ class ExposedPortForwardHandler(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
component=Kubelet,
|
component=Kubelet,
|
||||||
name="Exposed Port Forward",
|
name="Exposed Port Forward",
|
||||||
category=RemoteCodeExec,
|
category=GeneralDefenseEvasionTechnique,
|
||||||
vid="KHV041",
|
vid="KHV041",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -132,7 +135,7 @@ class ExposedAttachHandler(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
component=Kubelet,
|
component=Kubelet,
|
||||||
name="Exposed Attaching To Container",
|
name="Exposed Attaching To Container",
|
||||||
category=RemoteCodeExec,
|
category=ExecIntoContainerTechnique,
|
||||||
vid="KHV042",
|
vid="KHV042",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -146,7 +149,7 @@ class ExposedHealthzHandler(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
component=Kubelet,
|
component=Kubelet,
|
||||||
name="Cluster Health Disclosure",
|
name="Cluster Health Disclosure",
|
||||||
category=InformationDisclosure,
|
category=GeneralSensitiveInformationTechnique,
|
||||||
vid="KHV043",
|
vid="KHV043",
|
||||||
)
|
)
|
||||||
self.status = status
|
self.status = status
|
||||||
@@ -163,7 +166,7 @@ the whole cluster"""
|
|||||||
self,
|
self,
|
||||||
component=KubernetesCluster,
|
component=KubernetesCluster,
|
||||||
name="Exposed Existing Privileged Container(s) Via Secure Kubelet Port",
|
name="Exposed Existing Privileged Container(s) Via Secure Kubelet Port",
|
||||||
category=AccessRisk,
|
category=PrivilegedContainerTechnique,
|
||||||
vid="KHV051",
|
vid="KHV051",
|
||||||
)
|
)
|
||||||
self.exposed_existing_privileged_containers = exposed_existing_privileged_containers
|
self.exposed_existing_privileged_containers = exposed_existing_privileged_containers
|
||||||
@@ -178,7 +181,7 @@ class PrivilegedContainers(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
component=KubernetesCluster,
|
component=KubernetesCluster,
|
||||||
name="Privileged Container",
|
name="Privileged Container",
|
||||||
category=AccessRisk,
|
category=PrivilegedContainerTechnique,
|
||||||
vid="KHV044",
|
vid="KHV044",
|
||||||
)
|
)
|
||||||
self.containers = containers
|
self.containers = containers
|
||||||
@@ -193,7 +196,7 @@ class ExposedSystemLogs(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
component=Kubelet,
|
component=Kubelet,
|
||||||
name="Exposed System Logs",
|
name="Exposed System Logs",
|
||||||
category=InformationDisclosure,
|
category=AccessKubeletAPITechnique,
|
||||||
vid="KHV045",
|
vid="KHV045",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -206,7 +209,7 @@ class ExposedKubeletCmdline(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
component=Kubelet,
|
component=Kubelet,
|
||||||
name="Exposed Kubelet Cmdline",
|
name="Exposed Kubelet Cmdline",
|
||||||
category=InformationDisclosure,
|
category=AccessKubeletAPITechnique,
|
||||||
vid="KHV046",
|
vid="KHV046",
|
||||||
)
|
)
|
||||||
self.cmdline = cmdline
|
self.cmdline = cmdline
|
||||||
|
|||||||
@@ -5,12 +5,7 @@ import uuid
|
|||||||
from kube_hunter.conf import get_config
|
from kube_hunter.conf import get_config
|
||||||
from kube_hunter.core.events import handler
|
from kube_hunter.core.events import handler
|
||||||
from kube_hunter.core.events.types import Event, Vulnerability
|
from kube_hunter.core.events.types import Event, Vulnerability
|
||||||
from kube_hunter.core.types import (
|
from kube_hunter.core.types import ActiveHunter, Hunter, KubernetesCluster, HostPathMountPrivilegeEscalationTechnique
|
||||||
ActiveHunter,
|
|
||||||
Hunter,
|
|
||||||
KubernetesCluster,
|
|
||||||
PrivilegeEscalation,
|
|
||||||
)
|
|
||||||
from kube_hunter.modules.hunting.kubelet import (
|
from kube_hunter.modules.hunting.kubelet import (
|
||||||
ExposedPodsHandler,
|
ExposedPodsHandler,
|
||||||
ExposedRunHandler,
|
ExposedRunHandler,
|
||||||
@@ -28,7 +23,7 @@ class WriteMountToVarLog(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
"Pod With Mount To /var/log",
|
"Pod With Mount To /var/log",
|
||||||
category=PrivilegeEscalation,
|
category=HostPathMountPrivilegeEscalationTechnique,
|
||||||
vid="KHV047",
|
vid="KHV047",
|
||||||
)
|
)
|
||||||
self.pods = pods
|
self.pods = pods
|
||||||
@@ -44,7 +39,7 @@ class DirectoryTraversalWithKubelet(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
"Root Traversal Read On The Kubelet",
|
"Root Traversal Read On The Kubelet",
|
||||||
category=PrivilegeEscalation,
|
category=HostPathMountPrivilegeEscalationTechnique,
|
||||||
)
|
)
|
||||||
self.output = output
|
self.output = output
|
||||||
self.evidence = f"output: {self.output}"
|
self.evidence = f"output: {self.output}"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from kube_hunter.core.types import (
|
|||||||
ActiveHunter,
|
ActiveHunter,
|
||||||
Hunter,
|
Hunter,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
InformationDisclosure,
|
ConnectFromProxyServerTechnique,
|
||||||
)
|
)
|
||||||
from kube_hunter.modules.discovery.dashboard import KubeDashboardEvent
|
from kube_hunter.modules.discovery.dashboard import KubeDashboardEvent
|
||||||
from kube_hunter.modules.discovery.proxy import KubeProxyEvent
|
from kube_hunter.modules.discovery.proxy import KubeProxyEvent
|
||||||
@@ -26,7 +26,7 @@ class KubeProxyExposed(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
"Proxy Exposed",
|
"Proxy Exposed",
|
||||||
category=InformationDisclosure,
|
category=ConnectFromProxyServerTechnique,
|
||||||
vid="KHV049",
|
vid="KHV049",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -123,5 +123,6 @@ class K8sVersionDisclosureProve(ActiveHunter):
|
|||||||
version=version_metadata["gitVersion"],
|
version=version_metadata["gitVersion"],
|
||||||
from_endpoint="/version",
|
from_endpoint="/version",
|
||||||
extra_info="on kube-proxy",
|
extra_info="on kube-proxy",
|
||||||
|
category=ConnectFromProxyServerTechnique,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import os
|
|||||||
|
|
||||||
from kube_hunter.core.events import handler
|
from kube_hunter.core.events import handler
|
||||||
from kube_hunter.core.events.types import Vulnerability, Event
|
from kube_hunter.core.events.types import Vulnerability, Event
|
||||||
from kube_hunter.core.types import Hunter, KubernetesCluster, AccessRisk
|
from kube_hunter.core.types import Hunter, KubernetesCluster, AccessContainerServiceAccountTechnique
|
||||||
from kube_hunter.modules.discovery.hosts import RunningAsPodEvent
|
from kube_hunter.modules.discovery.hosts import RunningAsPodEvent
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -17,7 +17,7 @@ class ServiceAccountTokenAccess(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
KubernetesCluster,
|
KubernetesCluster,
|
||||||
name="Read access to pod's service account token",
|
name="Read access to pod's service account token",
|
||||||
category=AccessRisk,
|
category=AccessContainerServiceAccountTechnique,
|
||||||
vid="KHV050",
|
vid="KHV050",
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
@@ -31,7 +31,7 @@ class SecretsAccess(Vulnerability, Event):
|
|||||||
self,
|
self,
|
||||||
component=KubernetesCluster,
|
component=KubernetesCluster,
|
||||||
name="Access to pod's secrets",
|
name="Access to pod's secrets",
|
||||||
category=AccessRisk,
|
category=AccessContainerServiceAccountTechnique,
|
||||||
)
|
)
|
||||||
self.evidence = evidence
|
self.evidence = evidence
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class BaseReporter:
|
|||||||
{
|
{
|
||||||
"location": vuln.location(),
|
"location": vuln.location(),
|
||||||
"vid": vuln.get_vid(),
|
"vid": vuln.get_vid(),
|
||||||
"category": vuln.category.name,
|
"category": vuln.category.get_name(),
|
||||||
"severity": vuln.get_severity(),
|
"severity": vuln.get_severity(),
|
||||||
"vulnerability": vuln.get_name(),
|
"vulnerability": vuln.get_name(),
|
||||||
"description": vuln.explain(),
|
"description": vuln.explain(),
|
||||||
|
|||||||
@@ -12,10 +12,7 @@ class HTTPDispatcher:
|
|||||||
dispatch_url = os.environ.get("KUBEHUNTER_HTTP_DISPATCH_URL", "https://localhost/")
|
dispatch_url = os.environ.get("KUBEHUNTER_HTTP_DISPATCH_URL", "https://localhost/")
|
||||||
try:
|
try:
|
||||||
r = requests.request(
|
r = requests.request(
|
||||||
dispatch_method,
|
dispatch_method, dispatch_url, json=report, headers={"Content-Type": "application/json"}, verify=False
|
||||||
dispatch_url,
|
|
||||||
json=report,
|
|
||||||
headers={"Content-Type": "application/json"},
|
|
||||||
)
|
)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
logger.info(f"Report was dispatched to: {dispatch_url}")
|
logger.info(f"Report was dispatched to: {dispatch_url}")
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class PlainReporter(BaseReporter):
|
|||||||
column_names = [
|
column_names = [
|
||||||
"ID",
|
"ID",
|
||||||
"Location",
|
"Location",
|
||||||
"Category",
|
"MITRE Category",
|
||||||
"Vulnerability",
|
"Vulnerability",
|
||||||
"Description",
|
"Description",
|
||||||
"Evidence",
|
"Evidence",
|
||||||
@@ -91,7 +91,7 @@ class PlainReporter(BaseReporter):
|
|||||||
vuln_table = PrettyTable(column_names, hrules=ALL)
|
vuln_table = PrettyTable(column_names, hrules=ALL)
|
||||||
vuln_table.align = "l"
|
vuln_table.align = "l"
|
||||||
vuln_table.max_width = MAX_TABLE_WIDTH
|
vuln_table.max_width = MAX_TABLE_WIDTH
|
||||||
vuln_table.sortby = "Category"
|
vuln_table.sortby = "MITRE Category"
|
||||||
vuln_table.reversesort = True
|
vuln_table.reversesort = True
|
||||||
vuln_table.padding_width = 1
|
vuln_table.padding_width = 1
|
||||||
vuln_table.header_style = "upper"
|
vuln_table.header_style = "upper"
|
||||||
@@ -101,10 +101,11 @@ class PlainReporter(BaseReporter):
|
|||||||
evidence = str(vuln.evidence)
|
evidence = str(vuln.evidence)
|
||||||
if len(evidence) > EVIDENCE_PREVIEW:
|
if len(evidence) > EVIDENCE_PREVIEW:
|
||||||
evidence = evidence[:EVIDENCE_PREVIEW] + "..."
|
evidence = evidence[:EVIDENCE_PREVIEW] + "..."
|
||||||
|
|
||||||
row = [
|
row = [
|
||||||
vuln.get_vid(),
|
vuln.get_vid(),
|
||||||
vuln.location(),
|
vuln.location(),
|
||||||
vuln.category.name,
|
vuln.category.get_name(),
|
||||||
vuln.get_name(),
|
vuln.get_name(),
|
||||||
vuln.explain(),
|
vuln.explain(),
|
||||||
evidence,
|
evidence,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
-r requirements.txt
|
|
||||||
|
|
||||||
flake8
|
flake8
|
||||||
pytest >= 2.9.1
|
pytest >= 2.9.1
|
||||||
requests-mock >= 1.8
|
requests-mock >= 1.8
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ packages = find:
|
|||||||
install_requires =
|
install_requires =
|
||||||
netaddr
|
netaddr
|
||||||
netifaces
|
netifaces
|
||||||
scapy>=2.4.3
|
|
||||||
requests
|
requests
|
||||||
PrettyTable
|
PrettyTable
|
||||||
urllib3>=1.24.3
|
urllib3>=1.24.3
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# flake8: noqa: E402
|
# flake8: noqa: E402
|
||||||
|
|
||||||
from kube_hunter.conf import Config, set_config
|
from kube_hunter.conf import Config, set_config, get_config
|
||||||
|
|
||||||
set_config(Config(active=True))
|
set_config(Config(active=True))
|
||||||
|
|
||||||
@@ -20,12 +20,12 @@ from kube_hunter.modules.hunting.apiserver import (
|
|||||||
AccessApiServerActive,
|
AccessApiServerActive,
|
||||||
AccessApiServerWithToken,
|
AccessApiServerWithToken,
|
||||||
)
|
)
|
||||||
from kube_hunter.modules.hunting.arp import ArpSpoofHunter
|
|
||||||
from kube_hunter.modules.hunting.capabilities import PodCapabilitiesHunter
|
from kube_hunter.modules.hunting.capabilities import PodCapabilitiesHunter
|
||||||
from kube_hunter.modules.hunting.certificates import CertificateDiscovery
|
from kube_hunter.modules.hunting.certificates import CertificateDiscovery
|
||||||
from kube_hunter.modules.hunting.cves import K8sClusterCveHunter, KubectlCVEHunter
|
|
||||||
|
from kube_hunter.modules.hunting.cves import K8sClusterCveHunter
|
||||||
|
from kube_hunter.modules.hunting.cves import KubectlCVEHunter
|
||||||
from kube_hunter.modules.hunting.dashboard import KubeDashboard
|
from kube_hunter.modules.hunting.dashboard import KubeDashboard
|
||||||
from kube_hunter.modules.hunting.dns import DnsSpoofHunter
|
|
||||||
from kube_hunter.modules.hunting.etcd import EtcdRemoteAccess, EtcdRemoteAccessActive
|
from kube_hunter.modules.hunting.etcd import EtcdRemoteAccess, EtcdRemoteAccessActive
|
||||||
from kube_hunter.modules.hunting.kubelet import (
|
from kube_hunter.modules.hunting.kubelet import (
|
||||||
ProveAnonymousAuth,
|
ProveAnonymousAuth,
|
||||||
@@ -40,6 +40,8 @@ from kube_hunter.modules.hunting.mounts import VarLogMountHunter, ProveVarLogMou
|
|||||||
from kube_hunter.modules.hunting.proxy import KubeProxy, ProveProxyExposed, K8sVersionDisclosureProve
|
from kube_hunter.modules.hunting.proxy import KubeProxy, ProveProxyExposed, K8sVersionDisclosureProve
|
||||||
from kube_hunter.modules.hunting.secrets import AccessSecrets
|
from kube_hunter.modules.hunting.secrets import AccessSecrets
|
||||||
|
|
||||||
|
config = get_config()
|
||||||
|
|
||||||
PASSIVE_HUNTERS = {
|
PASSIVE_HUNTERS = {
|
||||||
ApiServiceDiscovery,
|
ApiServiceDiscovery,
|
||||||
KubeDashboardDiscovery,
|
KubeDashboardDiscovery,
|
||||||
@@ -56,7 +58,6 @@ PASSIVE_HUNTERS = {
|
|||||||
ApiVersionHunter,
|
ApiVersionHunter,
|
||||||
PodCapabilitiesHunter,
|
PodCapabilitiesHunter,
|
||||||
CertificateDiscovery,
|
CertificateDiscovery,
|
||||||
K8sClusterCveHunter,
|
|
||||||
KubectlCVEHunter,
|
KubectlCVEHunter,
|
||||||
KubeDashboard,
|
KubeDashboard,
|
||||||
EtcdRemoteAccess,
|
EtcdRemoteAccess,
|
||||||
@@ -67,11 +68,12 @@ PASSIVE_HUNTERS = {
|
|||||||
AccessSecrets,
|
AccessSecrets,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# if config.enable_cve_hunting:
|
||||||
|
# PASSIVE_HUNTERS.append(K8sClusterCveHunter)
|
||||||
|
|
||||||
ACTIVE_HUNTERS = {
|
ACTIVE_HUNTERS = {
|
||||||
ProveAzureSpnExposure,
|
ProveAzureSpnExposure,
|
||||||
AccessApiServerActive,
|
AccessApiServerActive,
|
||||||
ArpSpoofHunter,
|
|
||||||
DnsSpoofHunter,
|
|
||||||
EtcdRemoteAccessActive,
|
EtcdRemoteAccessActive,
|
||||||
ProveRunHandler,
|
ProveRunHandler,
|
||||||
ProveContainerLogsHandler,
|
ProveContainerLogsHandler,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# flake8: noqa: E402
|
# flake8: noqa: E402
|
||||||
|
from kube_hunter.core.types.vulnerabilities import AccessK8sApiServerTechnique
|
||||||
import requests_mock
|
import requests_mock
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ from kube_hunter.modules.hunting.apiserver import (
|
|||||||
from kube_hunter.modules.hunting.apiserver import ApiServerPassiveHunterFinished
|
from kube_hunter.modules.hunting.apiserver import ApiServerPassiveHunterFinished
|
||||||
from kube_hunter.modules.hunting.apiserver import CreateANamespace, DeleteANamespace
|
from kube_hunter.modules.hunting.apiserver import CreateANamespace, DeleteANamespace
|
||||||
from kube_hunter.modules.discovery.apiserver import ApiServer
|
from kube_hunter.modules.discovery.apiserver import ApiServer
|
||||||
from kube_hunter.core.types import UnauthenticatedAccess, InformationDisclosure
|
from kube_hunter.core.types import ExposedSensitiveInterfacesTechnique, AccessK8sApiServerTechnique
|
||||||
from kube_hunter.core.events import handler
|
from kube_hunter.core.events import handler
|
||||||
|
|
||||||
counter = 0
|
counter = 0
|
||||||
@@ -181,10 +182,10 @@ class test_ListClusterRoles:
|
|||||||
class test_ServerApiAccess:
|
class test_ServerApiAccess:
|
||||||
def __init__(self, event):
|
def __init__(self, event):
|
||||||
print("ServerApiAccess")
|
print("ServerApiAccess")
|
||||||
if event.category == UnauthenticatedAccess:
|
if event.category == ExposedSensitiveInterfacesTechnique:
|
||||||
assert event.auth_token is None
|
assert event.auth_token is None
|
||||||
else:
|
else:
|
||||||
assert event.category == InformationDisclosure
|
assert event.category == AccessK8sApiServerTechnique
|
||||||
assert event.auth_token == "so-secret"
|
assert event.auth_token == "so-secret"
|
||||||
global counter
|
global counter
|
||||||
counter += 1
|
counter += 1
|
||||||
|
|||||||
Reference in New Issue
Block a user