Skip to content

Commit

Permalink
Refacture host discovery to explicitly split discovery from outside c…
Browse files Browse the repository at this point in the history
…luster and discovery from pod.

Now depending on '--pod' flag scan starts with either HostScanEvent or RunningAsPodEvent.
  • Loading branch information
mcherny committed Dec 27, 2018
1 parent a7dd3a5 commit 545c603
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 61 deletions.
7 changes: 5 additions & 2 deletions kube-hunter.py
Expand Up @@ -44,7 +44,7 @@
from src.core.events import handler
from src.core.events.types import HuntFinished, HuntStarted
from src.modules.discovery import HostDiscovery
from src.modules.discovery.hosts import HostScanEvent
from src.modules.discovery.hosts import RunningAsPodEvent, HostScanEvent
import src


Expand Down Expand Up @@ -117,7 +117,10 @@ def main():
hunt_started = True
hunt_started_lock.release()
handler.publish_event(HuntStarted())
handler.publish_event(HostScanEvent())
if config.pod:
handler.publish_event(RunningAsPodEvent())
else:
handler.publish_event(HostScanEvent())

# Blocking to see discovery output
handler.join()
Expand Down
141 changes: 82 additions & 59 deletions src/modules/discovery/hosts.py
@@ -1,3 +1,4 @@
import os
import json
import logging
import socket
Expand All @@ -18,7 +19,17 @@
class RunningAsPodEvent(Event):
def __init__(self):
self.name = 'Running from within a pod'

self.auth_token = self.get_auth_token()
self.client_cert = self.get_client_cert()

def get_auth_token(self):
try:
with open("/run/secrets/kubernetes.io/serviceaccount/token") as token_file:
return token_file.read()
except IOError:
pass
def get_client_cert(self):
return "/run/secrets/kubernetes.io/serviceaccount/ca.crt"

class AzureMetadataApi(Vulnerability, Event):
"""Access to the Azure Metadata API exposes sensitive information about the machines associated with the cluster"""
Expand All @@ -31,51 +42,10 @@ class HostScanEvent(Event):
def __init__(self, pod=False, active=False, predefined_hosts=list()):
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()

def get_auth_token(self):
if config.pod:
try:
with open("/run/secrets/kubernetes.io/serviceaccount/token") as token_file:
return token_file.read()
except IOError:
pass
def get_client_cert(self):
if config.pod:
return "/run/secrets/kubernetes.io/serviceaccount/ca.crt"

@handler.subscribe(HostScanEvent)
class HostDiscovery(Hunter):
"""Host Discovery
Generates ip adresses to scan, based on cluster/scan type
"""
def __init__(self, event):
self.event = event

def execute(self):
if config.cidr:
try:
ip, sn = config.cidr.split('/')
except ValueError as e:
logging.error("unable to parse cidr: {0}".format(e))
return
cloud = self.get_cloud(ip)
for ip in self.generate_subnet(ip, sn=sn):
self.publish_event(NewHostEvent(host=ip, cloud=cloud))
elif config.internal:
self.scan_interfaces()
elif len(config.remote) > 0:
for host in config.remote:
self.publish_event(NewHostEvent(host=host, cloud=self.get_cloud(host)))
elif config.pod:
self.publish_event(RunningAsPodEvent())
if self.is_azure_pod():
self.azure_metadata_discovery()
else:
self.traceroute_discovery()

def get_cloud(self, host):
class HostDiscoveryHelpers:
@staticmethod
def get_cloud(host):
try:
logging.debug("Passive hunter is checking whether the cluster is deployed on azure's cloud")
metadata = requests.get("http://www.azurespeed.com/api/region?ipOrUrl={ip}".format(ip=host)).text
Expand All @@ -85,6 +55,38 @@ def get_cloud(self, host):
if "cloud" in metadata:
return json.loads(metadata)["cloud"]

# generator, generating a subnet by given a cidr
@staticmethod
def generate_subnet(ip, sn="24"):
logging.debug("HostDiscoveryHelpers.generate_subnet {0}/{1}".format(ip, sn))
subnet = IPNetwork('{ip}/{sn}'.format(ip=ip, sn=sn))
for ip in IPNetwork(subnet):
logging.debug("HostDiscoveryHelpers.generate_subnet yilding {0}".format(ip))
yield ip


@handler.subscribe(RunningAsPodEvent)
class FromPodHostDiscovery(Hunter):
"""Host Discovery when running as pod
Generates ip adresses to scan, based on cluster/scan type
"""
def __init__(self, event):
self.event = event

def execute(self):
# Discover master API server from in-pod environment variable.

