mirror of
https://github.com/aquasecurity/kube-hunter.git
synced 2026-02-25 23:34:18 +00:00
Compare commits
11 Commits
stop_using
...
improve_ak
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1e9516383 | ||
|
|
7f0697317e | ||
|
|
00db9cbdbe | ||
|
|
cb4c7199f3 | ||
|
|
1ea63ad17c | ||
|
|
f95df8172b | ||
|
|
a3ad928f29 | ||
|
|
22d6676e08 | ||
|
|
a2a4fad322 | ||
|
|
b9e0ef30e8 | ||
|
|
d00ef9031b |
14
.github/workflows/greetings.yml
vendored
14
.github/workflows/greetings.yml
vendored
@@ -1,14 +0,0 @@
|
||||
name: Greetings
|
||||
|
||||
on: [pull_request, issues]
|
||||
|
||||
jobs:
|
||||
greeting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/first-interaction@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: "Hola! @${{ github.actor }} 🥳 , You've just created an Issue!🌟 Thanks for making the Project Better"
|
||||
pr-message: 'Submitted a PR already ?? @${{ github.actor }} . Sit tight until one of our amazing maintainers review it. Make sure you read the contributing guide'
|
||||
|
||||
52
.github/workflows/release.yml
vendored
Normal file
52
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
on:
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
name: Upload Release Asset
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Upload Release Asset
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install -U pip
|
||||
python -m pip install -r requirements-dev.txt
|
||||
|
||||
- name: Build project
|
||||
shell: bash
|
||||
run: |
|
||||
make pyinstaller
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Upload Release Asset
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./dist/kube-hunter
|
||||
asset_name: kube-hunter-linux-x86_64-${{ github.ref }}
|
||||
asset_content_type: application/octet-stream
|
||||
21
.travis.yml
21
.travis.yml
@@ -1,21 +0,0 @@
|
||||
group: travis_latest
|
||||
language: python
|
||||
cache: pip
|
||||
python:
|
||||
- "3.6"
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
install:
|
||||
- pip install -r requirements.txt
|
||||
- pip install -r requirements-dev.txt
|
||||
before_script:
|
||||
- make lint-check
|
||||
script:
|
||||
- make test
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
notifications:
|
||||
email:
|
||||
on_success: change
|
||||
on_failure: always
|
||||
@@ -1,6 +1,6 @@
|
||||

