From c0b1169918a24b42ab1642740b478ec7fb943a8d Mon Sep 17 00:00:00 2001 From: Liz Rice Date: Tue, 19 Feb 2019 15:22:16 +0000 Subject: [PATCH 1/5] Only report API Servers that behave like Kubernetes API Servers This should stop my printer and home light server being reported as Kubernetes API Servers when I scan my home network! --- src/modules/discovery/apiserver.py | 11 +++++----- src/modules/discovery/test_apiserver.py | 28 +++++++++++++++++++++++++ src/modules/hunting/CVE_2018_1002105.py | 5 +++-- src/modules/hunting/apiserver.py | 5 +++-- 4 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 src/modules/discovery/test_apiserver.py diff --git a/src/modules/discovery/apiserver.py b/src/modules/discovery/apiserver.py index 02035c5..2e0b5b9 100644 --- a/src/modules/discovery/apiserver.py +++ b/src/modules/discovery/apiserver.py @@ -12,17 +12,18 @@ class ApiServer(Service, Event): Service.__init__(self, name="API Server") +# Other devices could have this port open, but we can check to see if it looks like a Kubernetes node +# A Kubernetes API server will respond with a JSON message that includes a "code" field for the HTTP status code @handler.subscribe(OpenPortEvent, predicate=lambda x: x.port==443 or x.port==6443) class ApiServerDiscovery(Hunter): """Api Server Discovery - Checks for the existence of a an Api Server + Checks for the existence of a an API Server """ def __init__(self, event): self.event = event def execute(self): - logging.debug("Attempting to discover an Api server") + logging.debug("Attempting to discover an API server") main_request = requests.get("https://{}:{}".format(self.event.host, self.event.port), verify=False).text - if "code" in main_request: - self.event.role = "Master" - self.publish_event(ApiServer()) + if '"code"' in main_request: + self.publish_event(ApiServer()) diff --git a/src/modules/discovery/test_apiserver.py b/src/modules/discovery/test_apiserver.py new file mode 100644 index 0000000..95afedb --- /dev/null +++ b/src/modules/discovery/test_apiserver.py @@ -0,0 +1,28 @@ +# from mock import patch +import requests_mock + +from apiserver import ApiServer, ApiServerDiscovery +from ...core.events.types import Event +from ...core.events import handler + +def test_ApiServer(): + + with requests_mock.Mocker() as m: + m.get('https://mockOther:443', text='elephant') + m.get('https://mockKubernetes:443', text='code') + + e = Event() + e.port = 443 + e.host = 'mockOther' + + a = ApiServerDiscovery(e) + a.execute() + + e.host = 'mockKubernetes' + a.execute() + +# We should only generate an ApiServer event for a response that looks like it came from a Kubernetes node +@handler.subscribe(ApiServer) +class testApiServer(object): + def __init__(self, event): + assert event.host == 'mockKubernetes' diff --git a/src/modules/hunting/CVE_2018_1002105.py b/src/modules/hunting/CVE_2018_1002105.py index c45468f..ae2b4b1 100644 --- a/src/modules/hunting/CVE_2018_1002105.py +++ b/src/modules/hunting/CVE_2018_1002105.py @@ -5,7 +5,8 @@ import uuid import ast from ...core.events import handler -from ...core.events.types import Vulnerability, Event, OpenPortEvent +from ...core.events.types import Vulnerability, Event +from ..discovery.apiserver import ApiServer from ...core.types import Hunter, ActiveHunter, KubernetesCluster, RemoteCodeExec, AccessRisk, InformationDisclosure, PrivilegeEscalation """ Vulnerabilities """ @@ -17,7 +18,7 @@ class ServerApiVersionEndPointAccess(Vulnerability, Event): self.evidence = evidence # Passive Hunter -@handler.subscribe(OpenPortEvent, predicate=lambda x: x.port == 443 or x.port == 6443) +@handler.subscribe(ApiServer) class IsVulnerableToCVEAttack(Hunter): """ Node is running a Kubernetes version vulnerable to critical CVE-2018-1002105 """ diff --git a/src/modules/hunting/apiserver.py b/src/modules/hunting/apiserver.py index b8de74b..8975d21 100644 --- a/src/modules/hunting/apiserver.py +++ b/src/modules/hunting/apiserver.py @@ -4,7 +4,8 @@ import requests import uuid from ...core.events import handler -from ...core.events.types import Vulnerability, Event, OpenPortEvent +from ...core.events.types import Vulnerability, Event +from ..discovery.apiserver import ApiServer from ...core.types import Hunter, ActiveHunter, KubernetesCluster, RemoteCodeExec, AccessRisk, InformationDisclosure @@ -203,7 +204,7 @@ class ApiServerPassiveHunterFinished(Event): # Passive Hunter -@handler.subscribe(OpenPortEvent, predicate=lambda x: x.port == 443 or x.port == 6443) +@handler.subscribe(ApiServer) class AccessApiServerViaServiceAccountToken(Hunter): """ API Server Hunter Accessing the API server within a compromised pod might grant an attacker full control over the cluster From 014595e92bb9df3bac4308ce663187875e4e66da Mon Sep 17 00:00:00 2001 From: Liz Rice Date: Wed, 20 Feb 2019 11:53:32 +0000 Subject: [PATCH 2/5] Remove commented out line --- src/modules/discovery/test_apiserver.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/discovery/test_apiserver.py b/src/modules/discovery/test_apiserver.py index 95afedb..bdb943f 100644 --- a/src/modules/discovery/test_apiserver.py +++ b/src/modules/discovery/test_apiserver.py @@ -1,4 +1,3 @@ -# from mock import patch import requests_mock from apiserver import ApiServer, ApiServerDiscovery From c1fc84ec5ee244a3c96a612c37772df7eb2f195e Mon Sep 17 00:00:00 2001 From: Liz Rice Date: Wed, 20 Feb 2019 12:11:24 +0000 Subject: [PATCH 3/5] Better test that reflects Kubernetes response --- src/modules/discovery/test_apiserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/discovery/test_apiserver.py b/src/modules/discovery/test_apiserver.py index bdb943f..006b48b 100644 --- a/src/modules/discovery/test_apiserver.py +++ b/src/modules/discovery/test_apiserver.py @@ -8,7 +8,7 @@ def test_ApiServer(): with requests_mock.Mocker() as m: m.get('https://mockOther:443', text='elephant') - m.get('https://mockKubernetes:443', text='code') + m.get('https://mockKubernetes:443', text='{"code":403}') e = Event() e.port = 443 From fe015981295bc56b05b06b3f9853014113553d8f Mon Sep 17 00:00:00 2001 From: Liz Rice Date: Fri, 22 Feb 2019 20:51:04 +0000 Subject: [PATCH 4/5] Correct secret location --- src/modules/discovery/hosts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/discovery/hosts.py b/src/modules/discovery/hosts.py index 3912ba1..b942ce3 100644 --- a/src/modules/discovery/hosts.py +++ b/src/modules/discovery/hosts.py @@ -24,12 +24,12 @@ class RunningAsPodEvent(Event): def get_auth_token(self): try: - with open("/run/secrets/kubernetes.io/serviceaccount/token") as token_file: + with open("/var/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" + return "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" class AzureMetadataApi(Vulnerability, Event): """Access to the Azure Metadata API exposes information about the machines associated with the cluster""" From 23fd1830a2bf2b12b5a00d4fe80043f73986c2ae Mon Sep 17 00:00:00 2001 From: Liz Rice Date: Mon, 25 Feb 2019 17:54:59 +0000 Subject: [PATCH 5/5] Doesn't hurt to mark this as Master --- src/modules/discovery/apiserver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/discovery/apiserver.py b/src/modules/discovery/apiserver.py index 2e0b5b9..86c5a8e 100644 --- a/src/modules/discovery/apiserver.py +++ b/src/modules/discovery/apiserver.py @@ -26,4 +26,5 @@ class ApiServerDiscovery(Hunter): logging.debug("Attempting to discover an API server") main_request = requests.get("https://{}:{}".format(self.event.host, self.event.port), verify=False).text if '"code"' in main_request: + self.event.role = "Master" self.publish_event(ApiServer())