mirror of
https://github.com/aquasecurity/kube-hunter.git
synced 2026-05-13 12:47:20 +00:00
1. Added an --active flag, to allow optional "Proof" result, which will do an active hunting of a found vulnerability
2. Added a --remote flag to specify remote clusters/machines for hunting. 3. Improved a bit of the architecture, (Services) Note: The reporter module, will gather vulnerabilities before their active hunting will start. This is not an issue, as we can access all of the attributes of the event directly from the active hunter (event.previous), which we will proccess on the end in the report
This commit is contained in:
@@ -6,8 +6,10 @@ import sys
|
||||
import time
|
||||
|
||||
parser = argparse.ArgumentParser(description='Kube-Hunter, Hunter for weak Kubernetes cluster')
|
||||
parser.add_argument('--pod', action="store_true", help="set hunter as an insider pod")
|
||||
parser.add_argument('--remote', nargs='+', metavar="LOGLEVEL", help="one or more remote ip/dns to hunt")
|
||||
parser.add_argument('--active', action="store_true", help="enables active hunting")
|
||||
parser.add_argument('--log', type=str, metavar="LOGLEVEL", default='INFO', help="set log level, options are:\nDEBUG INFO WARNING")
|
||||
parser.add_argument('--pod', action="store_true", help="When set, will scan the cluster as a pod, when unset, will scan all network interfaces")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
loglevel = getattr(logging, args.log.upper())
|
||||
@@ -25,7 +27,7 @@ from modules.discovery.hosts import HostScanEvent
|
||||
def main():
|
||||
logging.info("Started")
|
||||
try:
|
||||
handler.publish_event(HostScanEvent(pod=args.pod, active=True))
|
||||
handler.publish_event(HostScanEvent(pod=args.pod, active=args.active, predefined_hosts=args.remote))
|
||||
# Blocking to see discovery output
|
||||
while(True):
|
||||
time.sleep(100)
|
||||
@@ -34,7 +36,7 @@ def main():
|
||||
finally:
|
||||
handler.free()
|
||||
logging.debug("Cleaned Queue")
|
||||
log.print_results()
|
||||
log.print_results(args.active)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -6,7 +6,6 @@ from modules.discovery.kubelet import KubeletExposedHandler
|
||||
|
||||
services = list()
|
||||
vulnerabilities = list()
|
||||
informations = list()
|
||||
|
||||
@handler.subscribe(Vulnerability)
|
||||
class VulnerabilityReport(object):
|
||||
@@ -15,23 +14,11 @@ class VulnerabilityReport(object):
|
||||
|
||||
def execute(self):
|
||||
logging.info("[VULNERABILITY - {name}] {desc}".format(
|
||||
name=self.vulnerability.name,
|
||||
name=self.vulnerability.get_name(),
|
||||
desc=self.vulnerability.explain(),
|
||||
))
|
||||
vulnerabilities.append(self.vulnerability)
|
||||
|
||||
@handler.subscribe(Information)
|
||||
class ClusterInformation(object):
|
||||
def __init__(self, event):
|
||||
self.information = event
|
||||
|
||||
def execute(self):
|
||||
logging.info("[INFORMATION - {name}] {desc}".format(
|
||||
name=self.information.get_name(),
|
||||
desc=self.information.explain(),
|
||||
))
|
||||
informations.append(self.information)
|
||||
|
||||
@handler.subscribe(Service)
|
||||
class OpenServiceReport(object):
|
||||
def __init__(self, event):
|
||||
@@ -46,16 +33,19 @@ class OpenServiceReport(object):
|
||||
))
|
||||
services.append(self.service)
|
||||
|
||||
|
||||
|
||||
def print_results():
|
||||
def print_results(active):
|
||||
services_table = PrettyTable(["Service", "Location", "Description"])
|
||||
for service in services:
|
||||
services_table.add_row([service.get_name(), "{}:{}".format(service.host, service.port), service.explain()])
|
||||
services_table.add_row([service.get_name(), "{}:{}{}".format(service.host, service.port, service.get_path()), service.explain()])
|
||||
|
||||
vuln_table = PrettyTable(["Location", "From Component", "Vulnerability", "Description"])
|
||||
column_names = ["Location", "From Component", "Vulnerability", "Description"]
|
||||
if active: column_names.append("Proof")
|
||||
|
||||
vuln_table = PrettyTable(column_names)
|
||||
for vuln in vulnerabilities:
|
||||
vuln_table.add_row(["{}:{}".format(vuln.host, vuln.port), vuln.component.name, vuln.get_name(), vuln.explain()])
|
||||
row = ["{}:{}".format(vuln.host, vuln.port), vuln.component.name, vuln.get_name(), vuln.explain()]
|
||||
if active: row.append(vuln.attrs)
|
||||
vuln_table.add_row(row)
|
||||
|
||||
print "\nOpen Services:"
|
||||
print services_table
|
||||
|
||||
@@ -8,10 +8,8 @@ from ..types import Hunter
|
||||
|
||||
class KubeDashboardEvent(Service, Event):
|
||||
"""Allows multiple arbitrary operations on the cluster from all connections"""
|
||||
def __init__(self, path="/", secure=False):
|
||||
self.path = path
|
||||
self.secure
|
||||
Service.__init__(self, name="Kubernetes Dashboard")
|
||||
def __init__(self, **kargs):
|
||||
Service.__init__(self, name="Kubernetes Dashboard", **kargs)
|
||||
|
||||
@handler.subscribe(OpenPortEvent, predicate=lambda x: x.port == 30000)
|
||||
class KubeDashboard(Hunter):
|
||||
|
||||
@@ -13,9 +13,10 @@ from ..events.types import Event, NewHostEvent
|
||||
from ..types import Hunter
|
||||
|
||||
class HostScanEvent(Event):
|
||||
def __init__(self, pod=False, active=False):
|
||||
def __init__(self, pod=False, active=False, predefined_hosts=list()):
|
||||
self.pod = pod
|
||||
self.active = active # flag to specify whether to get actual data from vulnerabilities
|
||||
self.predefined_hosts = predefined_hosts
|
||||
self.auth_token = self.get_auth_token()
|
||||
self.client_cert = self.get_client_cert()
|
||||
|
||||
@@ -37,14 +38,17 @@ class HostDiscovery(Hunter):
|
||||
|
||||
def execute(self):
|
||||
logging.info("Discovering Open Kubernetes Services...")
|
||||
|
||||
if self.event.pod:
|
||||
if self.is_azure_cluster():
|
||||
self.azure_metadata_discovery()
|
||||
else:
|
||||
self.traceroute_discovery()
|
||||
else:
|
||||
# self.publish_event(NewHostEvent(host="acs954agent1.westus2.cloudapp.azure.com")) # test cluster
|
||||
elif len(self.event.predefined_hosts) == 0:
|
||||
self.scan_interfaces()
|
||||
else:
|
||||
for host in self.event.predefined_hosts:
|
||||
self.publish_event(NewHostEvent(host=host))
|
||||
|
||||
def is_azure_cluster(self):
|
||||
try:
|
||||
|
||||
@@ -23,6 +23,7 @@ class SecureKubeletEvent(Service, Event):
|
||||
self.token = token
|
||||
Service.__init__(self, name="Kubelet API")
|
||||
|
||||
|
||||
""" Vulnerabilities """
|
||||
class PodsHandler:
|
||||
"""Exposes sensitive information about pods that are bound to the node"""
|
||||
@@ -47,6 +48,12 @@ class AnonymousAuthEnabled(Vulnerability, Event):
|
||||
def proof(self):
|
||||
pass # TODO: decide on an appropriate proof
|
||||
|
||||
|
||||
|
||||
class KubeletPorts(Enum):
|
||||
SECURED = 10250
|
||||
READ_ONLY = 10255
|
||||
|
||||
@handler.subscribe(OpenPortEvent, predicate= lambda x: x.port == 10255 or x.port == 10250)
|
||||
class KubeletDiscovery(Hunter):
|
||||
def __init__(self, event):
|
||||
@@ -89,9 +96,4 @@ class KubeletDiscovery(Hunter):
|
||||
if self.event.port == KubeletPorts.SECURED.value:
|
||||
self.get_secure_access()
|
||||
elif self.event.port == KubeletPorts.READ_ONLY.value:
|
||||
self.get_read_only_access()
|
||||
|
||||
|
||||
class KubeletPorts(Enum):
|
||||
SECURED = 10250
|
||||
READ_ONLY = 10255
|
||||
self.get_read_only_access()
|
||||
@@ -8,6 +8,7 @@ from ..events import handler
|
||||
from ..events.types import Service, Event, OpenPortEvent
|
||||
|
||||
class KubeProxyEvent(Event, Service):
|
||||
"""proxies from a localhost address to the Kubernetes apiserver"""
|
||||
def __init__(self):
|
||||
Service.__init__(self, name="Kubernetes Proxy")
|
||||
|
||||
|
||||
@@ -35,12 +35,17 @@ class Kubelet(KubernetesCluster):
|
||||
""" Event Types """
|
||||
# TODO: make proof an abstract method.
|
||||
class Service(object):
|
||||
def __init__(self, name):
|
||||
def __init__(self, name, path="", secure=False):
|
||||
self.name = name
|
||||
self.secure = secure
|
||||
self.path = path
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
||||
def get_path(self):
|
||||
return "/" + self.path if self.path else ""
|
||||
|
||||
def explain(self):
|
||||
return self.__doc__
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
class ContainerLogsHandler:
|
||||
"""Outputs logs from a running container"""
|
||||
name="/containerlogs"
|
||||
name="/containerLogs"
|
||||
remediation="--enable-debugging-handlers=False On Kubelet"
|
||||
|
||||
class RunningPodsHandler:
|
||||
@@ -248,24 +248,37 @@ class SecureKubeletPortHunter(Hunter):
|
||||
|
||||
|
||||
""" Active Hunting Of Handlers"""
|
||||
@handler.subscribe(KubeletExposedHandler, predicate=lambda x: x.handler=="exec" and x.active)
|
||||
@handler.subscribe(KubeletExposedHandler, predicate=lambda x: x.handler.name==ExecHandler.name and x.active)
|
||||
class ActiveExecHandler(Hunter):
|
||||
def __init__(self, event):
|
||||
self.event = Event
|
||||
self.event = event
|
||||
|
||||
def execute(self):
|
||||
pass
|
||||
|
||||
@handler.subscribe(KubeletExposedHandler, predicate=lambda x: x.handler=="run" and x.active)
|
||||
|
||||
@handler.subscribe(KubeletExposedHandler, predicate=lambda x: x.handler.name==RunHandler.name and x.active)
|
||||
class ActiveRunHandler(Hunter):
|
||||
def __init__(self, event):
|
||||
self.event = Event
|
||||
self.event = event
|
||||
|
||||
def execute(self):
|
||||
pass
|
||||
|
||||
@handler.subscribe(KubeletExposedHandler, predicate=lambda x: x.handler.name==ContainerLogsHandler.name and x.active)
|
||||
class ActiveContainerLogs(Hunter):
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
|
||||
def execute(self):
|
||||
pass
|
||||
|
||||
@handler.subscribe(KubeletExposedHandler, predicate=lambda x: x.handler.name==AttachHandler.name and x.active)
|
||||
class ActiveContainerLogs(Hunter):
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
|
||||
def execute(self):
|
||||
pass
|
||||
# def get_kubesystem_pod_container(self):
|
||||
# pods_data = json.loads(requests.get("https://{host}:{port}/pods".format(host=self.event.host, port=self.event.port), verify=False).text)['items']
|
||||
# # filter running kubesystem pod
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
import logging
|
||||
from enum import Enum
|
||||
from ..types import Hunter
|
||||
|
||||
from requests import get
|
||||
|
||||
from ..events import handler
|
||||
from ..discovery.dashboard import KubeDashboardEvent
|
||||
from ..discovery.proxy import KubeProxyEvent
|
||||
from ..events import handler
|
||||
from ..events.types import Vulnerability, Event, KubernetesCluster
|
||||
from ..types import Hunter
|
||||
|
||||
|
||||
class Service(Enum):
|
||||
DASHBOARD = "kubernetes-dashboard"
|
||||
|
||||
class KubeProxyExposed(Vulnerability, Event):
|
||||
"""Exposes all oprations on the cluster"""
|
||||
def __init__(self):
|
||||
Vulnerability.__init__(self, KubernetesCluster, "Proxy Exposed")
|
||||
|
||||
@handler.subscribe(KubeProxyEvent)
|
||||
class KubeProxy(Hunter):
|
||||
def __init__(self, event):
|
||||
@@ -17,10 +25,12 @@ class KubeProxy(Hunter):
|
||||
self.api_url = "http://{host}:{port}/api/v1".format(host=self.event.host, port=self.event.port)
|
||||
|
||||
def execute(self):
|
||||
self.publish_event(KubeProxyExposed())
|
||||
for namespace, services in self.services.items():
|
||||
for service in services:
|
||||
curr_path = "api/v1/namespaces/{ns}/services/{sv}/proxy".format(ns=namespace,sv=service) # TODO: check if /proxy is a convention on other services
|
||||
if service == Service.DASHBOARD:
|
||||
if service == Service.DASHBOARD.value:
|
||||
logging.debug(service)
|
||||
curr_path = "api/v1/namespaces/{ns}/services/{sv}/proxy".format(ns=namespace,sv=service) # TODO: check if /proxy is a convention on other services
|
||||
self.publish_event(KubeDashboardEvent(path=curr_path, secure=False))
|
||||
|
||||
@property
|
||||
@@ -36,6 +46,7 @@ class KubeProxy(Hunter):
|
||||
resource_path = "/namespaces/{ns}/services".format(ns=namespace)
|
||||
resource_json = get(self.api_url + resource_path).json()
|
||||
services[namespace] = self.extract_names(resource_json)
|
||||
logging.debug(services)
|
||||
return services
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user