Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions documentation/modules/post/linux/gather/enum_containers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
## Container Platforms

This module looks for container platforms running on the target and then lists any currently running containers for each platform found. The currently supported container platforms are:

1. Docker
2. LXC
3. RKT

## Verification Steps

1. Start msfconsole
2. Get a session via exploit of your choice
3. Load the module `use post/linux/gather/enum_containers`
4. Set the session `set session 1`
5. run the module `run`
6. You should get feedback if any container platforms are runnable by the current user and if there are any active containers running on them

## Options

**SESSION**

Which session to use, which can be viewed with `sessions -l`

**CMD**

Optional shell command to run on each running container

## Scenarios

Scenario 1: Docker is installed with 4 running containers
```
msf5 post(linux/gather/enum_containers) > set session 4
session => 4
msf5 post(linux/gather/enum_containers) > run

[+] docker was found on the system!
[+] docker: 1 Running Containers / 5 Total
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
853913ae1e17 nginx "/docker-entrypoint.…" About an hour ago Up About an hour 80/tcp lucid_tu
0422ad0a1d6e nginx "/docker-entrypoint.…" About an hour ago Exited (0) About an hour ago gifted_thompson
35930fd284e1 nginx "/docker-entrypoint.…" 2 days ago Exited (0) 5 hours ago unruffled_gates
a7149a9a858e nginx "/docker-entrypoint.…" 2 days ago Exited (127) 2 days ago pedantic_tesla
cfa40ec4d85c nginx "/docker-entrypoint.…" 2 days ago Exited (0) 2 days ago fervent_gates
[+] Results stored in: /home/gwillcox/.msf4/loot/20200805143522_default_172.27.129.4_host.docker_cont_134332.txt
[*] Post module execution completed
```

Scenario 2: Docker, LXC and RKT are installed, and each of them are running their own containers
```
msf5 post(linux/gather/enum_containers) > set session 2
session => 2
msf5 post(linux/gather/enum_containers) > exploit

[+] docker was found on the system!
[+] docker: 1 Running Containers / 5 Total
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
853913ae1e17 nginx "/docker-entrypoint.…" About an hour ago Up About an hour 80/tcp lucid_tu
0422ad0a1d6e nginx "/docker-entrypoint.…" About an hour ago Exited (0) About an hour ago gifted_thompson
35930fd284e1 nginx "/docker-entrypoint.…" 2 days ago Exited (0) 5 hours ago unruffled_gates
a7149a9a858e nginx "/docker-entrypoint.…" 2 days ago Exited (127) 2 days ago pedantic_tesla
cfa40ec4d85c nginx "/docker-entrypoint.…" 2 days ago Exited (0) 2 days ago fervent_gates
[+] Results stored in: /home/gwillcox/.msf4/loot/20200805193841_default_172.27.129.4_host.docker_cont_169517.txt

[+] lxc was found on the system!
[+] lxc: 1 Running Containers / 1 Total
NAME STATE IPV4 IPV6 TYPE SNAPSHOTS
one-fox RUNNING 10.166.198.97 (eth0) fd42:a29:a47e:79c6:216:3eff:fe1f:1dca (eth0) CONTAINER 0
[+] Results stored in: /home/gwillcox/.msf4/loot/20200805193842_default_172.27.129.4_host.lxc_contain_448673.txt

[+] rkt was found on the system!
[+] rkt: 2 Running Containers / 1 Total
UUID APP IMAGE NAME STATE CREATED STARTED NETWORKS
1f5f73a2 etcd coreos.com/etcd:v3.1.7 running 32 minutes ago 32 minutes ago default:ip4=172.16.28.3
384c8a25 etcd coreos.com/etcd:v3.1.7 exited garbage 4 hours ago 4 hours ago default:ip4=172.16.28.2
[+] Results stored in: /home/gwillcox/.msf4/loot/20200805193842_default_172.27.129.4_host.rkt_contain_801968.txt

[*] Post module execution completed
msf5 post(linux/gather/enum_containers) >

Scenario 3: No container software is runnable
```
msf5 post(linux/gather/enum_containers) > set session 6
session => 6
msf5 post(linux/gather/enum_containers) > run
[-] No container software appears to be installed or runnable by the current user
[*] Post module execution completed
```

Scenario 4: List all containers and execute the `env` command on all running containers
```
msf5 post(linux/gather/enum_containers) > set session 6
session => 6
msf5 post(linux/gather/enum_containers) > set CMD "env"
CMD => env
msf5 post(linux/gather/enum_containers) > run

