Ejemplo n.º 1
0
 def run(self, command, container):
     config = get_config()
     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)
Ejemplo n.º 2
0
    def aws_metadata_v1_discovery(self):
        config = get_config()
        logger.debug("From pod attempting to access aws's metadata v1")
        mac_address = requests.get(
            "http://169.254.169.254/latest/meta-data/mac",
            timeout=config.network_timeout,
        ).text
        logger.debug(f"Extracted mac from aws's metadata v1: {mac_address}")

        cidr = requests.get(
            f"http://169.254.169.254/latest/meta-data/network/interfaces/macs/{mac_address}/subnet-ipv4-cidr-block",
            timeout=config.network_timeout,
        ).text
        logger.debug(f"Trying to extract cidr from aws's metadata v1: {cidr}")

        try:
            cidr = cidr.split("/")
            address, subnet = (cidr[0], cidr[1])
            subnet = subnet if not config.quick else "24"
            cidr = f"{address}/{subnet}"
            logger.debug(f"From pod discovered subnet {cidr}")

            self.publish_event(AWSMetadataApi(cidr=cidr))
            return [(address, subnet)], "AWS"
        except Exception as x:
            logger.debug(f"ERROR: could not parse cidr from aws metadata api: {cidr} - {x}")

        return [], "AWS"
Ejemplo n.º 3
0
 def get_pods(self, namespace=None):
     config = get_config()
     pods = []
     try:
         if not namespace:
             r = requests.get(
                 f"{self.path}/api/v1/pods",
                 headers=self.headers,
                 verify=False,
                 timeout=config.network_timeout,
             )
         else:
             r = requests.get(
                 f"{self.path}/api/v1/namespaces/{namespace}/pods",
                 headers=self.headers,
                 verify=False,
                 timeout=config.network_timeout,
             )
         if r.status_code == 200:
             resp = json.loads(r.content)
             for item in resp["items"]:
                 name = item["metadata"]["name"].encode("ascii", "ignore")
                 namespace = item["metadata"]["namespace"].encode(
                     "ascii", "ignore")
                 pods.append({"name": name, "namespace": namespace})
             return pods
     except (requests.exceptions.ConnectionError, KeyError):
         pass
     return None
Ejemplo n.º 4
0
    def aws_metadata_v2_discovery(self):
        config = get_config()
        logger.debug("From pod attempting to access aws's metadata v2")
        token = requests.get(
            "http://169.254.169.254/latest/api/token",
            headers={"X-aws-ec2-metatadata-token-ttl-seconds": "21600"},
            timeout=config.network_timeout,
        ).text
        mac_address = requests.get(
            "http://169.254.169.254/latest/meta-data/mac",
            headers={"X-aws-ec2-metatadata-token": token},
            timeout=config.network_timeout,
        ).text
        cidr = requests.get(
            f"http://169.254.169.254/latest/meta-data/network/interfaces/macs/{mac_address}/subnet-ipv4-cidr-block",
            headers={"X-aws-ec2-metatadata-token": token},
            timeout=config.network_timeout,
        ).text.split("/")

        try:
            address, subnet = (cidr[0], cidr[1])
            subnet = subnet if not config.quick else "24"
            cidr = f"{address}/{subnet}"
            logger.debug(f"From pod discovered subnet {cidr}")

            self.publish_event(AWSMetadataApi(cidr=cidr))

            return [(address, subnet)], "AWS"
        except Exception as x:
            logger.debug(f"ERROR: could not parse cidr from aws metadata api: {cidr} - {x}")

        return [], "AWS"
Ejemplo n.º 5
0
    def execute(self):
        config = get_config()
        # Scan any hosts that the user specified
        if config.remote or config.cidr:
            self.publish_event(HostScanEvent())
        else:
            # Discover cluster subnets, we'll scan all these hosts
            cloud = None
            if self.is_azure_pod():
                subnets, cloud = self.azure_metadata_discovery()
            else:
                subnets = self.traceroute_discovery()

            should_scan_apiserver = False
            if self.event.kubeservicehost:
                should_scan_apiserver = True
            for ip, mask in subnets:
                if self.event.kubeservicehost and self.event.kubeservicehost in IPNetwork(
                        f"{ip}/{mask}"):
                    should_scan_apiserver = False
                logger.debug(f"From pod scanning subnet {ip}/{mask}")
                for ip in IPNetwork(f"{ip}/{mask}"):
                    self.publish_event(NewHostEvent(host=ip, cloud=cloud))
            if should_scan_apiserver:
                self.publish_event(
                    NewHostEvent(host=IPAddress(self.event.kubeservicehost),
                                 cloud=cloud))