if self.is_azure_pod():
subnets, cloud =self.azure_metadata_discovery()
else:
subnets, cloud = self.traceroute_discovery()


for subnet in subnets:
logging.debug("From pod scanning subnet {0}/{1}".format(subnet[0], subnet[1]))
for ip in HostDiscoveryHelpers.generate_subnet(ip=subnet[0], sn=subnet[1]):
self.publish_event(NewHostEvent(host=ip, cloud=cloud))

def is_azure_pod(self):
try:
logging.debug("Attempting to access Azure Metadata API")
Expand All @@ -93,28 +95,55 @@ def is_azure_pod(self):
except requests.exceptions.ConnectionError:
return False

# for pod scanning
# for pod scanning
def traceroute_discovery(self):
external_ip = requests.get("http://canhazip.com").text # getting external ip, to determine if cloud cluster
cloud = self.get_cloud(external_ip)
cloud = HostDiscoveryHelpers.get_cloud(external_ip)
logging.getLogger("scapy.runtime").setLevel(logging.ERROR) # disables scapy's warnings
from scapy.all import ICMP, IP, Ether, srp1

node_internal_ip = srp1(Ether() / IP(dst="google.com" , ttl=1) / ICMP(), verbose=0)[IP].src
for ip in self.generate_subnet(ip=node_internal_ip, sn="24"):
self.publish_event(NewHostEvent(host=ip, cloud=external_ip))
return [ [node_internal_ip,"24"], ], external_ip

# quering azure's interface metadata api | works only from a pod
def azure_metadata_discovery(self):
logging.debug("Passive hunter is attempting to pull azure's metadata")
machine_metadata = json.loads(requests.get("http://169.254.169.254/metadata/instance?api-version=2017-08-01", headers={"Metadata":"true"}).text)
address, subnet= "", ""
subnets = list()
for interface in machine_metadata["network"]["interface"]:
address, subnet = interface["ipv4"]["subnet"][0]["address"], interface["ipv4"]["subnet"][0]["prefix"]
for ip in self.generate_subnet(address, sn=subnet if not config.quick else "24"):
self.publish_event(NewHostEvent(host=ip, cloud="Azure"))
logging.debug("From pod discovered subnet {0}/{1}".format(address, subnet if not config.quick else "24"))
subnets.append([address,subnet if not config.quick else "24"])

self.publish_event(AzureMetadataApi(cidr="{}/{}".format(address, subnet)))

return subnets, "Azure"

@handler.subscribe(HostScanEvent)
class HostDiscovery(Hunter):
"""Host Discovery
Generates ip adresses to scan, based on cluster/scan type
"""
def __init__(self, event):
self.event = event

def execute(self):
if config.cidr:
try:
ip, sn = config.cidr.split('/')
except ValueError as e:
logging.error("unable to parse cidr: {0}".format(e))
return
cloud = HostDiscoveryHelpers.get_cloud(ip)
for ip in HostDiscoveryHelpers.generate_subnet(ip, sn=sn):
self.publish_event(NewHostEvent(host=ip, cloud=cloud))
elif config.internal:
self.scan_interfaces()
elif len(config.remote) > 0:
for host in config.remote:
self.publish_event(NewHostEvent(host=host, cloud=HostDiscoveryHelpers.get_cloud(host)))

# for normal scanning
def scan_interfaces(self):
try:
Expand All @@ -124,23 +153,17 @@ def scan_interfaces(self):
logging.debug("unable to determine local IP address: {0}".format(e))
logging.info("~ default to 127.0.0.1")
external_ip = "127.0.0.1"
cloud = self.get_cloud(external_ip)
cloud = HostDiscoveryHelpers.get_cloud(external_ip)
for ip in self.generate_interfaces_subnet():
handler.publish_event(NewHostEvent(host=ip, cloud=cloud))

# generator, generating a subnet by given a cidr
def generate_subnet(self, ip, sn="24"):
subnet = IPNetwork('{ip}/{sn}'.format(ip=ip, sn=sn))
for ip in IPNetwork(subnet):
yield ip

# generate all subnets from all internal network interfaces
def generate_interfaces_subnet(self, sn='24'):
for ifaceName in interfaces():
for ip in [i['addr'] for i in ifaddresses(ifaceName).setdefault(AF_INET, [])]:
if not self.event.localhost and InterfaceTypes.LOCALHOST.value in ip.__str__():
continue
for ip in self.generate_subnet(ip, sn):
for ip in HostDiscoveryHelpers.generate_subnet(ip, sn):
yield ip

# for comparing prefixes
Expand Down

0 comments on commit 545c603

Please sign in to comment.