2020-07-13 11:22:16 +01:00
##
# 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 = { } )
2020-07-25 12:29:37 +01:00
super (
update_info (
info ,
'Name' = > 'Linux Container Enumeration' ,
'Description' = > %q{
2020-08-05 18:59:24 -05:00
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.
2020-07-25 12:29:37 +01:00
} ,
'License' = > MSF_LICENSE ,
'Author' = > [ 'stealthcopter' ] ,
'Platform' = > [ 'linux' ] ,
2023-02-03 18:12:53 +00:00
'SessionTypes' = > [ 'shell' , 'meterpreter' ] ,
'Notes' = > {
'Stability' = > [ CRASH_SAFE ] ,
'SideEffects' = > [ IOC_IN_LOGS ] ,
'Reliability' = > [ ]
}
2020-07-25 12:29:37 +01:00
)
)
2020-07-18 12:11:35 +01:00
register_options (
2020-07-25 12:29:37 +01:00
[
OptString . new ( 'CMD' , [ false , 'Optional command to run on each running container' , '' ] )
]
)
2020-07-18 12:11:35 +01:00
end
def cmd
datastore [ 'CMD' ]
2020-07-13 11:22:16 +01:00
end
# Check if a container program is installed and usable by current user
def runnable ( container_type )
case container_type
when 'docker'
2020-08-05 16:38:39 -05:00
command = 'docker >/dev/null 2>&1 && echo true'
2020-07-13 11:22:16 +01:00
when 'lxc'
2020-08-05 16:38:39 -05:00
command = 'lxc >/dev/null 2>&1 && echo true'
2020-07-13 11:22:16 +01:00
when 'rkt'
2020-08-05 21:14:24 -05:00
# Apparently rkt doesn't play nice with 2>&1 for most commands, though `rkt help`
2020-08-05 18:59:24 -05:00
# seems to be fine so this is why its used here vs just 'rkt'
command = 'rkt help >/dev/null 2>&1 && echo true'
2020-07-13 11:22:16 +01:00
else
print_error ( " Invalid container type #{ container_type } " )
return false
end
2020-07-18 12:11:35 +01:00
cmd_exec ( command ) =~ / true$ /
2020-07-13 11:22:16 +01:00
end
# Count the number of currently running containers
2021-02-24 20:24:57 +00:00
def count_containers ( container_type , count_inactive : true )
2020-07-13 11:22:16 +01:00
case container_type
when 'docker'
2020-07-18 12:11:35 +01:00
command = if count_inactive
2020-08-05 17:46:18 -05:00
'docker container ls --format "{{.Names}}" | wc -l'
2020-07-18 12:11:35 +01:00
else
2020-08-05 17:46:18 -05:00
'docker container ls -a --format "{{.Names}}" | wc -l'
2020-07-18 12:11:35 +01:00
end
2020-07-13 11:22:16 +01:00
when 'lxc'
2020-07-18 12:11:35 +01:00
command = if count_inactive
2020-08-05 17:46:18 -05:00
'lxc list -c n --format csv | wc -l'
2020-07-18 12:11:35 +01:00
else
2020-08-05 17:46:18 -05:00
'lxc list -c n,s --format csv | grep ,RUNNING | wc -l'
2020-07-18 12:11:35 +01:00
end
2020-07-13 11:22:16 +01:00
when 'rkt'
2020-07-18 12:11:35 +01:00
command = if count_inactive
2020-08-05 17:46:18 -05:00
'rkt list | tail -n +2 | wc -l'
2020-07-18 12:11:35 +01:00
else
2020-08-05 19:40:22 -05:00
'rkt list | tail -n +2 | grep running | wc -l'
2020-07-18 12:11:35 +01:00
end
2020-07-13 11:22:16 +01:00
else
print_error ( " Invalid container type ' #{ container_type } ' " )
return 0
end
2020-08-05 17:46:18 -05:00
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
2020-07-13 11:22:16 +01:00
end
2020-07-18 12:11:35 +01:00
# List containers
2020-07-13 11:22:16 +01:00
def list_containers ( container_type )
case container_type
when 'docker'
2020-07-30 16:52:41 +01:00
result = cmd_exec ( 'docker container ls -a' )
2020-07-18 12:11:35 +01:00
when 'lxc'
2020-07-30 16:52:41 +01:00
# 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
2020-07-18 12:11:35 +01:00
when 'rkt'
2020-07-30 16:52:41 +01:00
result = cmd_exec ( 'rkt list' )
2020-07-18 12:11:35 +01:00
else
print_error ( " Invalid container type ' #{ container_type } ' " )
2020-08-05 19:40:22 -05:00
return nil
2020-07-18 12:11:35 +01:00
end
2020-07-30 16:52:41 +01:00
result
2020-07-18 12:11:35 +01:00
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'
2020-08-05 19:40:22 -05:00
command = 'rkt list | tail -n +2 | cut -f1'
2020-07-18 12:11:35 +01:00
else
print_error ( " Invalid container type ' #{ container_type } ' " )
2020-08-05 19:40:22 -05:00
return nil
2020-07-18 12:11:35 +01:00
end
cmd_exec ( command ) . each_line . map ( & :strip )
end
# Execute a command on a container
2020-08-05 20:18:10 -05:00
def container_execute ( container_type , container_identifier , command )
2020-07-18 12:11:35 +01:00
case container_type
when 'docker'
command = " docker exec ' #{ container_identifier } ' #{ command } "
2020-07-13 11:22:16 +01:00
when 'lxc'
2020-07-18 12:11:35 +01:00
command = " lxc exec ' #{ container_identifier } ' -- #{ command } "
2020-07-13 11:22:16 +01:00
when 'rkt'
2020-08-05 20:47:06 -05:00
print_error ( " RKT containers do not support command execution \n Use the command \" rkt enter ' #{ container_identifier } ' \" to manually enumerate this container " )
return nil
2020-07-13 11:22:16 +01:00
else
print_error ( " Invalid container type ' #{ container_type } ' " )
2020-08-05 19:40:22 -05:00
return nil
2020-07-13 11:22:16 +01:00
end
2020-07-18 12:11:35 +01:00
vprint_status ( " Running #{ command } " )
cmd_exec ( command )
2020-07-13 11:22:16 +01:00
end
# Run Method for when run command is issued
def run
2020-07-18 12:11:35 +01:00
platforms = %w[ docker lxc rkt ] . select { | p | runnable ( p ) }
2020-07-13 11:22:16 +01:00
2020-07-18 12:11:35 +01:00
if platforms . empty?
print_error ( 'No container software appears to be installed or runnable by the current user' )
return
2020-07-13 11:22:16 +01:00
end
2020-07-18 12:11:35 +01:00
platforms . each do | platform |
2020-08-05 17:46:18 -05:00
print_good ( " #{ platform } was found on the system! " )
2021-02-24 20:24:57 +00:00
num_containers = count_containers ( platform , count_inactive : false )
2020-07-25 12:29:37 +01:00
if num_containers == 0
2020-08-05 17:46:18 -05:00
print_error ( " No active or inactive containers were found for #{ platform } \n " )
2020-07-25 12:29:37 +01:00
else
2021-02-24 20:24:57 +00:00
num_running_containers = count_containers ( platform , count_inactive : true )
2020-07-25 12:29:37 +01:00
print_good ( " #{ platform } : #{ num_running_containers } Running Containers / #{ num_containers } Total " )
end
2020-07-18 12:11:35 +01:00
2020-08-05 17:46:18 -05:00
next unless num_containers > 0
2020-07-18 12:11:35 +01:00
containers = list_containers ( platform )
2020-08-05 19:40:22 -05:00
next if containers . nil?
2020-07-30 16:52:41 +01:00
# Using print so not to mess up table formatting
2020-08-05 18:01:14 -05:00
print_line ( containers . to_s )
2020-07-30 16:52:41 +01:00
p = store_loot ( " host. #{ platform } _containers " , 'text/plain' , session , containers , " #{ platform } _containers.txt " , " #{ platform } Containers " )
print_good ( " Results stored in: #{ p } \n " )
2020-07-18 12:11:35 +01:00
next if cmd . blank?
running_container_ids = list_running_containers_id ( platform )
2020-08-05 19:40:22 -05:00
next if running_container_ids . nil?
2020-08-06 09:31:17 +01:00
2020-07-18 12:11:35 +01:00
running_container_ids . each do | container_id |
print_status ( " Executing command on #{ platform } container #{ container_id } " )
2020-08-05 19:40:22 -05:00
command_result = container_execute ( platform , container_id , cmd )
2020-08-06 09:31:17 +01:00
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 " )
2020-07-18 12:11:35 +01:00
end
2020-07-13 11:22:16 +01:00
end
end
end