Ejemplo n.º 6
0
 def execute(self):
     config = get_config()
     report = config.reporter.get_report(statistics=config.statistics,
                                         mapping=config.mapping)
     config.dispatcher.dispatch(report)
     handler.publish_event(ReportDispatched())
     handler.publish_event(TablesPrinted())
Ejemplo n.º 7
0
 def test_logs_endpoint(self):
     config = get_config()
     logs_url = self.session.get(
         self.path + KubeletHandlers.LOGS.value.format(path=""),
         timeout=config.network_timeout,
     ).text
     return "<pre>" in logs_url
Ejemplo n.º 8
0
 def get_pods_endpoint(self):
     config = get_config()
     logger.debug("Attempting to find pods endpoints")
     response = requests.get(f"{self.path}/pods",
                             timeout=config.network_timeout)
     if "items" in response.text:
         return response.json()
Ejemplo n.º 9
0
 def test_running_pods(self):
     config = get_config()
     pods_url = self.path + KubeletHandlers.RUNNINGPODS.value
     r = self.session.get(pods_url,
                          verify=False,
                          timeout=config.network_timeout)
     return r.json() if r.status_code == 200 else False
Ejemplo n.º 10
0
 def test_handlers(self):
     config = get_config()
     # if kube-hunter runs in a pod, we test with kube-hunter's pod
     pod = self.kubehunter_pod if config.pod else self.get_random_pod()
     if pod:
         debug_handlers = self.DebugHandlers(self.path, pod, self.session)
         try:
             # TODO: use named expressions, introduced in python3.8
             running_pods = debug_handlers.test_running_pods()
             if running_pods:
                 self.publish_event(
                     ExposedRunningPodsHandler(
                         count=len(running_pods["items"])))
             cmdline = debug_handlers.test_pprof_cmdline()
             if cmdline:
                 self.publish_event(ExposedKubeletCmdline(cmdline=cmdline))
             if debug_handlers.test_container_logs():
                 self.publish_event(ExposedContainerLogsHandler())
             if debug_handlers.test_exec_container():
                 self.publish_event(ExposedExecHandler())
             if debug_handlers.test_run_container():
                 self.publish_event(ExposedRunHandler())
             if debug_handlers.test_port_forward():
                 self.publish_event(
                     ExposedPortForwardHandler())  # not implemented
             if debug_handlers.test_attach_container():
                 self.publish_event(ExposedAttachHandler())
             if debug_handlers.test_logs_endpoint():
                 self.publish_event(ExposedSystemLogs())
         except Exception:
             logger.debug("Failed testing debug handlers", exc_info=True)
Ejemplo n.º 11
0
 def execute(self):
     config = get_config()
     pods_raw = self.event.session.get(
         self.base_url + KubeletHandlers.PODS.value,
         verify=False,
         timeout=config.network_timeout,
     ).text
     if "items" in pods_raw:
         pods_data = json.loads(pods_raw)["items"]
         for pod_data in pods_data:
             container_data = pod_data["spec"]["containers"][0]
             if container_data:
                 container_name = container_data["name"]
                 output = requests.get(
                     f"{self.base_url}/" +
                     KubeletHandlers.CONTAINERLOGS.value.format(
                         pod_namespace=pod_data["metadata"]["namespace"],
                         pod_id=pod_data["metadata"]["name"],
                         container_name=container_name,
                     ),
                     verify=False,
                     timeout=config.network_timeout,
                 )
                 if output.status_code == 200 and output.text:
                     self.event.evidence = f"{container_name}: {output.text}"
                     return
Ejemplo n.º 12
0
 def get_pods_endpoint(self):
     config = get_config()
     response = self.session.get(f"{self.path}/pods",
                                 verify=False,
                                 timeout=config.network_timeout)
     if "items" in response.text:
         return response.json()
