mirror of
https://github.com/aquasecurity/kube-hunter.git
synced 2026-05-11 11:47:15 +00:00
Merge branch 'master' into access-secrets-hunter
This commit is contained in:
@@ -41,7 +41,7 @@ class EventQueue(Queue, object):
|
||||
self.subscribe_event(event, hook=hook, predicate=predicate)
|
||||
return hook
|
||||
return wrapper
|
||||
|
||||
|
||||
# getting uninstantiated event object
|
||||
def subscribe_event(self, event, hook=None, predicate=None):
|
||||
if ActiveHunter in hook.__mro__:
|
||||
@@ -75,13 +75,13 @@ class EventQueue(Queue, object):
|
||||
hook = self.get()
|
||||
try:
|
||||
hook.execute()
|
||||
except Exception as ex:
|
||||
except Exception as ex:
|
||||
logging.debug(ex.message)
|
||||
self.task_done()
|
||||
logging.debug("closing thread...")
|
||||
|
||||
def notifier(self):
|
||||
time.sleep(2)
|
||||
time.sleep(2)
|
||||
while self.unfinished_tasks > 0:
|
||||
logging.debug("{} tasks left".format(self.unfinished_tasks))
|
||||
time.sleep(3)
|
||||
@@ -91,5 +91,5 @@ class EventQueue(Queue, object):
|
||||
self.running = False
|
||||
with self.mutex:
|
||||
self.queue.clear()
|
||||
|
||||
|
||||
handler = EventQueue(800)
|
||||
|
||||
28
src/modules/discovery/etcd.py
Normal file
28
src/modules/discovery/etcd.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
from ...core.events import handler
|
||||
from ...core.events.types import Event, OpenPortEvent, Service
|
||||
from ...core.types import Hunter
|
||||
|
||||
# Service:
|
||||
|
||||
class EtcdAccessEvent(Service, Event):
|
||||
"""Etcd is a DB that stores cluster's data, it contains configuration and current state information, and might contain secrets"""
|
||||
def __init__(self):
|
||||
Service.__init__(self, name="Etcd")
|
||||
|
||||
|
||||
|
||||
@handler.subscribe(OpenPortEvent, predicate= lambda p: p.port == 2379)
|
||||
class EtcdRemoteAccess(Hunter):
|
||||
"""Etcd Remote Access
|
||||
Checks for remote availability of etcd, version, read access, write access
|
||||
"""
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
|
||||
def execute(self):
|
||||
self.publish_event(EtcdAccessEvent())
|
||||
@@ -15,7 +15,6 @@ from ...core.events import handler
|
||||
from ...core.events.types import Event, NewHostEvent, Vulnerability
|
||||
from ...core.types import Hunter, InformationDisclosure, Azure
|
||||
|
||||
|
||||
class RunningAsPodEvent(Event):
|
||||
def __init__(self):
|
||||
self.name = 'Running from within a pod'
|
||||
|
||||
@@ -7,7 +7,7 @@ from ...core.events import handler
|
||||
from ...core.events.types import NewHostEvent, OpenPortEvent
|
||||
|
||||
|
||||
default_ports = [8001, 10250, 10255, 30000, 443, 6443]
|
||||
default_ports = [8001, 10250, 10255, 30000, 443, 6443, 2379]
|
||||
|
||||
@handler.subscribe(NewHostEvent)
|
||||
class PortDiscovery(Hunter):
|
||||
|
||||
@@ -7,7 +7,7 @@ from kubelet import ExposedRunHandler
|
||||
|
||||
from ...core.events import handler
|
||||
from ...core.events.types import Event, Vulnerability
|
||||
from ...core.types import Hunter, ActiveHunter, KubernetesCluster, IdentityTheft, Azure
|
||||
from ...core.types import Hunter, ActiveHunter, IdentityTheft, Azure
|
||||
|
||||
|
||||
class AzureSpnExposure(Vulnerability, Event):
|
||||
|
||||
122
src/modules/hunting/etcd.py
Normal file
122
src/modules/hunting/etcd.py
Normal file
@@ -0,0 +1,122 @@
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
from ...core.events import handler
|
||||
from ...core.events.types import Vulnerability, Event, OpenPortEvent
|
||||
from ...core.types import ActiveHunter, Hunter, KubernetesCluster, InformationDisclosure, RemoteCodeExec, \
|
||||
UnauthenticatedAccess, AccessRisk
|
||||
|
||||
""" Vulnerabilities """
|
||||
class EtcdRemoteWriteAccessEvent(Vulnerability, Event):
|
||||
"""Remote write access might grant an attacker full control over the kubernetes cluster"""
|
||||
|
||||
def __init__(self, write_res):
|
||||
Vulnerability.__init__(self, KubernetesCluster, name="Etcd Remote Write Access Event", category=RemoteCodeExec)
|
||||
self.evidence = write_res
|
||||
|
||||
class EtcdRemoteReadAccessEvent(Vulnerability, Event):
|
||||
"""Remote read access might expose to an attacker cluster's possible exploits, secrets and more."""
|
||||
|
||||
def __init__(self, keys):
|
||||
Vulnerability.__init__(self, KubernetesCluster, name="Etcd Remote Read Access Event", category=AccessRisk)
|
||||
self.evidence = keys
|
||||
|
||||
class EtcdRemoteVersionDisclosureEvent(Vulnerability, Event):
|
||||
"""Remote version disclosure might give an attacker a valuable data to attack a cluster"""
|
||||
|
||||
def __init__(self, version):
|
||||
Vulnerability.__init__(self, KubernetesCluster, name="Etcd Remote version disclosure",
|
||||
category=InformationDisclosure)
|
||||
self.evidence = version
|
||||
|
||||
class EtcdAccessEnabledWithoutAuthEvent(Vulnerability, Event):
|
||||
"""Etcd is accessible using HTTP (without authorization and authentication), it would allow a potential attacker to
|
||||
gain access to the etcd"""
|
||||
|
||||
def __init__(self, version):
|
||||
Vulnerability.__init__(self, KubernetesCluster, name="Etcd is accessible using insecure connection (HTTP)",
|
||||
category=UnauthenticatedAccess)
|
||||
self.evidence = version
|
||||
|
||||
# Active Hunter
|
||||
@handler.subscribe(OpenPortEvent, predicate=lambda p: p.port == 2379)
|
||||
class EtcdRemoteAccessActive(ActiveHunter):
|
||||
"""Etcd Remote Access
|
||||
Checks for remote write access to etcd"""
|
||||
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
self.write_evidence = ''
|
||||
|
||||
def db_keys_write_access(self):
|
||||
logging.debug("Active hunter is attempting to write keys remotely on host " + self.event.host)
|
||||
data = {
|
||||
'value': 'remotely written data'
|
||||
}
|
||||
try:
|
||||
r = requests.post("{protocol}://{host}:{port}/v2/keys/message".format(host=self.event.host, port=2379,
|
||||
protocol=self.protocol), data=data)
|
||||
self.write_evidence = r.content if r.status_code == 200 and r.content != '' else False
|
||||
return self.write_evidence
|
||||
except requests.exceptions.ConnectionError:
|
||||
return False
|
||||
|
||||
def execute(self):
|
||||
if self.db_keys_write_access():
|
||||
self.publish_event(EtcdRemoteWriteAccessEvent(self.write_evidence))
|
||||
|
||||
|
||||
# Passive Hunter
|
||||
@handler.subscribe(OpenPortEvent, predicate=lambda p: p.port == 2379)
|
||||
class EtcdRemoteAccess(Hunter):
|
||||
"""Etcd Remote Access
|
||||
Checks for remote availability of etcd, version, read access, write access
|
||||
"""
|
||||
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
self.version_evidence = ''
|
||||
self.keys_evidence = ''
|
||||
self.protocol = 'https'
|
||||
|
||||
def db_keys_disclosure(self):
|
||||
logging.debug(self.event.host + " Passive hunter is attempting to read etcd keys remotely")
|
||||
try:
|
||||
r = requests.get(
|
||||
"{protocol}://{host}:{port}/v2/keys".format(protocol=self.protocol, host=self.event.host, port=2379),
|
||||
verify=False)
|
||||
self.keys_evidence = r.content if r.status_code == 200 and r.content != '' else False
|
||||
return self.keys_evidence
|
||||
except requests.exceptions.ConnectionError:
|
||||
return False
|
||||
|
||||
def version_disclosure(self):
|
||||
logging.debug(self.event.host + " Passive hunter is attempting to check etcd version remotely")
|
||||
try:
|
||||
r = requests.get(
|
||||
"{protocol}://{host}:{port}/version".format(protocol=self.protocol, host=self.event.host, port=2379),
|
||||
verify=False)
|
||||
self.version_evidence = r.content if r.status_code == 200 and r.content != '' else False
|
||||
return self.version_evidence
|
||||
except requests.exceptions.ConnectionError:
|
||||
return False
|
||||
|
||||
def insecure_access(self):
|
||||
logging.debug(self.event.host + " Passive hunter is attempting to access etcd insecurely")
|
||||
try:
|
||||
r = requests.get("http://{host}:{port}/version".format(host=self.event.host, port=2379), verify=False)
|
||||
return r.content if r.status_code == 200 and r.content != '' else False
|
||||
except requests.exceptions.ConnectionError:
|
||||
return False
|
||||
|
||||
def execute(self):
|
||||
if self.insecure_access(): # make a decision between http and https protocol
|
||||
self.protocol = 'http'
|
||||
if self.version_disclosure():
|
||||
self.publish_event(EtcdRemoteVersionDisclosureEvent(self.version_evidence))
|
||||
if self.protocol == 'http':
|
||||
self.publish_event(EtcdAccessEnabledWithoutAuthEvent(self.version_evidence))
|
||||
if self.db_keys_disclosure():
|
||||
self.publish_event(EtcdRemoteReadAccessEvent(self.keys_evidence))
|
||||
|
||||
Reference in New Issue
Block a user