diff --git a/.gitignore b/.gitignore index c6c78a9..5cb60b1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *aqua* venv/ .vscode +.coverage .idea # Directory Cache Files diff --git a/.travis.yml b/.travis.yml index 9246e9e..55c51f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,11 @@ before_script: - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - pip install pytest coverage pytest-cov script: - python runtest.py +after_success: + - bash <(curl -s https://codecov.io/bash) notifications: on_success: change on_failure: change # `always` will be the setting once code changes slow down diff --git a/README.md b/README.md index bcf1b11..87df0dd 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,18 @@ ![kube-hunter](https://github.com/aquasecurity/kube-hunter/blob/master/kube-hunter.png) [![Build Status](https://travis-ci.org/aquasecurity/kube-hunter.svg?branch=master)](https://travis-ci.org/aquasecurity/kube-hunter) +[![codecov](https://codecov.io/gh/aquasecurity/kube-hunter/branch/master/graph/badge.svg)](https://codecov.io/gh/aquasecurity/kube-hunter) [![License](https://img.shields.io/github/license/aquasecurity/kube-hunter)](https://github.com/aquasecurity/kube-hunter/blob/master/LICENSE) [![Docker image](https://images.microbadger.com/badges/image/aquasec/kube-hunter.svg)](https://microbadger.com/images/aquasec/kube-hunter "Get your own image badge on microbadger.com") + kube-hunter hunts for security weaknesses in Kubernetes clusters. The tool was developed to increase awareness and visibility for security issues in Kubernetes environments. **You should NOT run kube-hunter on a Kubernetes cluster that you don't own!** **Run kube-hunter**: kube-hunter is available as a container (aquasec/kube-hunter), and we also offer a web site at [kube-hunter.aquasec.com](https://kube-hunter.aquasec.com) where you can register online to receive a token allowing you to see and share the results online. You can also run the Python code yourself as described below. +**Explore vulnerabilities**: The kube-hunter knowledge base includes articles about discoverable vulnerabilities and issues. When kube-hunter reports an issue, it will show its VID (Vulnerability ID) so you can look it up in the KB at https://aquasecurity.github.io/kube-hunter/ + **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](src/README.md). [![kube-hunter demo video](https://github.com/aquasecurity/kube-hunter/blob/master/kube-hunter-screenshot.png)](https://youtu.be/s2-6rTkH8a8?t=57s) @@ -146,7 +150,8 @@ By default, kube-hunter runs in interactive mode. You can also specify the scann ### Pod This option lets you discover what running a malicious container can do/discover on your cluster. This gives a perspective on what an attacker could do if they were able to compromise a pod, perhaps through a software vulnerability. This may reveal significantly more vulnerabilities. -The `job.yaml` file defines a Job that will run kube-hunter in a pod, using default Kubernetes pod access settings. +The example `job.yaml` file defines a Job that will run kube-hunter in a pod, using default Kubernetes pod access settings. (You may wish to modify this definition, for example to run as a non-root user, or to run in a different namespace.) + * Run the job with `kubectl create -f ./job.yaml` * Find the pod name with `kubectl describe job kube-hunter` * View the test results with `kubectl logs ` diff --git a/docs/_kb/KHV037.md b/docs/_kb/KHV037.md index 170e7d3..6392be2 100644 --- a/docs/_kb/KHV037.md +++ b/docs/_kb/KHV037.md @@ -8,7 +8,7 @@ categories: [Information Disclosure] ## Issue description -The kubelet is is leaking container logs via the `/containerLogs` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. +The kubelet is leaking container logs via the `/containerLogs` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. ## Remediation diff --git a/docs/_kb/KHV038.md b/docs/_kb/KHV038.md index 97f3758..f16d49a 100644 --- a/docs/_kb/KHV038.md +++ b/docs/_kb/KHV038.md @@ -8,7 +8,7 @@ categories: [Information Disclosure] ## Issue description -The kubelet is is leaking information about runnig pods via the `/runningpods` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. +The kubelet is leaking information about running pods via the `/runningpods` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. ## Remediation diff --git a/docs/_kb/KHV039.md b/docs/_kb/KHV039.md index ae796b0..7478afa 100644 --- a/docs/_kb/KHV039.md +++ b/docs/_kb/KHV039.md @@ -8,7 +8,7 @@ categories: [Remote Code Execution] ## Issue description -An attacker could run arbitrary commands on a container via the the kubelet's `/exec` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. +An attacker could run arbitrary commands on a container via the kubelet's `/exec` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. ## Remediation diff --git a/docs/_kb/KHV040.md b/docs/_kb/KHV040.md index aae3a8c..43ebd7d 100644 --- a/docs/_kb/KHV040.md +++ b/docs/_kb/KHV040.md @@ -8,7 +8,7 @@ categories: [Remote Code Execution] ## Issue description -An attacker could run arbitrary commands on a container via the the kubelet's `/run` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. +An attacker could run arbitrary commands on a container via the kubelet's `/run` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. ## Remediation diff --git a/docs/_kb/KHV041.md b/docs/_kb/KHV041.md index 1e02312..2d526e9 100644 --- a/docs/_kb/KHV041.md +++ b/docs/_kb/KHV041.md @@ -8,7 +8,7 @@ categories: [Remote Code Execution] ## Issue description -An attacker could read and write data from a pod via the the kubelet's `/portForward` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. +An attacker could read and write data from a pod via the kubelet's `/portForward` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. ## Remediation diff --git a/docs/_kb/KHV042.md b/docs/_kb/KHV042.md index 5fcc373..1aa0d23 100644 --- a/docs/_kb/KHV042.md +++ b/docs/_kb/KHV042.md @@ -8,7 +8,7 @@ categories: [Remote Code Execution] ## Issue description -An attacker could attach to a running container via a websocket on the the the kubelet's `/attach` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. +An attacker could attach to a running container via a websocket on the kubelet's `/attach` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. ## Remediation diff --git a/docs/_kb/KHV043.md b/docs/_kb/KHV043.md index da2a1f0..26481db 100644 --- a/docs/_kb/KHV043.md +++ b/docs/_kb/KHV043.md @@ -8,7 +8,7 @@ categories: [Information Disclosure] ## Issue description -The kubelet is is leaking it's health information, which may contain sensitive information, via the `/healthz` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. +The kubelet is leaking it's health information, which may contain sensitive information, via the `/healthz` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. ## Remediation diff --git a/docs/_kb/KHV045.md b/docs/_kb/KHV045.md index ecacd3b..9b0cceb 100644 --- a/docs/_kb/KHV045.md +++ b/docs/_kb/KHV045.md @@ -8,7 +8,7 @@ categories: [Information Disclosure] ## Issue description -The kubelet is is leaking system logs via the `/logs` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. +The kubelet is leaking system logs via the `/logs` endpoint. This endpoint is exposed as part of the kubelet's debug handlers. ## Remediation diff --git a/docs/_kb/KHV049.md b/docs/_kb/KHV049.md index 5a297f2..8c0eb90 100644 --- a/docs/_kb/KHV049.md +++ b/docs/_kb/KHV049.md @@ -8,7 +8,7 @@ categories: [Information Disclosure] ## Issue description -An open kubectl proxy was detected. `kubectl proxy` is a convenient tool to connent from a local machine into an application running in Kubernetes or to the Kubernetes API. This is common practice to browse for example the Kubernetes dashboard. Leaving an open proxy can be exploited by an attacker to gain access into your entire cluster. +An open kubectl proxy was detected. `kubectl proxy` is a convenient tool to connect from a local machine into an application running in Kubernetes or to the Kubernetes API. This is common practice to browse for example the Kubernetes dashboard. Leaving an open proxy can be exploited by an attacker to gain access into your entire cluster. ## Remediation diff --git a/kube-hunter.py b/kube-hunter.py index 5869609..1de9684 100755 --- a/kube-hunter.py +++ b/kube-hunter.py @@ -9,7 +9,7 @@ parser.add_argument('--list', action="store_true", help="displays all tests in k parser.add_argument('--interface', action="store_true", help="set hunting of all network interfaces") parser.add_argument('--pod', action="store_true", help="set hunter as an insider pod") parser.add_argument('--quick', action="store_true", help="Prefer quick scan (subnet 24)") -parser.add_argument('--ignore-downstream', action="store_true", help="Ignore patched kubernetes versions") +parser.add_argument('--include-patched-versions', action="store_true", help="Don't skip patched versions when scanning") parser.add_argument('--cidr', type=str, help="set an ip range to scan, example: 192.168.0.0/16") parser.add_argument('--mapping', action="store_true", help="outputs only a mapping of the cluster's nodes") parser.add_argument('--remote', nargs='+', metavar="HOST", default=list(), help="one or more remote ip/dns to hunt") diff --git a/plugins/logging_mod.py b/plugins/logging_mod.py index 168e4a9..54ec930 100644 --- a/plugins/logging_mod.py +++ b/plugins/logging_mod.py @@ -1,5 +1,5 @@ import logging -# Supress logging from scapy +# Suppress logging from scapy logging.getLogger("scapy.runtime").setLevel(logging.CRITICAL) logging.getLogger("scapy.loading").setLevel(logging.CRITICAL) diff --git a/runtest.py b/runtest.py index fe7619d..11c5976 100644 --- a/runtest.py +++ b/runtest.py @@ -6,7 +6,7 @@ parser.add_argument('--list', action="store_true", help="displays all tests in k parser.add_argument('--interface', action="store_true", help="set hunting of all interface network interfaces") parser.add_argument('--pod', action="store_true", help="set hunter as an insider pod") parser.add_argument('--quick', action="store_true", help="Prefer quick scan (subnet 24)") -parser.add_argument('--ignore-downstream', action="store_true", help="Ignore patched kubernetes versions") +parser.add_argument('--include-patched-versions', action="store_true", help="Don't skip patched versions when scanning") parser.add_argument('--cidr', type=str, help="set an ip range to scan, example: 192.168.0.0/16") parser.add_argument('--mapping', action="store_true", help="outputs only a mapping of the cluster's nodes") parser.add_argument('--remote', nargs='+', metavar="HOST", default=list(), help="one or more remote ip/dns to hunt") diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..66296ed --- /dev/null +++ b/setup.cfg @@ -0,0 +1,33 @@ +[aliases] +test=pytest + +# PyTest +[tool:pytest] +minversion = 2.9.1 +norecursedirs = .venv .vscode +addopts = --cov=src +testpaths = tests +console_output_style = progress +python_classes = Test* +python_files = test_*.py +python_functions = test_* +filterwarnings = ignore::DeprecationWarning + +# Coverage +[coverage:report] +# show missing lines numbers +show_missing = True +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + # Don't complain if tests don't hit defensive + # assertion code: + raise AssertionError + raise NotImplementedError + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: diff --git a/src/README.md b/src/README.md index 4f6f640..e8b3ba1 100644 --- a/src/README.md +++ b/src/README.md @@ -25,7 +25,7 @@ When you write your module, you can decide on which Event to subscribe to, meani ----------------------- ### Hunter Types -There are three hunter types which you can implement: a `Hunter`, `ActiveHunter` and `Discovery`. Hunters just probe the state of a cluster, whereas ActiveHunter modules can attempt operations that could change the state of the cluster. Discovery is Hunter for discovery purposes only. +There are three hunter types which you can implement: a `Hunter`, `ActiveHunter` and `Discovery`. Hunters just probe the state of a cluster, whereas ActiveHunter modules can attempt operations that could change the state of the cluster. Discovery is Hunter for discovery purposes only. ##### Hunter Example: ~~~python diff --git a/src/core/events/types/common.py b/src/core/events/types/common.py index ea897aa..190db42 100644 --- a/src/core/events/types/common.py +++ b/src/core/events/types/common.py @@ -27,7 +27,7 @@ class Event(object): # Event's logical location to be used mainly for reports. # If event don't implement it check previous event # This is because events are composed (previous -> previous ...) - # and not inheritted + # and not inherited def location(self): location = None if self.previous: @@ -77,7 +77,7 @@ class Vulnerability(object): UnauthenticatedAccess: "low" }) - # TODO: make vid mandatry once migration is done + # TODO: make vid mandatory once migration is done def __init__(self, component, name, category=None, vid=None): self.vid = vid self.component = component @@ -154,7 +154,7 @@ class ReportDispatched(Event): pass -""" Core Vulnerabilites """ +""" Core Vulnerabilities """ class K8sVersionDisclosure(Vulnerability, Event): """The kubernetes version could be obtained from the {} endpoint """ def __init__(self, version, from_endpoint, extra_info=""): diff --git a/src/modules/hunting/arp.py b/src/modules/hunting/arp.py index ecd0fd4..f8545eb 100644 --- a/src/modules/hunting/arp.py +++ b/src/modules/hunting/arp.py @@ -26,7 +26,7 @@ class ArpSpoofHunter(ActiveHunter): return ans[ARP].hwsrc if ans else None def detect_l3_on_host(self, arp_responses): - """ returns True for an existance of an L3 network plugin """ + """ returns True for an existence of an L3 network plugin """ logging.debug("Attempting to detect L3 network plugin using ARP") unique_macs = list(set(response[ARP].hwsrc for _, response in arp_responses)) diff --git a/src/modules/hunting/cves.py b/src/modules/hunting/cves.py index 47c011c..f8c7b29 100644 --- a/src/modules/hunting/cves.py +++ b/src/modules/hunting/cves.py @@ -154,7 +154,7 @@ class K8sClusterCveHunter(Hunter): ServerApiClusterScopedResourcesAccess: ["1.13.9", "1.14.5", "1.15.2"] } for vulnerability, fix_versions in cve_mapping.items(): - if CveUtils.is_vulnerable(fix_versions, self.event.version, config.ignore_downstream): + if CveUtils.is_vulnerable(fix_versions, self.event.version, not config.include_patched_versions): self.publish_event(vulnerability(self.event.version)) @@ -173,5 +173,5 @@ class KubectlCVEHunter(Hunter): } logging.debug('Kubectl Cve Hunter determining vulnerable version: {}'.format(self.event.version)) for vulnerability, fix_versions in cve_mapping.items(): - if CveUtils.is_vulnerable(fix_versions, self.event.version, config.ignore_downstream): + if CveUtils.is_vulnerable(fix_versions, self.event.version, not config.include_patched_versions): self.publish_event(vulnerability(binary_version=self.event.version)) diff --git a/tests/hunting/test_cvehunting.py b/tests/hunting/test_cvehunting.py index 3c9ef70..d0ed2d3 100644 --- a/tests/hunting/test_cvehunting.py +++ b/tests/hunting/test_cvehunting.py @@ -20,16 +20,7 @@ def test_K8sCveHunter(): assert cve_counter == 2 cve_counter = 0 - # test complex version - e = K8sVersionDisclosure(version="1.10.1-gke-1", from_endpoint="/version") - h = K8sClusterCveHunter(e) - h.execute() - - time.sleep(0.01) - assert cve_counter == 2 - cve_counter = 0 - - # test complex version + # test patched version e = K8sVersionDisclosure(version="v1.13.6-gke.13", from_endpoint="/version") h = K8sClusterCveHunter(e) h.execute()