Ejemplo n.º 13
0
 def get_read_only_access(self):
     config = get_config()
     endpoint = f"http://{self.event.host}:{self.event.port}/pods"
     logger.debug(f"Trying to get kubelet read access at {endpoint}")
     r = requests.get(endpoint, timeout=config.network_timeout)
     if r.status_code == 200:
         self.publish_event(ReadOnlyKubeletEvent())
Ejemplo n.º 14
0
    def execute(self):
        config = get_config()
        # Attempt to read all hosts from the Kubernetes API
        for host in list_all_k8s_cluster_nodes(config.kubeconfig):
            self.publish_event(NewHostEvent(host=host))
        # Scan any hosts that the user specified
        if config.remote or config.cidr:
            self.publish_event(HostScanEvent())
        else:
            # Discover cluster subnets, we'll scan all these hosts
            cloud, subnets = None, list()
            if self.is_azure_pod():
                subnets, cloud = self.azure_metadata_discovery()
            elif self.is_aws_pod_v1():
                subnets, cloud = self.aws_metadata_v1_discovery()
            elif self.is_aws_pod_v2():
                subnets, cloud = self.aws_metadata_v2_discovery()

            subnets += self.gateway_discovery()

            should_scan_apiserver = False
            if self.event.kubeservicehost:
                should_scan_apiserver = True
            for ip, mask in subnets:
                if self.event.kubeservicehost and self.event.kubeservicehost in IPNetwork(f"{ip}/{mask}"):
                    should_scan_apiserver = False
                logger.debug(f"From pod scanning subnet {ip}/{mask}")
                for ip in IPNetwork(f"{ip}/{mask}"):
                    self.publish_event(NewHostEvent(host=ip, cloud=cloud))
            if should_scan_apiserver:
                self.publish_event(NewHostEvent(host=IPAddress(self.event.kubeservicehost), cloud=cloud))
Ejemplo n.º 15
0
    def publish_event(self, event, caller=None):
        config = get_config()

        # setting event chain
        if caller:
            event.previous = caller.event
            event.hunter = caller.__class__

        # applying filters on the event, before publishing it to subscribers.
        # if filter returned None, not proceeding to publish
        event = self.apply_filters(event)
        if event:
            # If event was rewritten, make sure it's linked to its parent ('previous') event
            if caller:
                event.previous = caller.event
                event.hunter = caller.__class__

            for hooked_event in self.hooks.keys():
                if hooked_event in event.__class__.__mro__:
                    for hook, predicate in self.hooks[hooked_event]:
                        if predicate and not predicate(event):
                            continue

                        if config.statistics and caller:
                            if Vulnerability in event.__class__.__mro__:
                                caller.__class__.publishedVulnerabilities += 1

                        logger.debug(
                            f"Event {event.__class__} got published with {event}"
                        )
                        self.put(hook(event))
Ejemplo n.º 16
0
 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"],
                         }
Ejemplo n.º 17
0
 def test_container_logs(self):
     config = get_config()
     logs_url = self.path + KubeletHandlers.CONTAINERLOGS.value.format(
         pod_namespace=self.pod["namespace"],
         pod_id=self.pod["name"],
         container_name=self.pod["container"],
     )
     return self.session.get(logs_url, verify=False, timeout=config.network_timeout).status_code == 200
Ejemplo n.º 18
0
 def test_pprof_cmdline(self):
     config = get_config()
     cmd = self.session.get(
         self.path + KubeletHandlers.PPROF_CMDLINE.value,
         verify=False,
         timeout=config.network_timeout,
     )
     return cmd.text if cmd.status_code == 200 else None
Ejemplo n.º 19
0
 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)
Ejemplo n.º 20
0
 def traceroute_discovery(self):
     config = get_config()
     node_internal_ip = srp1(
         Ether() / IP(dst="1.1.1.1", ttl=1) / ICMP(),
         verbose=0,
         timeout=config.network_timeout,
     )[IP].src
     return [[node_internal_ip, "24"]]
Ejemplo n.º 21
0
 def execute(self):
     config = get_config()
     version_metadata = requests.get(
         f"http://{self.event.host}:{self.event.port}/version",
         verify=False,
         timeout=config.network_timeout,
     ).json()
     if "buildDate" in version_metadata:
         self.event.evidence = "build date: {}".format(version_metadata["buildDate"])