|
||||
|
||||
[](https://travis-ci.org/aquasecurity/kube-hunter)
|
||||
[](https://github.com/aquasecurity/kube-hunter/actions)
|
||||
[](https://codecov.io/gh/aquasecurity/kube-hunter)
|
||||
[](https://github.com/psf/black)
|
||||
[](https://github.com/aquasecurity/kube-hunter/blob/master/LICENSE)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
|
||||
from kube_hunter.conf import get_config
|
||||
from kube_hunter.modules.hunting.kubelet import ExposedRunHandler
|
||||
from kube_hunter.modules.hunting.kubelet import ExposedPodsHandler, SecureKubeletPortHunter
|
||||
from kube_hunter.core.events import handler
|
||||
from kube_hunter.core.events.types import Event, Vulnerability
|
||||
from kube_hunter.core.types import Hunter, ActiveHunter, IdentityTheft, Azure
|
||||
@@ -14,7 +15,7 @@ logger = logging.getLogger(__name__)
|
||||
class AzureSpnExposure(Vulnerability, Event):
|
||||
"""The SPN is exposed, potentially allowing an attacker to gain access to the Azure subscription"""
|
||||
|
||||
def __init__(self, container):
|
||||
def __init__(self, container, evidence=""):
|
||||
Vulnerability.__init__(
|
||||
self,
|
||||
Azure,
|
||||
@@ -23,9 +24,10 @@ class AzureSpnExposure(Vulnerability, Event):
|
||||
vid="KHV004",
|
||||
)
|
||||
self.container = container
|
||||
self.evidence = evidence
|
||||
|
||||
|
||||
@handler.subscribe(ExposedRunHandler, predicate=lambda x: x.cloud == "Azure")
|
||||
@handler.subscribe(ExposedPodsHandler, predicate=lambda x: x.cloud_type == "Azure")
|
||||
class AzureSpnHunter(Hunter):
|
||||
"""AKS Hunting
|
||||
Hunting Azure cluster deployments using specific known configurations
|
||||
@@ -37,35 +39,33 @@ class AzureSpnHunter(Hunter):
|
||||
|
||||
# getting a container that has access to the azure.json file
|
||||
def get_key_container(self):
|
||||
config = get_config()
|
||||
endpoint = f"{self.base_url}/pods"
|
||||
logger.debug("Trying to find container with access to azure.json file")
|
||||
try:
|
||||
r = requests.get(endpoint, verify=False, timeout=config.network_timeout)
|
||||
except requests.Timeout:
|
||||
logger.debug("failed getting pod info")
|
||||
else:
|
||||
pods_data = r.json().get("items", [])
|
||||
suspicious_volume_names = []
|
||||
for pod_data in pods_data:
|
||||
for volume in pod_data["spec"].get("volumes", []):
|
||||
if volume.get("hostPath"):
|
||||
path = volume["hostPath"]["path"]
|
||||
if "/etc/kubernetes/azure.json".startswith(path):
|
||||
suspicious_volume_names.append(volume["name"])
|
||||
for container in pod_data["spec"]["containers"]:
|
||||
for mount in container.get("volumeMounts", []):
|
||||
if mount["name"] in suspicious_volume_names:
|
||||
return {
|
||||
"name": container["name"],
|
||||
"pod": pod_data["metadata"]["name"],
|
||||
"namespace": pod_data["metadata"]["namespace"],
|
||||
}
|
||||
|
||||
# pods are saved in the previous event object
|
||||
pods_data = self.event.pods
|
||||
|
||||
suspicious_volume_names = []
|
||||
for pod_data in pods_data:
|
||||
for volume in pod_data["spec"].get("volumes", []):
|
||||
if volume.get("hostPath"):
|
||||
path = volume["hostPath"]["path"]
|
||||
if "/etc/kubernetes/azure.json".startswith(path):
|
||||
suspicious_volume_names.append(volume["name"])
|
||||
for container in pod_data["spec"]["containers"]:
|
||||
for mount in container.get("volumeMounts", []):
|
||||
if mount["name"] in suspicious_volume_names:
|
||||
return {
|
||||
"name": container["name"],
|
||||
"pod": pod_data["metadata"]["name"],
|
||||
"namespace": pod_data["metadata"]["namespace"],
|
||||
"mount": mount,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
container = self.get_key_container()
|
||||
if container:
|
||||
self.publish_event(AzureSpnExposure(container=container))
|
||||
evidence = f"pod: {container['pod']}, namespace: {container['namespace']}"
|
||||
self.publish_event(AzureSpnExposure(container=container, evidence=evidence))
|
||||
|
||||
|
||||
@handler.subscribe(AzureSpnExposure)
|
||||
@@ -78,14 +78,42 @@ class ProveAzureSpnExposure(ActiveHunter):
|
||||
self.event = event
|
||||
self.base_url = f"https://{self.event.host}:{self.event.port}"
|
||||
|
||||
def test_run_capability(self):
|
||||
"""
|
||||
Uses SecureKubeletPortHunter to test the /run handler
|
||||
TODO: when multiple event subscription is implemented, use this here to make sure /run is accessible
|
||||
"""
|
||||
debug_handlers = SecureKubeletPortHunter.DebugHandlers(path=self.base_url, session=self.event.session, pod=None)
|
||||
return debug_handlers.test_run_container()
|
||||
|
||||
def run(self, command, container):
|
||||
config = get_config()
|
||||
run_url = "/".join(self.base_url, "run", container["namespace"], container["pod"], container["name"])
|
||||
return requests.post(run_url, verify=False, params={"cmd": command}, timeout=config.network_timeout)
|
||||
run_url = f"{self.base_url}/run/{container['namespace']}/{container['pod']}/{container['name']}"
|
||||
return self.event.session.post(run_url, verify=False, params={"cmd": command}, timeout=config.network_timeout)
|
||||
|
||||
def get_full_path_to_azure_file(self):
|
||||
"""
|
||||
Returns a full path to /etc/kubernetes/azure.json
|
||||
Taking into consideration the difference folder of the mount inside the container.
|
||||
TODO: implement the edge case where the mount is to parent /etc folder.
|
||||
"""
|
||||
azure_file_path = self.event.container["mount"]["mountPath"]
|
||||
|
||||
# taking care of cases where a subPath is added to map the specific file
|
||||
if not azure_file_path.endswith("azure.json"):
|
||||
azure_file_path = os.path.join(azure_file_path, "azure.json")
|
||||
|
||||
return azure_file_path
|
||||
|
||||
def execute(self):
|
||||
if not self.test_run_capability():
|
||||
logger.debug("Not proving AzureSpnExposure because /run debug handler is disabled")
|
||||
return
|
||||
|
||||
try:
|
||||
subscription = self.run("cat /etc/kubernetes/azure.json", container=self.event.container).json()
|
||||
azure_file_path = self.get_full_path_to_azure_file()
|
||||
logger.debug(f"trying to access the azure.json at the resolved path: {azure_file_path}")
|
||||
subscription = self.run(f"cat {azure_file_path}", container=self.event.container).json()
|
||||
except requests.Timeout:
|
||||
logger.debug("failed to run command in container", exc_info=True)
|
||||
except json.decoder.JSONDecodeError:
|
||||
|
||||
3
pyinstaller_hooks/hook-prettytable.py
Normal file
3
pyinstaller_hooks/hook-prettytable.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from PyInstaller.utils.hooks import collect_all
|
||||
|
||||
datas, binaries, hiddenimports = collect_all("prettytable")
|
||||
2
setup.py
2
setup.py
@@ -41,6 +41,8 @@ class PyInstallerCommand(Command):
|
||||
cfg.read("setup.cfg")
|
||||
command = [
|
||||
"pyinstaller",
|
||||
"--additional-hooks-dir",
|
||||
"pyinstaller_hooks",
|
||||
"--clean",
|
||||
"--onefile",
|
||||
"--name",
|
||||
|
||||
@@ -3,54 +3,47 @@ import requests_mock
|
||||
|
||||
from kube_hunter.conf import Config, set_config
|
||||
|
||||
import json
|
||||
|
||||
set_config(Config())
|
||||
|
||||
from kube_hunter.modules.hunting.kubelet import ExposedRunHandler
|
||||
from kube_hunter.modules.hunting.kubelet import ExposedPodsHandler
|
||||
from kube_hunter.modules.hunting.aks import AzureSpnHunter
|
||||
|
||||
|
||||
def test_AzureSpnHunter():
|
||||
e = ExposedRunHandler()
|
||||
e.host = "mockKubernetes"
|
||||
e.port = 443
|
||||
e.protocol = "https"
|
||||
|
||||
e = ExposedPodsHandler(pods=[])
|
||||
pod_template = '{{"items":[ {{"apiVersion":"v1","kind":"Pod","metadata":{{"name":"etc","namespace":"default"}},"spec":{{"containers":[{{"command":["sleep","99999"],"image":"ubuntu","name":"test","volumeMounts":[{{"mountPath":"/mp","name":"v"}}]}}],"volumes":[{{"hostPath":{{"path":"{}"}},"name":"v"}}]}}}} ]}}'
|
||||
|
||||
bad_paths = ["/", "/etc", "/etc/", "/etc/kubernetes", "/etc/kubernetes/azure.json"]
|
||||
good_paths = ["/yo", "/etc/yo", "/etc/kubernetes/yo.json"]
|
||||
|
||||
for p in bad_paths:
|
||||
with requests_mock.Mocker() as m:
|
||||
m.get("https://mockKubernetes:443/pods", text=pod_template.format(p))
|
||||
h = AzureSpnHunter(e)
|
||||
c = h.get_key_container()
|
||||
assert c
|
||||
e.pods = json.loads(pod_template.format(p))["items"]
|
||||
h = AzureSpnHunter(e)
|
||||
c = h.get_key_container()
|
||||
assert c
|
||||
|
||||
for p in good_paths:
|
||||
with requests_mock.Mocker() as m:
|
||||
m.get("https://mockKubernetes:443/pods", text=pod_template.format(p))
|
||||
h = AzureSpnHunter(e)
|
||||
c = h.get_key_container()
|
||||
assert c == None
|
||||
|
||||
with requests_mock.Mocker() as m:
|
||||
pod_no_volume_mounts = '{"items":[ {"apiVersion":"v1","kind":"Pod","metadata":{"name":"etc","namespace":"default"},"spec":{"containers":[{"command":["sleep","99999"],"image":"ubuntu","name":"test"}],"volumes":[{"hostPath":{"path":"/whatever"},"name":"v"}]}} ]}'
|
||||
m.get("https://mockKubernetes:443/pods", text=pod_no_volume_mounts)
|
||||
e.pods = json.loads(pod_template.format(p))["items"]
|
||||
h = AzureSpnHunter(e)
|
||||
c = h.get_key_container()
|
||||
assert c == None
|
||||
|
||||
with requests_mock.Mocker() as m:
|
||||
pod_no_volumes = '{"items":[ {"apiVersion":"v1","kind":"Pod","metadata":{"name":"etc","namespace":"default"},"spec":{"containers":[{"command":["sleep","99999"],"image":"ubuntu","name":"test"}]}} ]}'
|
||||
m.get("https://mockKubernetes:443/pods", text=pod_no_volumes)
|
||||
h = AzureSpnHunter(e)
|
||||
c = h.get_key_container()
|
||||
assert c == None
|
||||
pod_no_volume_mounts = '{"items":[ {"apiVersion":"v1","kind":"Pod","metadata":{"name":"etc","namespace":"default"},"spec":{"containers":[{"command":["sleep","99999"],"image":"ubuntu","name":"test"}],"volumes":[{"hostPath":{"path":"/whatever"},"name":"v"}]}} ]}'
|
||||
e.pods = json.loads(pod_no_volume_mounts)["items"]
|
||||
h = AzureSpnHunter(e)
|
||||
c = h.get_key_container()
|
||||
assert c == None
|
||||
|
||||
with requests_mock.Mocker() as m:
|
||||
pod_other_volume = '{"items":[ {"apiVersion":"v1","kind":"Pod","metadata":{"name":"etc","namespace":"default"},"spec":{"containers":[{"command":["sleep","99999"],"image":"ubuntu","name":"test","volumeMounts":[{"mountPath":"/mp","name":"v"}]}],"volumes":[{"emptyDir":{},"name":"v"}]}} ]}'
|
||||
m.get("https://mockKubernetes:443/pods", text=pod_other_volume)
|
||||
h = AzureSpnHunter(e)
|
||||
c = h.get_key_container()
|
||||
assert c == None
|
||||
pod_no_volumes = '{"items":[ {"apiVersion":"v1","kind":"Pod","metadata":{"name":"etc","namespace":"default"},"spec":{"containers":[{"command":["sleep","99999"],"image":"ubuntu","name":"test"}]}} ]}'
|
||||
e.pods = json.loads(pod_no_volumes)["items"]
|
||||
h = AzureSpnHunter(e)
|
||||
c = h.get_key_container()
|
||||
assert c == None
|
||||
|
||||
pod_other_volume = '{"items":[ {"apiVersion":"v1","kind":"Pod","metadata":{"name":"etc","namespace":"default"},"spec":{"containers":[{"command":["sleep","99999"],"image":"ubuntu","name":"test","volumeMounts":[{"mountPath":"/mp","name":"v"}]}],"volumes":[{"emptyDir":{},"name":"v"}]}} ]}'
|
||||
e.pods = json.loads(pod_other_volume)["items"]
|
||||
h = AzureSpnHunter(e)
|
||||
c = h.get_key_container()
|
||||
assert c == None
|
||||
|
||||
Reference in New Issue
Block a user