Lucene search
K

Linux Container Enumeration

🗓️ 06 Aug 2020 17:41:09Reported by stealthcopterType 
metasploit
 metasploit
🔗 www.rapid7.com👁 99 Views

This module attempts to enumerate containers on the target machine and optionally run a command on each active container found. Currently it supports Docker, LXC and RKT

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

class MetasploitModule < Msf::Post
  include Msf::Post::File

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Linux Container Enumeration',
        'Description' => %q{
          This module attempts to enumerate containers on the target machine and optionally run a command on each active container found.
          Currently it supports Docker, LXC and RKT.
        },
        'License' => MSF_LICENSE,
        'Author' => ['stealthcopter'],
        'Platform' => ['linux'],
        'SessionTypes' => ['shell', 'meterpreter'],
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [IOC_IN_LOGS],
          'Reliability' => []
        }
      )
    )
    register_options(
      [
        OptString.new('CMD', [false, 'Optional command to run on each running container', ''])
      ]
    )
  end

  def cmd
    datastore['CMD']
  end

  # Check if a container program is installed and usable by current user
  def runnable(container_type)
    case container_type
    when 'docker'
      command = 'docker >/dev/null 2>&1 && echo true'
    when 'lxc'
      command = 'lxc >/dev/null 2>&1 && echo true'
    when 'rkt'
      # Apparently rkt doesn't play nice with 2>&1 for most commands, though `rkt help`
      # seems to be fine so this is why its used here vs just 'rkt'
      command = 'rkt help >/dev/null 2>&1 && echo true'
    else
      print_error("Invalid container type #{container_type}")
      return false
    end
    cmd_exec(command) =~ /true$/
  end

  # Count the number of currently running containers
  def count_containers(container_type, count_inactive: true)
    case container_type
    when 'docker'
      command = if count_inactive
                  'docker container ls --format "{{.Names}}" | wc -l'
                else
                  'docker container ls -a --format "{{.Names}}" | wc -l'
                end
    when 'lxc'
      command = if count_inactive
                  'lxc list -c n --format csv | wc -l'
                else
                  'lxc list -c n,s --format csv | grep ,RUNNING | wc -l'
                end
    when 'rkt'
      command = if count_inactive
                  'rkt list | tail -n +2 | wc -l'
                else
                  'rkt list | tail -n +2 | grep running | wc -l'
                end
    else
      print_error("Invalid container type '#{container_type}'")
      return 0
    end

    result = cmd_exec(command)
    if result =~ /denied/
      print_error("Was unable to enumerate the number of #{container_type} containers due to a lack of permissions!")
      return 0
    else
      result.to_i
    end
  end

  # List containers
  def list_containers(container_type)
    case container_type
    when 'docker'
      result = cmd_exec('docker container ls -a')
    when 'lxc'
      # LXC does some awful table formatting, lets try and fix it to be more uniform
      result = cmd_exec('lxc list').each_line.reject { |st| st =~ /^\+--/ }.map.with_index.map do |s, i|
        if i == 0
          s.split('| ').map { |t| t.strip.ljust(t.size, ' ').gsub(/\|/, '') }.join + "\n"
        else
          s.gsub(/\| /, '').gsub(/\|/, '')
        end
      end.join.strip
    when 'rkt'
      result = cmd_exec('rkt list')
    else
      print_error("Invalid container type '#{container_type}'")
      return nil
    end
    result
  end

  # List running containers identifiers
  def list_running_containers_id(container_type)
    case container_type
    when 'docker'
      command = 'docker container ls --format "{{.Names}}"'
    when 'lxc'
      command = 'lxc list -c n,s --format csv 2>/dev/null | grep ,RUNNING|cut -d, -f1'
    when 'rkt'
      command = 'rkt list | tail -n +2 | cut -f1'
    else
      print_error("Invalid container type '#{container_type}'")
      return nil
    end
    cmd_exec(command).each_line.map(&:strip)
  end

  # Execute a command on a container
  def container_execute(container_type, container_identifier, command)
    case container_type
    when 'docker'
      command = "docker exec '#{container_identifier}' #{command}"
    when 'lxc'
      command = "lxc exec '#{container_identifier}' -- #{command}"
    when 'rkt'
      print_error("RKT containers do not support command execution\nUse the command \"rkt enter '#{container_identifier}'\" to manually enumerate this container")
      return nil
    else
      print_error("Invalid container type '#{container_type}'")
      return nil
    end
    vprint_status("Running #{command}")
    cmd_exec(command)
  end

  # Run Method for when run command is issued
  def run
    platforms = %w[docker lxc rkt].select { |p| runnable(p) }

    if platforms.empty?
      print_error('No container software appears to be installed or runnable by the current user')
      return
    end

    platforms.each do |platform|
      print_good("#{platform} was found on the system!")
      num_containers = count_containers(platform, count_inactive: false)

      if num_containers == 0
        print_error("No active or inactive containers were found for #{platform}\n")
      else
        num_running_containers = count_containers(platform, count_inactive: true)
        print_good("#{platform}: #{num_running_containers} Running Containers / #{num_containers} Total")
      end

      next unless num_containers > 0

      containers = list_containers(platform)
      next if containers.nil?

      # Using print so not to mess up table formatting
      print_line(containers.to_s)

      p = store_loot("host.#{platform}_containers", 'text/plain', session, containers, "#{platform}_containers.txt", "#{platform} Containers")
      print_good("Results stored in: #{p}\n")

      next if cmd.blank?

      running_container_ids = list_running_containers_id(platform)
      next if running_container_ids.nil?

      running_container_ids.each do |container_id|
        print_status("Executing command on #{platform} container #{container_id}")
        command_result = container_execute(platform, container_id, cmd)
        next if command_result.nil?

        print_good(command_result)
        p = store_loot("host.#{platform}_command_results", 'text/plain', session, command_result, "#{platform}_containers_command_results.txt", "#{platform} Containers Command Results")
        print_good("Command execution results stored in: #{p}\n")
      end
    end
  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