Ejemplo n.º 22
0
    def get_request(self, url, verify=False):
        config = get_config()
        try:
            response_text = self.event.session.get(url=url, verify=verify, timeout=config.network_timeout).text.rstrip()

            return response_text
        except Exception as ex:
            logging.debug("Exception: " + str(ex))
            return "Exception: " + str(ex)
Ejemplo n.º 23
0
 def execute(self):
     config = get_config()
     cve_mapping = {
         KubectlCpVulnerability: ["1.11.9", "1.12.7", "1.13.5", "1.14.0"],
         IncompleteFixToKubectlCpVulnerability: ["1.12.9", "1.13.6", "1.14.2"],
     }
     logger.debug(f"Checking known CVEs for kubectl version: {self.event.version}")
     for vulnerability, fix_versions in cve_mapping.items():
         if CveUtils.is_vulnerable(fix_versions, self.event.version, not config.include_patched_versions):
             self.publish_event(vulnerability(binary_version=self.event.version))
Ejemplo n.º 24
0
 def execute(self):
     config = get_config()
     if config.cidr:
         for ip in HostDiscoveryHelpers.generate_hosts(config.cidr):
             self.publish_event(NewHostEvent(host=ip))
     elif config.interface:
         self.scan_interfaces()
     elif len(config.remote) > 0:
         for host in config.remote:
             self.publish_event(NewHostEvent(host=host))
Ejemplo n.º 25
0
 def insecure_access(self):
     config = get_config()
     logger.debug(f"Trying to access etcd insecurely at {self.event.host}")
     try:
         r = requests.get(
             f"http://{self.event.host}:{ETCD_PORT}/version", verify=False, timeout=config.network_timeout,
         )
         return r.content if r.status_code == 200 and r.content else False
     except requests.exceptions.ConnectionError:
         return False
Ejemplo n.º 26
0
 def access_api_server(self):
     config = get_config()
     logger.debug(f"Passive Hunter is attempting to access the API at {self.path}")
     try:
         r = requests.get(f"{self.path}/api", headers=self.headers, verify=False, timeout=config.network_timeout)
         if r.status_code == 200 and r.content:
             return r.content
     except requests.exceptions.ConnectionError:
         pass
     return False
Ejemplo n.º 27
0
 def has_api_behaviour(self, protocol):
     config = get_config()
     try:
         r = self.session.get(f"{protocol}://{self.event.host}:{self.event.port}", timeout=config.network_timeout)
         if ("k8s" in r.text) or ('"code"' in r.text and r.status_code != 200):
             return True
     except requests.exceptions.SSLError:
         logger.debug(f"{[protocol]} protocol not accepted on {self.event.host}:{self.event.port}")
     except Exception:
         logger.debug(f"Failed probing {self.event.host}:{self.event.port}", exc_info=True)
Ejemplo n.º 28
0
 def services(self):
     config = get_config()
     # map between namespaces and service names
     services = dict()
     for namespace in self.namespaces:
         resource_path = f"{self.api_url}/namespaces/{namespace}/services"
         resource_json = requests.get(resource_path, timeout=config.network_timeout).json()
         services[namespace] = self.extract_names(resource_json)
     logger.debug(f"Enumerated services [{' '.join(services)}]")
     return services
Ejemplo n.º 29
0
 def get_k8s_version(self):
     config = get_config()
     logger.debug("Passive hunter is attempting to find kubernetes version")
     metrics = requests.get(f"{self.path}/metrics", timeout=config.network_timeout).text
     for line in metrics.split("\n"):
         if line.startswith("kubernetes_build_info"):
             for info in line[line.find("{") + 1 : line.find("}")].split(","):
                 k, v = info.split("=")
                 if k == "gitVersion":
                     return v.strip('"')
Ejemplo n.º 30
0
 def accesible(self):
     config = get_config()
     endpoint = f"http://{self.host}:{self.port}/api/v1"
     logger.debug("Attempting to discover a proxy service")
     try:
         r = requests.get(endpoint, timeout=config.network_timeout)
         if r.status_code == 200 and "APIResourceList" in r.text:
             return True
     except requests.Timeout:
         logger.debug(f"failed to get {endpoint}", exc_info=True)
     return False