[+] docker was found on the system!
[+] docker: 1 Running Containers / 5 Total
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
853913ae1e17 nginx "/docker-entrypoint.…" 2 hours ago Up 2 hours 80/tcp lucid_tu
0422ad0a1d6e nginx "/docker-entrypoint.…" 2 hours ago Exited (0) 2 hours ago gifted_thompson
35930fd284e1 nginx "/docker-entrypoint.…" 2 days ago Exited (0) 6 hours ago unruffled_gates
a7149a9a858e nginx "/docker-entrypoint.…" 2 days ago Exited (127) 2 days ago pedantic_tesla
cfa40ec4d85c nginx "/docker-entrypoint.…" 2 days ago Exited (0) 2 days ago fervent_gates
[+] Results stored in: /home/gwillcox/.msf4/loot/20200805202620_default_172.27.129.4_host.docker_cont_406553.txt

[*] Executing command on docker container lucid_tu
[+] PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=853913ae1e17
NGINX_VERSION=1.19.1
NJS_VERSION=0.4.2
PKG_RELEASE=1~buster
HOME=/root
[+] lxc was found on the system!
[+] lxc: 1 Running Containers / 1 Total
NAME STATE IPV4 IPV6 TYPE SNAPSHOTS
one-fox RUNNING 10.166.198.97 (eth0) fd42:a29:a47e:79c6:216:3eff:fe1f:1dca (eth0) CONTAINER 0
[+] Results stored in: /home/gwillcox/.msf4/loot/20200805202623_default_172.27.129.4_host.lxc_contain_977736.txt

[*] Executing command on lxc container one-fox
[+] PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
container=lxc
HOME=/root
USER=root
LANG=C.UTF-8
[+] rkt was found on the system!
[+] rkt: 2 Running Containers / 1 Total
UUID APP IMAGE NAME STATE CREATED STARTED NETWORKS
1f5f73a2 etcd coreos.com/etcd:v3.1.7 running 1 hour ago 1 hour ago default:ip4=172.16.28.3
384c8a25 etcd coreos.com/etcd:v3.1.7 exited garbage 5 hours ago 5 hours ago default:ip4=172.16.28.2
[+] Results stored in: /home/gwillcox/.msf4/loot/20200805202625_default_172.27.129.4_host.rkt_contain_522670.txt

[*] Executing command on rkt container 1f5f73a2
[-] RKT containers do not support command execution
Use rkt enter '1f5f73a2' to manually enumerate this container
[+] USER=root
HOME=/root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/system/bin:/system/sbin:/system/xbin
LANG=C
PWD=/home/gwillcox/git/metasploit-framework
[*] Executing command on rkt container 384c8a25
[-] RKT containers do not support command execution
Use rkt enter '384c8a25' to manually enumerate this container
[+] USER=root
HOME=/root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/system/bin:/system/sbin:/system/xbin
LANG=C
PWD=/home/gwillcox/git/metasploit-framework
[*] Post module execution completed
msf5 post(linux/gather/enum_containers) >
```
192 changes: 192 additions & 0 deletions modules/post/linux/gather/enum_containers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
##
# 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']
)
)
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, false)

if num_containers == 0
print_error("No active or inactive containers were found for #{platform}\n")
else
num_running_containers = count_containers(platform, 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